mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-16 04:35:24 +02:00
Compare commits
75 Commits
fix/env-ed
...
v0.29.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a0acd9cad | ||
|
|
64a606ffa4 | ||
|
|
29851491f6 | ||
|
|
95633b4122 | ||
|
|
c73632cbe0 | ||
|
|
30b3e1fe48 | ||
|
|
a07106d649 | ||
|
|
df98cea19f | ||
|
|
ccc8f6d047 | ||
|
|
222b167a76 | ||
|
|
bad9731878 | ||
|
|
7e13243c1d | ||
|
|
4d8a2a38e8 | ||
|
|
de3db08e60 | ||
|
|
a2d655083a | ||
|
|
f3356cfe90 | ||
|
|
2362778fe1 | ||
|
|
628f16e8cb | ||
|
|
ea8e99d76d | ||
|
|
d4719ece58 | ||
|
|
e679a322b9 | ||
|
|
f24f1ada5f | ||
|
|
5b6d80e177 | ||
|
|
2c9ca651a8 | ||
|
|
413ed9bd80 | ||
|
|
4f578516d6 | ||
|
|
1e57d48ab4 | ||
|
|
a177d34dfd | ||
|
|
1034c79245 | ||
|
|
304454b22d | ||
|
|
42c2076281 | ||
|
|
5cd7de8188 | ||
|
|
1352b859e2 | ||
|
|
1c2307b86f | ||
|
|
4832fd929c | ||
|
|
d1b639a55a | ||
|
|
40de13e4d4 | ||
|
|
f0ea1c8796 | ||
|
|
b45e7e415c | ||
|
|
67d3e92aaf | ||
|
|
76af74d8aa | ||
|
|
b15ede8877 | ||
|
|
ea805c1520 | ||
|
|
976932fb03 | ||
|
|
ac8960efdd | ||
|
|
d6050ce05a | ||
|
|
5a46b879f5 | ||
|
|
222e4878bd | ||
|
|
fd267a64de | ||
|
|
fa3cdf148b | ||
|
|
74caf141f4 | ||
|
|
8b7d9c0896 | ||
|
|
13e20e9ef8 | ||
|
|
f9b0589070 | ||
|
|
b615d04ad2 | ||
|
|
6c4efa48b1 | ||
|
|
85d48aba2b | ||
|
|
3b138f8e8a | ||
|
|
b91067dc2a | ||
|
|
335a16b915 | ||
|
|
274f38029c | ||
|
|
4cbc91d3d0 | ||
|
|
10d17de186 | ||
|
|
65f0919fa7 | ||
|
|
9b7abfbed7 | ||
|
|
6676a86b34 | ||
|
|
d603654ac1 | ||
|
|
d9ffe519b0 | ||
|
|
fa91a74462 | ||
|
|
d7794286be | ||
|
|
f337dd7e01 | ||
|
|
5d5d95bbd3 | ||
|
|
7be1084a10 | ||
|
|
19a525fac1 | ||
|
|
7984497398 |
79
.github/workflows/dokploy.yml
vendored
79
.github/workflows/dokploy.yml
vendored
@@ -138,6 +138,8 @@ jobs:
|
||||
needs: [combine-manifests]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.get_version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -160,3 +162,80 @@ jobs:
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
sync-version:
|
||||
needs: [generate-release]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Sync version to MCP repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo
|
||||
cd /tmp/mcp-repo
|
||||
|
||||
jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run fetch-openapi
|
||||
pnpm run generate
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
--allow-empty
|
||||
git push
|
||||
|
||||
echo "✅ MCP repo synced to version ${{ needs.generate-release.outputs.version }}"
|
||||
|
||||
- name: Sync version to CLI repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo
|
||||
cd /tmp/cli-repo
|
||||
|
||||
jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
cp ${{ github.workspace }}/openapi.json ./openapi.json
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run generate
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
--allow-empty
|
||||
git push
|
||||
|
||||
echo "✅ CLI repo synced to version ${{ needs.generate-release.outputs.version }}"
|
||||
|
||||
- name: Sync version to SDK repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/sdk.git /tmp/sdk-repo
|
||||
cd /tmp/sdk-repo
|
||||
|
||||
jq --arg v "${{ needs.generate-release.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
cp ${{ github.workspace }}/openapi.json ./openapi.json
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run generate
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ needs.generate-release.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
--allow-empty
|
||||
git push
|
||||
|
||||
echo "✅ SDK repo synced to version ${{ needs.generate-release.outputs.version }}"
|
||||
|
||||
83
.github/workflows/sync-version.yml
vendored
83
.github/workflows/sync-version.yml
vendored
@@ -1,83 +0,0 @@
|
||||
name: Sync version to MCP and CLI repos
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-version:
|
||||
name: Sync version to external repos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Dokploy repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(jq -r .version apps/dokploy/package.json | sed 's/^v//')
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: Sync version to MCP repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/mcp.git /tmp/mcp-repo
|
||||
cd /tmp/mcp-repo
|
||||
|
||||
# Regenerate tools from latest OpenAPI spec
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run fetch-openapi
|
||||
pnpm run generate
|
||||
|
||||
# Bump version after install so pnpm install doesn't overwrite it
|
||||
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
-m "Release: ${{ github.event.release.html_url }}" \
|
||||
--allow-empty
|
||||
|
||||
git push
|
||||
|
||||
|
||||
- name: Sync version to CLI repository
|
||||
run: |
|
||||
git clone https://x-access-token:${{ secrets.DOCS_SYNC_TOKEN }}@github.com/dokploy/cli.git /tmp/cli-repo
|
||||
|
||||
cd /tmp/cli-repo
|
||||
|
||||
# Copy latest openapi spec and regenerate commands
|
||||
cp ${{ github.workspace }}/openapi.json ./openapi.json
|
||||
npm install -g pnpm
|
||||
pnpm install
|
||||
pnpm run generate
|
||||
|
||||
# Bump version after install so pnpm install doesn't overwrite it
|
||||
if [ -f package.json ]; then
|
||||
jq --arg v "${{ steps.get_version.outputs.version }}" '.version = $v' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
fi
|
||||
|
||||
git config user.name "Dokploy Bot"
|
||||
git config user.email "bot@dokploy.com"
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${{ steps.get_version.outputs.version }}" \
|
||||
-m "Source: ${{ github.repository }}@${{ github.sha }}" \
|
||||
-m "Release: ${{ github.event.release.html_url }}" \
|
||||
--allow-empty
|
||||
|
||||
git push
|
||||
|
||||
echo "CLI repo synced to version ${{ steps.get_version.outputs.version }}"
|
||||
|
||||
52
apps/dokploy/__test__/compose/build-compose-command.test.ts
Normal file
52
apps/dokploy/__test__/compose/build-compose-command.test.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { getBuildComposeCommand } from "@dokploy/server/utils/builders/compose";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Isolate the command builder from the compose-file I/O performed by
|
||||
// writeDomainsToCompose; we only care about the docker invocation it emits.
|
||||
vi.mock("@dokploy/server/utils/docker/domain", () => ({
|
||||
writeDomainsToCompose: vi.fn().mockResolvedValue(""),
|
||||
}));
|
||||
|
||||
const baseCompose = {
|
||||
appName: "my-app",
|
||||
sourceType: "raw",
|
||||
command: "",
|
||||
composePath: "docker-compose.yml",
|
||||
composeType: "stack",
|
||||
isolatedDeployment: false,
|
||||
randomize: false,
|
||||
suffix: "",
|
||||
serverId: null,
|
||||
env: "",
|
||||
mounts: [],
|
||||
domains: [],
|
||||
environment: { project: { env: "" }, env: "" },
|
||||
} as unknown as Parameters<typeof getBuildComposeCommand>[0];
|
||||
|
||||
// Regression coverage for #4401: the deploy command runs under `env -i`, which
|
||||
// clears the environment except for the vars listed explicitly. HOME must be
|
||||
// preserved so docker can resolve ~/.docker/config.json — otherwise
|
||||
// `docker stack deploy --with-registry-auth` ships no credentials to the swarm
|
||||
// and private-registry images fail to pull.
|
||||
describe("getBuildComposeCommand registry auth (#4401)", () => {
|
||||
it("preserves HOME for swarm stack deploys", async () => {
|
||||
const command = await getBuildComposeCommand({
|
||||
...baseCompose,
|
||||
composeType: "stack",
|
||||
});
|
||||
|
||||
expect(command).toContain("stack deploy");
|
||||
expect(command).toContain("--with-registry-auth");
|
||||
expect(command).toContain('env -i PATH="$PATH" HOME="$HOME"');
|
||||
});
|
||||
|
||||
it("preserves HOME for docker compose deploys", async () => {
|
||||
const command = await getBuildComposeCommand({
|
||||
...baseCompose,
|
||||
composeType: "docker-compose",
|
||||
});
|
||||
|
||||
expect(command).toContain("compose -p my-app");
|
||||
expect(command).toContain('env -i PATH="$PATH" HOME="$HOME"');
|
||||
});
|
||||
});
|
||||
@@ -103,6 +103,51 @@ describe("createDomainLabels", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should add tls=true for certificateType none on websecure entrypoint", async () => {
|
||||
const noneDomain = {
|
||||
...baseDomain,
|
||||
https: true,
|
||||
certificateType: "none" as const,
|
||||
};
|
||||
const labels = await createDomainLabels(appName, noneDomain, "websecure");
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-websecure.tls=true",
|
||||
);
|
||||
// no cert resolver should be set when relying on a default/custom cert
|
||||
expect(labels).not.toContain(
|
||||
"traefik.http.routers.test-app-1-websecure.tls.certresolver=letsencrypt",
|
||||
);
|
||||
});
|
||||
|
||||
it("should not add tls=true for certificateType none on web entrypoint", async () => {
|
||||
const noneDomain = {
|
||||
...baseDomain,
|
||||
https: true,
|
||||
certificateType: "none" as const,
|
||||
};
|
||||
const labels = await createDomainLabels(appName, noneDomain, "web");
|
||||
expect(labels).not.toContain(
|
||||
"traefik.http.routers.test-app-1-web.tls=true",
|
||||
);
|
||||
});
|
||||
|
||||
it("should add tls=true for certificateType none on a custom https entrypoint", async () => {
|
||||
const noneDomain = {
|
||||
...baseDomain,
|
||||
https: true,
|
||||
customEntrypoint: "websecure-custom",
|
||||
certificateType: "none" as const,
|
||||
};
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
noneDomain,
|
||||
"websecure-custom",
|
||||
);
|
||||
expect(labels).toContain(
|
||||
"traefik.http.routers.test-app-1-websecure-custom.tls=true",
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle different ports correctly", async () => {
|
||||
const customPortDomain = { ...baseDomain, port: 3000 };
|
||||
const labels = await createDomainLabels(appName, customPortDomain, "web");
|
||||
|
||||
41
apps/dokploy/__test__/deploy/should-deploy.test.ts
Normal file
41
apps/dokploy/__test__/deploy/should-deploy.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { shouldDeploy } from "@dokploy/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("shouldDeploy", () => {
|
||||
it("should deploy when no watch paths are configured", () => {
|
||||
expect(shouldDeploy(null, ["src/index.ts"])).toBe(true);
|
||||
expect(shouldDeploy([], ["src/index.ts"])).toBe(true);
|
||||
});
|
||||
|
||||
it("should deploy when watch paths match modified files", () => {
|
||||
expect(shouldDeploy(["src/**"], ["src/index.ts"])).toBe(true);
|
||||
expect(shouldDeploy(["apps/web/**"], ["apps/web/page.tsx"])).toBe(true);
|
||||
});
|
||||
|
||||
it("should not deploy when watch paths do not match", () => {
|
||||
expect(shouldDeploy(["src/**"], ["docs/readme.md"])).toBe(false);
|
||||
});
|
||||
|
||||
it("should not throw when modified files contain non-string values", () => {
|
||||
expect(() =>
|
||||
shouldDeploy(["src/**"], ["src/index.ts", undefined, null] as any),
|
||||
).not.toThrow();
|
||||
expect(
|
||||
shouldDeploy(["src/**"], ["src/index.ts", undefined, null] as any),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should not throw when modified files are undefined or null", () => {
|
||||
expect(() => shouldDeploy(["src/**"], undefined)).not.toThrow();
|
||||
expect(() => shouldDeploy(["src/**"], null)).not.toThrow();
|
||||
expect(shouldDeploy(["src/**"], undefined)).toBe(false);
|
||||
expect(shouldDeploy(["src/**"], null)).toBe(false);
|
||||
});
|
||||
|
||||
it("should not throw when every modified file is non-string", () => {
|
||||
expect(() =>
|
||||
shouldDeploy(["src/**"], [undefined, undefined] as any),
|
||||
).not.toThrow();
|
||||
expect(shouldDeploy(["src/**"], [undefined, undefined] as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -58,7 +58,7 @@ beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("static roles bypass enterprise resources", () => {
|
||||
describe("owner and admin bypass enterprise resources", () => {
|
||||
it("owner bypasses deployment.read", async () => {
|
||||
memberToReturn = mockMemberData("owner");
|
||||
await expect(
|
||||
@@ -73,15 +73,8 @@ describe("static roles bypass enterprise resources", () => {
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("member bypasses schedule.delete", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(
|
||||
checkPermission(ctx, { schedule: ["delete"] }),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("member bypasses multiple enterprise permissions at once", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
it("owner bypasses multiple enterprise permissions at once", async () => {
|
||||
memberToReturn = mockMemberData("owner");
|
||||
await expect(
|
||||
checkPermission(ctx, {
|
||||
deployment: ["read"],
|
||||
@@ -92,6 +85,55 @@ describe("static roles bypass enterprise resources", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("member is denied org-level enterprise resources (CVE: bypass via staticRoles)", () => {
|
||||
it("member is denied registry.read", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(
|
||||
checkPermission(ctx, { registry: ["read"] }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("member is denied certificate.read", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(
|
||||
checkPermission(ctx, { certificate: ["read"] }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("member is denied destination.read", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(
|
||||
checkPermission(ctx, { destination: ["read"] }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("member is denied notification.read", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(
|
||||
checkPermission(ctx, { notification: ["read"] }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("member is denied auditLog.read", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(
|
||||
checkPermission(ctx, { auditLog: ["read"] }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("member is denied server.read", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(checkPermission(ctx, { server: ["read"] })).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("member is denied registry.create", async () => {
|
||||
memberToReturn = mockMemberData("member");
|
||||
await expect(
|
||||
checkPermission(ctx, { registry: ["create"] }),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("static roles validate free-tier resources", () => {
|
||||
it("owner passes project.create", async () => {
|
||||
memberToReturn = mockMemberData("owner");
|
||||
|
||||
@@ -65,6 +65,8 @@ const baseSettings: WebServerSettings = {
|
||||
cleanupCacheApplications: false,
|
||||
cleanupCacheOnCompose: false,
|
||||
cleanupCacheOnPreviews: false,
|
||||
remoteServersOnly: false,
|
||||
enforceSSO: false,
|
||||
createdAt: null,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
@@ -78,4 +78,20 @@ describe("readValidDirectory (path traversal)", () => {
|
||||
it("returns false for empty string (resolves to cwd)", () => {
|
||||
expect(readValidDirectory("")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true for Next.js dynamic route paths with square brackets", () => {
|
||||
expect(
|
||||
readValidDirectory(
|
||||
`${BASE}/applications/myapp/code/app/api/[id]/route.ts`,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
readValidDirectory(`${BASE}/applications/myapp/code/pages/[slug].tsx`),
|
||||
).toBe(true);
|
||||
expect(
|
||||
readValidDirectory(
|
||||
`${BASE}/applications/myapp/code/app/[...catch]/page.tsx`,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -224,7 +224,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<FormLabel>Memory Limit</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -263,7 +263,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<FormLabel>Memory Reservation</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -303,7 +303,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<FormLabel>CPU Limit</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -343,7 +343,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<FormLabel>CPU Reservation</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
@@ -379,7 +379,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
||||
<FormLabel className="text-base">Ulimits</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={0}>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
|
||||
@@ -806,7 +806,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||
<FormLabel>Middlewares</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||
?
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { HelpCircle, KeyRoundIcon, LockIcon, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -6,7 +7,6 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -5,7 +6,6 @@ import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -422,7 +422,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
||||
<FormLabel>Watch Paths</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||
?
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { HelpCircle, KeyRoundIcon, LockIcon, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -6,7 +7,6 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
@@ -449,7 +449,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
||||
<FormLabel>Watch Paths</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||
?
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -5,7 +6,6 @@ import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -440,7 +440,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
||||
<FormLabel>Watch Paths</FormLabel>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<TooltipTrigger type="button">
|
||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||
?
|
||||
</div>
|
||||
|
||||
@@ -90,7 +90,7 @@ export function AnalyzeLogs({ logs, context }: Props) {
|
||||
disabled={logs.length === 0}
|
||||
title="Analyze logs with AI"
|
||||
>
|
||||
<Bot className="mr-2 h-4 w-4" />
|
||||
<Bot className="mr-2 size-4" />
|
||||
AI
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
||||
@@ -347,11 +347,13 @@ export const DockerLogsId: React.FC<Props> = ({
|
||||
title={isPaused ? "Resume logs" : "Pause logs"}
|
||||
>
|
||||
{isPaused ? (
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
<Play className="size-4" />
|
||||
) : (
|
||||
<Pause className="mr-2 h-4 w-4" />
|
||||
<Pause className="size-4" />
|
||||
)}
|
||||
{isPaused ? "Resume" : "Pause"}
|
||||
<span className="hidden lg:ml-2 lg:inline">
|
||||
{isPaused ? "Resume" : "Pause"}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -362,11 +364,13 @@ export const DockerLogsId: React.FC<Props> = ({
|
||||
title="Copy logs to clipboard"
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="mr-2 h-4 w-4" />
|
||||
<Check className="size-4" />
|
||||
) : (
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
<Copy className="size-4" />
|
||||
)}
|
||||
Copy
|
||||
<span className="hidden lg:ml-2 lg:inline">
|
||||
{copied ? "Copied" : "Copy"}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -374,17 +378,18 @@ export const DockerLogsId: React.FC<Props> = ({
|
||||
className="h-9 sm:w-auto w-full"
|
||||
onClick={handleDownload}
|
||||
disabled={filteredLogs.length === 0 || !data?.Name}
|
||||
title="Download logs as text file"
|
||||
>
|
||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
||||
Download logs
|
||||
<DownloadIcon className="size-4" />
|
||||
<span className="hidden lg:ml-2 lg:inline">Download logs</span>
|
||||
</Button>
|
||||
<AnalyzeLogs logs={filteredLogs} context="runtime" />
|
||||
</div>
|
||||
</div>
|
||||
{isPaused && (
|
||||
<AlertBlock type="warning">
|
||||
<AlertBlock type="warning" className="items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<Pause className="h-4 w-4" />
|
||||
<Pause className="size-4" />
|
||||
<span>
|
||||
Logs paused
|
||||
{messageBuffer.length > 0 && (
|
||||
|
||||
@@ -71,6 +71,9 @@ interface Props {
|
||||
export const AddApplication = ({ environmentId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: webServerSettings } =
|
||||
api.settings.getWebServerSettings.useQuery();
|
||||
const showLocalOption = !isCloud && !webServerSettings?.remoteServersOnly;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
@@ -171,7 +174,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||
Select a Server{" "}
|
||||
{showLocalOption ? "(Optional)" : ""}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
</TooltipTrigger>
|
||||
@@ -191,17 +195,19 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={
|
||||
field.value || (!isCloud ? "dokploy" : undefined)
|
||||
field.value || (showLocalOption ? "dokploy" : undefined)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
||||
placeholder={
|
||||
showLocalOption ? "Dokploy" : "Select a Server"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{!isCloud && (
|
||||
{showLocalOption && (
|
||||
<SelectItem value="dokploy">
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>Dokploy</span>
|
||||
@@ -225,7 +231,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>
|
||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||
Servers (
|
||||
{servers?.length + (showLocalOption ? 1 : 0)})
|
||||
</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
|
||||
@@ -74,6 +74,9 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: webServerSettings } =
|
||||
api.settings.getWebServerSettings.useQuery();
|
||||
const showLocalOption = !isCloud && !webServerSettings?.remoteServersOnly;
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
const { mutateAsync, isPending, error, isError } =
|
||||
api.compose.create.useMutation();
|
||||
@@ -182,7 +185,8 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||
Select a Server{" "}
|
||||
{showLocalOption ? "(Optional)" : ""}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
</TooltipTrigger>
|
||||
@@ -202,17 +206,19 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={
|
||||
field.value || (!isCloud ? "dokploy" : undefined)
|
||||
field.value || (showLocalOption ? "dokploy" : undefined)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
||||
placeholder={
|
||||
showLocalOption ? "Dokploy" : "Select a Server"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{!isCloud && (
|
||||
{showLocalOption && (
|
||||
<SelectItem value="dokploy">
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>Dokploy</span>
|
||||
@@ -236,7 +242,8 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>
|
||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||
Servers (
|
||||
{servers?.length + (showLocalOption ? 1 : 0)})
|
||||
</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
|
||||
@@ -219,6 +219,9 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const slug = slugify(projectName);
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: webServerSettings } =
|
||||
api.settings.getWebServerSettings.useQuery();
|
||||
const showLocalOption = !isCloud && !webServerSettings?.remoteServersOnly;
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
const libsqlMutation = api.libsql.create.useMutation();
|
||||
const mariadbMutation = api.mariadb.create.useMutation();
|
||||
@@ -470,19 +473,20 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={
|
||||
field.value || (!isCloud ? "dokploy" : undefined)
|
||||
field.value ||
|
||||
(showLocalOption ? "dokploy" : undefined)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
!isCloud ? "Dokploy" : "Select a Server"
|
||||
showLocalOption ? "Dokploy" : "Select a Server"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{!isCloud && (
|
||||
{showLocalOption && (
|
||||
<SelectItem value="dokploy">
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>Dokploy</span>
|
||||
@@ -501,7 +505,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>
|
||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
||||
Servers (
|
||||
{servers?.length + (showLocalOption ? 1 : 0)})
|
||||
</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
|
||||
494
apps/dokploy/components/dashboard/project/add-import.tsx
Normal file
494
apps/dokploy/components/dashboard/project/add-import.tsx
Normal file
@@ -0,0 +1,494 @@
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { Code2, FileInput, Globe2, HardDrive, HelpCircle } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { slugify } from "@/lib/slug";
|
||||
import { api } from "@/utils/api";
|
||||
import { APP_NAME_MESSAGE, APP_NAME_REGEX } from "@/utils/schema";
|
||||
|
||||
const AddImportSchema = z.object({
|
||||
name: z.string().min(1, { message: "Name is required" }),
|
||||
appName: z
|
||||
.string()
|
||||
.min(1, { message: "App name is required" })
|
||||
.regex(APP_NAME_REGEX, { message: APP_NAME_MESSAGE }),
|
||||
base64: z.string().min(1, { message: "Base64 content is required" }),
|
||||
serverId: z.string().optional(),
|
||||
});
|
||||
|
||||
type AddImport = z.infer<typeof AddImportSchema>;
|
||||
|
||||
type TemplateInfo = {
|
||||
compose: string;
|
||||
template: {
|
||||
domains: Array<{
|
||||
serviceName: string;
|
||||
port: number;
|
||||
path?: string;
|
||||
host?: string;
|
||||
}>;
|
||||
envs: string[];
|
||||
mounts: Array<{ filePath: string; content: string }>;
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
environmentId: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
export const AddImport = ({ environmentId, projectName }: Props) => {
|
||||
const utils = api.useUtils();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [previewOpen, setPreviewOpen] = useState(false);
|
||||
const [mountOpen, setMountOpen] = useState(false);
|
||||
const [selectedMount, setSelectedMount] = useState<{
|
||||
filePath: string;
|
||||
content: string;
|
||||
} | null>(null);
|
||||
const [templateInfo, setTemplateInfo] = useState<TemplateInfo | null>(null);
|
||||
|
||||
const slug = slugify(projectName);
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||
const shouldShowServerDropdown = !!(servers && servers.length > 0);
|
||||
|
||||
const { mutateAsync: previewTemplate, isPending: isProcessing } =
|
||||
api.compose.previewTemplate.useMutation();
|
||||
const { mutateAsync: createCompose, isPending: isCreating } =
|
||||
api.compose.create.useMutation();
|
||||
const { mutateAsync: importCompose, isPending: isImporting } =
|
||||
api.compose.import.useMutation();
|
||||
|
||||
const form = useForm<AddImport>({
|
||||
defaultValues: { name: "", appName: `${slug}-`, base64: "" },
|
||||
resolver: zodResolver(AddImportSchema),
|
||||
});
|
||||
|
||||
const resetAll = () => {
|
||||
form.reset({ name: "", appName: `${slug}-`, base64: "" });
|
||||
setTemplateInfo(null);
|
||||
setPreviewOpen(false);
|
||||
setMountOpen(false);
|
||||
setSelectedMount(null);
|
||||
};
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) resetAll();
|
||||
setVisible(open);
|
||||
};
|
||||
|
||||
const handleLoad = async (data: AddImport) => {
|
||||
try {
|
||||
const result = await previewTemplate({
|
||||
appName: data.appName,
|
||||
base64: data.base64.trim(),
|
||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||
});
|
||||
setTemplateInfo(result);
|
||||
setPreviewOpen(true);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Error processing template",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleImport = async () => {
|
||||
const data = form.getValues();
|
||||
try {
|
||||
const compose = await createCompose({
|
||||
name: data.name,
|
||||
appName: data.appName,
|
||||
environmentId,
|
||||
composeType: "docker-compose",
|
||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
||||
});
|
||||
await importCompose({
|
||||
composeId: compose.composeId,
|
||||
base64: data.base64.trim(),
|
||||
});
|
||||
toast.success("Compose imported successfully");
|
||||
await utils.environment.one.invalidate({ environmentId });
|
||||
resetAll();
|
||||
setVisible(false);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Error importing compose",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelPreview = () => {
|
||||
setPreviewOpen(false);
|
||||
setTemplateInfo(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={visible} onOpenChange={handleOpenChange}>
|
||||
<DialogTrigger className="w-full">
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<FileInput className="size-4 text-muted-foreground" />
|
||||
<span>Import</span>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Import Compose</DialogTitle>
|
||||
<DialogDescription>
|
||||
Paste a base64-encoded compose export to preview and import it
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-import"
|
||||
onSubmit={form.handleSubmit(handleLoad)}
|
||||
className="grid w-full gap-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My App"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value || "";
|
||||
form.setValue(
|
||||
"appName",
|
||||
`${slug}-${slugify(val.trim())}`,
|
||||
);
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{shouldShowServerDropdown && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="serverId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</FormLabel>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className="z-[999] w-[300px]"
|
||||
align="start"
|
||||
side="top"
|
||||
>
|
||||
<span>
|
||||
If no server is selected, the compose will be
|
||||
deployed on the server where the user is logged
|
||||
in.
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={
|
||||
field.value || (!isCloud ? "dokploy" : undefined)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={
|
||||
!isCloud ? "Dokploy" : "Select a Server"
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
{!isCloud && (
|
||||
<SelectItem value="dokploy">
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>Dokploy</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
Default
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
)}
|
||||
{servers?.map((server) => (
|
||||
<SelectItem
|
||||
key={server.serverId}
|
||||
value={server.serverId}
|
||||
>
|
||||
<span className="flex items-center gap-2 justify-between w-full">
|
||||
<span>{server.name}</span>
|
||||
<span className="text-muted-foreground text-xs self-center">
|
||||
{server.ipAddress}
|
||||
</span>
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
<SelectLabel>
|
||||
Servers (
|
||||
{(servers?.length ?? 0) + (!isCloud ? 1 : 0)})
|
||||
</SelectLabel>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="appName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>App Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="my-app" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="base64"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Configuration (Base64)</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Paste your base64-encoded compose export here..."
|
||||
className="font-mono resize-none h-32"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
isLoading={isCreating || isProcessing}
|
||||
>
|
||||
Load
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Preview modal */}
|
||||
<Dialog
|
||||
open={previewOpen}
|
||||
onOpenChange={(open) => !open && handleCancelPreview()}
|
||||
>
|
||||
<DialogContent className="max-w-[60vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-bold">
|
||||
Template Information
|
||||
</DialogTitle>
|
||||
<DialogDescription className="space-y-2">
|
||||
<p>Review the template information before importing</p>
|
||||
<AlertBlock type="warning">
|
||||
Warning: This will remove all existing environment variables,
|
||||
mounts, and domains from this service.
|
||||
</AlertBlock>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Code2 className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">Docker Compose</h3>
|
||||
</div>
|
||||
<CodeEditor
|
||||
language="yaml"
|
||||
value={templateInfo?.compose || ""}
|
||||
className="font-mono"
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
{templateInfo?.template.domains &&
|
||||
templateInfo.template.domains.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe2 className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">Domains</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{templateInfo.template.domains.map((domain, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg border bg-card p-3 text-card-foreground shadow-sm"
|
||||
>
|
||||
<div className="font-medium">
|
||||
{domain.serviceName}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div>Port: {domain.port}</div>
|
||||
{domain.host && <div>Host: {domain.host}</div>}
|
||||
{domain.path && <div>Path: {domain.path}</div>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{templateInfo?.template.envs &&
|
||||
templateInfo.template.envs.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Code2 className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">
|
||||
Environment Variables
|
||||
</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{templateInfo.template.envs.map((env, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg truncate border bg-card p-2 font-mono text-sm"
|
||||
>
|
||||
{env}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{templateInfo?.template.mounts &&
|
||||
templateInfo.template.mounts.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<HardDrive className="h-5 w-5 text-primary" />
|
||||
<h3 className="text-lg font-semibold">Mounts</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{templateInfo.template.mounts.map((mount, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg border bg-card p-2 font-mono text-sm hover:bg-accent cursor-pointer transition-colors"
|
||||
onClick={() => {
|
||||
setSelectedMount(mount);
|
||||
setMountOpen(true);
|
||||
}}
|
||||
>
|
||||
{mount.filePath}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button variant="outline" onClick={handleCancelPreview}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button isLoading={isImporting} onClick={handleImport}>
|
||||
Import
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Mount content modal */}
|
||||
<Dialog open={mountOpen} onOpenChange={setMountOpen}>
|
||||
<DialogContent className="max-w-[50vw]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl font-bold">
|
||||
{selectedMount?.filePath}
|
||||
</DialogTitle>
|
||||
<DialogDescription>Mount File Content</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="h-[45vh] pr-4">
|
||||
<CodeEditor
|
||||
language="yaml"
|
||||
value={selectedMount?.content || ""}
|
||||
className="font-mono"
|
||||
readOnly
|
||||
/>
|
||||
</ScrollArea>
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button onClick={() => setMountOpen(false)}>Close</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
export const ToggleEnforceSSO = () => {
|
||||
const { data, refetch } = api.settings.getWebServerSettings.useQuery();
|
||||
const { mutateAsync } = api.settings.updateEnforceSSO.useMutation();
|
||||
|
||||
const handleToggle = async (checked: boolean) => {
|
||||
try {
|
||||
await mutateAsync({ enforceSSO: checked });
|
||||
await refetch();
|
||||
toast.success("Enforce SSO updated");
|
||||
} catch {
|
||||
toast.error("Error updating Enforce SSO");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch checked={!!data?.enforceSSO} onCheckedChange={handleToggle} />
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Label className="text-primary flex items-center gap-1.5 cursor-pointer">
|
||||
Enforce SSO
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</Label>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-sm">
|
||||
<p>
|
||||
When enabled, the email/password login form is hidden and users
|
||||
must sign in exclusively through SSO.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
export const ToggleRemoteServersOnly = () => {
|
||||
const { data, refetch } = api.settings.getWebServerSettings.useQuery();
|
||||
|
||||
const { mutateAsync } = api.settings.updateRemoteServersOnly.useMutation();
|
||||
|
||||
const handleToggle = async (checked: boolean) => {
|
||||
try {
|
||||
await mutateAsync({ remoteServersOnly: checked });
|
||||
await refetch();
|
||||
toast.success("Remote Servers Only updated");
|
||||
} catch {
|
||||
toast.error("Error updating Remote Servers Only");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch
|
||||
checked={!!data?.remoteServersOnly}
|
||||
onCheckedChange={handleToggle}
|
||||
/>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Label className="text-primary flex items-center gap-1.5 cursor-pointer">
|
||||
Remote Servers Only
|
||||
<HelpCircle className="size-4 text-muted-foreground" />
|
||||
</Label>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-sm">
|
||||
<p>
|
||||
When enabled, all services (applications, databases, compose) must
|
||||
be deployed to a remote server. Deploying directly to the Dokploy
|
||||
host VM is not allowed.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -131,10 +131,10 @@ export const ShowServers = () => {
|
||||
className="relative hover:shadow-lg transition-shadow flex flex-col bg-transparent"
|
||||
>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<ServerIcon className="size-5 text-muted-foreground" />
|
||||
<CardTitle className="text-lg">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<ServerIcon className="size-5 shrink-0 text-muted-foreground" />
|
||||
<CardTitle className="text-lg break-words min-w-0">
|
||||
{server.name}
|
||||
</CardTitle>
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@ export const ShowServers = () => {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 w-8 p-0"
|
||||
className="h-8 w-8 shrink-0 p-0"
|
||||
>
|
||||
<span className="sr-only">
|
||||
More options
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { ServerIcon } from "lucide-react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { CopyIcon, ServerIcon } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -49,8 +51,17 @@ export const WebServer = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center flex-wrap justify-between gap-4">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-sm text-muted-foreground flex items-center gap-1.5">
|
||||
Server IP: {webServerSettings?.serverIp}
|
||||
{webServerSettings?.serverIp && (
|
||||
<CopyIcon
|
||||
className="size-3.5 cursor-pointer hover:text-foreground transition-colors"
|
||||
onClick={() => {
|
||||
copy(webServerSettings.serverIp ?? "");
|
||||
toast.success("Copied to clipboard");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Version: {dokployVersion}
|
||||
|
||||
@@ -29,10 +29,15 @@ type SSOEmailForm = z.infer<typeof ssoEmailSchema>;
|
||||
|
||||
interface SignInWithSSOProps {
|
||||
/** Content shown when SSO is collapsed (e.g. email/password form) */
|
||||
children: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
/** When true, SSO is the only option — no fallback to email/password */
|
||||
enforce?: boolean;
|
||||
}
|
||||
|
||||
export function SignInWithSSO({ children }: SignInWithSSOProps) {
|
||||
export function SignInWithSSO({
|
||||
children,
|
||||
enforce = false,
|
||||
}: SignInWithSSOProps) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const form = useForm<SSOEmailForm>({
|
||||
@@ -72,7 +77,7 @@ export function SignInWithSSO({ children }: SignInWithSSOProps) {
|
||||
<LogIn className="mr-2 size-4" />
|
||||
Sign in with SSO
|
||||
</Button>
|
||||
{children}
|
||||
{!enforce && children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -113,13 +118,15 @@ export function SignInWithSSO({ children }: SignInWithSSOProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(false)}
|
||||
className="text-xs text-muted-foreground hover:underline"
|
||||
>
|
||||
Use email and password instead
|
||||
</button>
|
||||
{!enforce && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(false)}
|
||||
className="text-xs text-muted-foreground hover:underline"
|
||||
>
|
||||
Use email and password instead
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,13 @@ export const CodeEditor = ({
|
||||
? css()
|
||||
: language === "shell"
|
||||
? StreamLanguage.define(shell)
|
||||
: StreamLanguage.define(properties),
|
||||
: StreamLanguage.define({
|
||||
...properties,
|
||||
// The legacy properties mode lacks comment metadata, so
|
||||
// CodeMirror's toggle-comment shortcut (Mod-/) has no comment
|
||||
// token to use. Declare `#` as the line comment for env editors.
|
||||
languageData: { commentTokens: { line: "#" } },
|
||||
}),
|
||||
props.lineWrapping ? EditorView.lineWrapping : [],
|
||||
language === "yaml"
|
||||
? autocompletion({
|
||||
|
||||
1
apps/dokploy/drizzle/0167_fresh_goliath.sql
Normal file
1
apps/dokploy/drizzle/0167_fresh_goliath.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "webServerSettings" ADD COLUMN "remoteServersOnly" boolean DEFAULT false NOT NULL;
|
||||
1
apps/dokploy/drizzle/0168_long_justice.sql
Normal file
1
apps/dokploy/drizzle/0168_long_justice.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "webServerSettings" ADD COLUMN "enforceSSO" boolean DEFAULT false NOT NULL;
|
||||
11
apps/dokploy/drizzle/0169_parched_johnny_storm.sql
Normal file
11
apps/dokploy/drizzle/0169_parched_johnny_storm.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE "schedule" DROP CONSTRAINT "schedule_userId_user_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "schedule" ADD COLUMN "organizationId" text;--> statement-breakpoint
|
||||
ALTER TABLE "schedule" ADD CONSTRAINT "schedule_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
UPDATE "schedule" s
|
||||
SET "organizationId" = m."organization_id"
|
||||
FROM "member" m
|
||||
WHERE s."scheduleType" = 'dokploy-server'
|
||||
AND s."userId" = m."user_id"
|
||||
AND m."role" = 'owner';--> statement-breakpoint
|
||||
ALTER TABLE "schedule" DROP COLUMN "userId";
|
||||
8325
apps/dokploy/drizzle/meta/0167_snapshot.json
Normal file
8325
apps/dokploy/drizzle/meta/0167_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
8332
apps/dokploy/drizzle/meta/0168_snapshot.json
Normal file
8332
apps/dokploy/drizzle/meta/0168_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
8332
apps/dokploy/drizzle/meta/0169_snapshot.json
Normal file
8332
apps/dokploy/drizzle/meta/0169_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1170,6 +1170,27 @@
|
||||
"when": 1778303519111,
|
||||
"tag": "0166_nosy_slapstick",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 167,
|
||||
"version": "7",
|
||||
"when": 1780122576214,
|
||||
"tag": "0167_fresh_goliath",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 168,
|
||||
"version": "7",
|
||||
"when": 1780122833339,
|
||||
"tag": "0168_long_justice",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 169,
|
||||
"version": "7",
|
||||
"when": 1780127552074,
|
||||
"tag": "0169_parched_johnny_storm",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.29.4",
|
||||
"version": "v0.29.7",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -123,7 +123,7 @@
|
||||
"lucide-react": "^0.469.0",
|
||||
"micromatch": "4.0.8",
|
||||
"nanoid": "3.3.11",
|
||||
"next": "^16.2.0",
|
||||
"next": "16.2.6",
|
||||
"next-themes": "^0.2.1",
|
||||
"nextjs-toploader": "^3.9.17",
|
||||
"node-os-utils": "2.0.1",
|
||||
|
||||
@@ -32,6 +32,7 @@ import { AddAiAssistant } from "@/components/dashboard/project/add-ai-assistant"
|
||||
import { AddApplication } from "@/components/dashboard/project/add-application";
|
||||
import { AddCompose } from "@/components/dashboard/project/add-compose";
|
||||
import { AddDatabase } from "@/components/dashboard/project/add-database";
|
||||
import { AddImport } from "@/components/dashboard/project/add-import";
|
||||
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";
|
||||
@@ -1091,6 +1092,10 @@ const EnvironmentPage = (
|
||||
projectName={projectData?.name}
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
<AddImport
|
||||
projectName={projectData?.name}
|
||||
environmentId={environmentId}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
|
||||
@@ -93,6 +93,7 @@ const Service = (
|
||||
);
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
@@ -147,8 +148,9 @@ const Service = (
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (data?.server?.ipAddress) {
|
||||
copy(data.server.ipAddress);
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -85,6 +85,7 @@ const Service = (
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -134,8 +135,9 @@ const Service = (
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (data?.server?.ipAddress) {
|
||||
copy(data.server.ipAddress);
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
@@ -61,6 +63,7 @@ const Libsql = (
|
||||
const { data: auth } = api.user.get.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
|
||||
return (
|
||||
<div className="pb-10">
|
||||
@@ -99,6 +102,14 @@ const Libsql = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
@@ -63,6 +65,7 @@ const Mariadb = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
@@ -111,6 +114,14 @@ const Mariadb = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
@@ -63,6 +65,7 @@ const Mongo = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -110,6 +113,14 @@ const Mongo = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
@@ -62,6 +64,7 @@ const MySql = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -110,6 +113,14 @@ const MySql = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
@@ -62,6 +64,7 @@ const Postgresql = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -109,6 +112,14 @@ const Postgresql = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { HelpCircle, ServerOff } from "lucide-react";
|
||||
import type {
|
||||
GetServerSidePropsContext,
|
||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { type ReactElement, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import superjson from "superjson";
|
||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||
@@ -62,6 +64,7 @@ const Redis = (
|
||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||
|
||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||
projectId: data?.environment?.projectId || "",
|
||||
});
|
||||
@@ -109,6 +112,14 @@ const Redis = (
|
||||
<div className="flex flex-col h-fit w-fit gap-2">
|
||||
<div className="flex flex-row h-fit w-fit gap-2">
|
||||
<Badge
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
const ip = data?.server?.ipAddress || serverIp;
|
||||
if (ip) {
|
||||
copy(ip);
|
||||
toast.success("IP Address Copied!");
|
||||
}
|
||||
}}
|
||||
variant={
|
||||
!data?.serverId
|
||||
? "default"
|
||||
|
||||
@@ -5,18 +5,13 @@ import type { ReactElement } from "react";
|
||||
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
function SchedulesPage() {
|
||||
const { data: user } = api.user.get.useQuery();
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-8xl mx-auto min-h-[45vh]">
|
||||
<div className="rounded-xl bg-background shadow-md h-full">
|
||||
<ShowSchedules
|
||||
scheduleType="dokploy-server"
|
||||
id={user?.user.id || ""}
|
||||
/>
|
||||
<ShowSchedules scheduleType="dokploy-server" id="dokploy-server" />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import type { ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
import { ToggleEnforceSSO } from "@/components/dashboard/settings/servers/actions/toggle-enforce-sso";
|
||||
import { ToggleRemoteServersOnly } from "@/components/dashboard/settings/servers/actions/toggle-remote-servers-only";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-feature-gate";
|
||||
import { SSOSettings } from "@/components/proprietary/sso/sso-settings";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
|
||||
const Page = () => {
|
||||
interface Props {
|
||||
isCloud: boolean;
|
||||
}
|
||||
|
||||
const Page = ({ isCloud }: Props) => {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||
@@ -29,6 +41,33 @@ const Page = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{!isCloud && (
|
||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl mx-auto w-full">
|
||||
<div className="rounded-xl bg-background shadow-md">
|
||||
<EnterpriseFeatureGate
|
||||
lockedProps={{
|
||||
title: "Self-hosted Restrictions",
|
||||
description:
|
||||
"Deployment and authentication restrictions are part of Dokploy Enterprise. Add a valid license to configure them.",
|
||||
ctaLabel: "Go to License",
|
||||
}}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">
|
||||
Self-hosted Restrictions
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Control deployment targets and authentication behavior.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<ToggleRemoteServersOnly />
|
||||
<ToggleEnforceSSO />
|
||||
</CardContent>
|
||||
</EnterpriseFeatureGate>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -76,6 +115,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
isCloud: IS_CLOUD,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { IS_CLOUD, isAdminPresent } from "@dokploy/server";
|
||||
import {
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
isAdminPresent,
|
||||
} from "@dokploy/server";
|
||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
import { REGEXP_ONLY_DIGITS } from "input-otp";
|
||||
@@ -52,8 +56,9 @@ type LoginForm = z.infer<typeof LoginSchema>;
|
||||
|
||||
interface Props {
|
||||
IS_CLOUD: boolean;
|
||||
enforceSSO: boolean;
|
||||
}
|
||||
export default function Home({ IS_CLOUD }: Props) {
|
||||
export default function Home({ IS_CLOUD, enforceSSO }: Props) {
|
||||
const router = useRouter();
|
||||
const { config: whitelabeling } = useWhitelabelingPublic();
|
||||
const { data: showSignInWithSSO } = api.sso.showSignInWithSSO.useQuery();
|
||||
@@ -247,7 +252,9 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
<CardContent className="p-0">
|
||||
{!isTwoFactor ? (
|
||||
<>
|
||||
{showSignInWithSSO ? (
|
||||
{enforceSSO ? (
|
||||
<SignInWithSSO enforce />
|
||||
) : showSignInWithSSO ? (
|
||||
<SignInWithSSO>{loginContent}</SignInWithSSO>
|
||||
) : (
|
||||
loginContent
|
||||
@@ -417,6 +424,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
props: {
|
||||
IS_CLOUD: IS_CLOUD,
|
||||
enforceSSO: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -442,9 +450,12 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
};
|
||||
}
|
||||
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
|
||||
return {
|
||||
props: {
|
||||
hasAdmin,
|
||||
enforceSSO: webServerSettings?.enforceSSO ?? false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ async function main() {
|
||||
|
||||
if (records.length === 0) {
|
||||
console.log("✅ No 2FA records found, nothing to migrate.");
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`📦 Found ${records.length} 2FA record(s) to migrate.`);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getAccessibleServerIds,
|
||||
getApplicationStats,
|
||||
getContainerLogs,
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
mechanizeDockerContainer,
|
||||
readConfig,
|
||||
@@ -87,7 +88,11 @@ export const applicationRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create an application",
|
||||
|
||||
@@ -91,7 +91,11 @@ export const composeRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a compose",
|
||||
@@ -585,7 +589,11 @@ export const composeRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, environment.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a compose",
|
||||
@@ -862,6 +870,76 @@ export const composeRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
previewTemplate: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
base64: z.string(),
|
||||
appName: z.string(),
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
if (input.serverId) {
|
||||
const accessibleIds = await getAccessibleServerIds(ctx.session);
|
||||
if (!accessibleIds.has(input.serverId)) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to access this server",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const decodedData = Buffer.from(input.base64, "base64").toString(
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
let serverIp = "127.0.0.1";
|
||||
|
||||
if (input.serverId) {
|
||||
const server = await findServerById(input.serverId);
|
||||
serverIp = server.ipAddress;
|
||||
} else if (process.env.NODE_ENV !== "development") {
|
||||
const settings = await getWebServerSettings();
|
||||
serverIp = settings?.serverIp || "127.0.0.1";
|
||||
}
|
||||
|
||||
const templateData = JSON.parse(decodedData);
|
||||
const config = parse(templateData.config) as CompleteTemplate;
|
||||
|
||||
if (!templateData.compose || !config) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message:
|
||||
"Invalid template format. Must contain compose and config fields",
|
||||
});
|
||||
}
|
||||
|
||||
const configModified = {
|
||||
...config,
|
||||
variables: {
|
||||
APP_NAME: input.appName,
|
||||
...config.variables,
|
||||
},
|
||||
};
|
||||
|
||||
const processedTemplate = processTemplate(configModified, {
|
||||
serverIp,
|
||||
projectName: input.appName,
|
||||
});
|
||||
|
||||
return {
|
||||
compose: templateData.compose,
|
||||
template: processedTemplate,
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `Error processing template: ${error instanceof Error ? error.message : error}`,
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
import: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
|
||||
@@ -151,6 +151,14 @@ export const deploymentRouter = createTRPCRouter({
|
||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||
deployment: ["cancel"],
|
||||
});
|
||||
} else if (deployment.schedule?.serverId) {
|
||||
const targetServer = await findServerById(deployment.schedule.serverId);
|
||||
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You don't have access to this deployment.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!deployment.pid) {
|
||||
@@ -188,6 +196,14 @@ export const deploymentRouter = createTRPCRouter({
|
||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||
deployment: ["cancel"],
|
||||
});
|
||||
} else if (deployment.schedule?.serverId) {
|
||||
const targetServer = await findServerById(deployment.schedule.serverId);
|
||||
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You don't have access to this deployment.",
|
||||
});
|
||||
}
|
||||
}
|
||||
const result = await removeDeployment(input.deploymentId);
|
||||
await audit(ctx, {
|
||||
@@ -197,4 +213,47 @@ export const deploymentRouter = createTRPCRouter({
|
||||
});
|
||||
return result;
|
||||
}),
|
||||
|
||||
readLogs: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
deploymentId: z.string().min(1),
|
||||
tail: z.number().int().min(1).max(10000).default(100),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const deployment = await findDeploymentById(input.deploymentId);
|
||||
const serviceId = deployment.applicationId || deployment.composeId;
|
||||
if (serviceId) {
|
||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||
deployment: ["read"],
|
||||
});
|
||||
} else if (deployment.schedule?.serverId) {
|
||||
const targetServer = await findServerById(deployment.schedule.serverId);
|
||||
if (targetServer.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You don't have access to this deployment.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!deployment.logPath) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const command = `tail -n ${input.tail} "${deployment.logPath}" 2>/dev/null || echo ""`;
|
||||
const serverId = deployment.serverId || deployment.schedule?.serverId;
|
||||
if (serverId) {
|
||||
const { stdout } = await execAsyncRemote(serverId, command);
|
||||
return stdout;
|
||||
}
|
||||
|
||||
if (IS_CLOUD) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { stdout } = await execAsync(command);
|
||||
return stdout;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
findProjectById,
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
removeLibsqlById,
|
||||
@@ -51,7 +52,11 @@ export const libsqlRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a Libsql",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getServiceContainerCommand,
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
removeMariadbById,
|
||||
@@ -62,7 +63,11 @@ export const mariadbRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a Mariadb",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getServiceContainerCommand,
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
removeMongoById,
|
||||
@@ -61,7 +62,11 @@ export const mongoRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a mongo",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getServiceContainerCommand,
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
removeMySqlById,
|
||||
@@ -62,7 +63,11 @@ export const mysqlRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a MySQL",
|
||||
|
||||
@@ -295,6 +295,14 @@ export const organizationRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
// Owner role is non-delegable — no one can invite as owner
|
||||
if (input.role === "owner") {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Cannot invite a user with the owner role",
|
||||
});
|
||||
}
|
||||
|
||||
// If assigning a custom role, verify it exists
|
||||
if (!["owner", "admin", "member"].includes(input.role)) {
|
||||
const customRole = await db.query.organizationRole.findFirst({
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
getContainerLogs,
|
||||
getMountPath,
|
||||
getServiceContainerCommand,
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
removePostgresById,
|
||||
@@ -63,7 +64,11 @@ export const postgresRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a Postgres",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
requestToHeaders,
|
||||
} from "@dokploy/server/index";
|
||||
import { auth } from "@dokploy/server/lib/auth";
|
||||
import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { and, asc, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
@@ -43,6 +44,13 @@ export const ssoRouter = createTRPCRouter({
|
||||
owner.user.enableEnterpriseFeatures && owner.user.isValidEnterpriseLicense
|
||||
);
|
||||
}),
|
||||
enforceSSO: publicProcedure.query(async () => {
|
||||
if (IS_CLOUD) {
|
||||
return false;
|
||||
}
|
||||
const settings = await getWebServerSettings();
|
||||
return settings?.enforceSSO ?? false;
|
||||
}),
|
||||
listProviders: enterpriseProcedure.query(async ({ ctx }) => {
|
||||
const providers = await db.query.ssoProvider.findMany({
|
||||
where: and(
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getAccessibleServerIds,
|
||||
getContainerLogs,
|
||||
getServiceContainerCommand,
|
||||
getWebServerSettings,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
removeRedisById,
|
||||
@@ -59,7 +60,11 @@ export const redisRouter = createTRPCRouter({
|
||||
|
||||
await checkServiceAccess(ctx, project.projectId, "create");
|
||||
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
const webServerSettings = await getWebServerSettings();
|
||||
if (
|
||||
(IS_CLOUD || webServerSettings?.remoteServersOnly) &&
|
||||
!input.serverId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You need to use a server to create a Redis",
|
||||
|
||||
@@ -75,7 +75,12 @@ export const scheduleRouter = createTRPCRouter({
|
||||
}
|
||||
}
|
||||
}
|
||||
const newSchedule = await createSchedule(input);
|
||||
const newSchedule = await createSchedule({
|
||||
...input,
|
||||
...(input.scheduleType === "dokploy-server" && {
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
}),
|
||||
});
|
||||
|
||||
if (newSchedule?.enabled) {
|
||||
if (IS_CLOUD) {
|
||||
@@ -162,17 +167,6 @@ export const scheduleRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
existingSchedule.scheduleType === "dokploy-server" &&
|
||||
existingSchedule.userId &&
|
||||
existingSchedule.userId !== ctx.user.id
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You can only manage your own host-level schedules.",
|
||||
});
|
||||
}
|
||||
}
|
||||
const updatedSchedule = await updateSchedule(input);
|
||||
|
||||
@@ -256,17 +250,6 @@ export const scheduleRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
scheduleItem.scheduleType === "dokploy-server" &&
|
||||
scheduleItem.userId &&
|
||||
scheduleItem.userId !== ctx.user.id
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You can only manage your own host-level schedules.",
|
||||
});
|
||||
}
|
||||
}
|
||||
await deleteSchedule(input.scheduleId);
|
||||
|
||||
@@ -323,21 +306,27 @@ export const scheduleRouter = createTRPCRouter({
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
input.scheduleType === "dokploy-server" &&
|
||||
input.id !== ctx.user.id
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You can only list your own host-level schedules.",
|
||||
});
|
||||
if (input.scheduleType === "dokploy-server") {
|
||||
const member = await findMemberByUserId(
|
||||
ctx.user.id,
|
||||
ctx.session.activeOrganizationId,
|
||||
);
|
||||
if (member.role !== "owner" && member.role !== "admin") {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Only owners and admins can list host-level schedules.",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const where = {
|
||||
application: eq(schedules.applicationId, input.id),
|
||||
compose: eq(schedules.composeId, input.id),
|
||||
server: eq(schedules.serverId, input.id),
|
||||
"dokploy-server": eq(schedules.userId, input.id),
|
||||
"dokploy-server": eq(
|
||||
schedules.organizationId,
|
||||
ctx.session.activeOrganizationId,
|
||||
),
|
||||
};
|
||||
return db.query.schedules.findMany({
|
||||
where: where[input.scheduleType],
|
||||
@@ -376,17 +365,6 @@ export const scheduleRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
schedule.scheduleType === "dokploy-server" &&
|
||||
schedule.userId &&
|
||||
schedule.userId !== ctx.user.id
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You don't have access to this schedule.",
|
||||
});
|
||||
}
|
||||
}
|
||||
return schedule;
|
||||
}),
|
||||
@@ -439,17 +417,6 @@ export const scheduleRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
scheduleItem.scheduleType === "dokploy-server" &&
|
||||
scheduleItem.userId &&
|
||||
scheduleItem.userId !== ctx.user.id
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You can only manage your own host-level schedules.",
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
await runCommand(input.scheduleId);
|
||||
|
||||
@@ -45,7 +45,7 @@ export const securityRouter = createTRPCRouter({
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const security = await findSecurityById(input.securityId);
|
||||
await checkServicePermissionAndAccess(ctx, security.applicationId, {
|
||||
service: ["delete"],
|
||||
service: ["create"],
|
||||
});
|
||||
const result = await deleteSecurityById(input.securityId);
|
||||
await audit(ctx, {
|
||||
|
||||
@@ -77,6 +77,7 @@ import { appRouter } from "../root";
|
||||
import {
|
||||
adminProcedure,
|
||||
createTRPCRouter,
|
||||
enterpriseProcedure,
|
||||
protectedProcedure,
|
||||
publicProcedure,
|
||||
} from "../trpc";
|
||||
@@ -445,6 +446,50 @@ export const settingsRouter = createTRPCRouter({
|
||||
return true;
|
||||
}),
|
||||
|
||||
updateRemoteServersOnly: enterpriseProcedure
|
||||
.input(z.object({ remoteServersOnly: z.boolean() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
if (IS_CLOUD) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "This feature is only available for self-hosted instances",
|
||||
});
|
||||
}
|
||||
|
||||
await updateWebServerSettings({
|
||||
remoteServersOnly: input.remoteServersOnly,
|
||||
});
|
||||
|
||||
await audit(ctx, {
|
||||
action: "update",
|
||||
resourceType: "settings",
|
||||
resourceName: "remote-servers-only",
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
|
||||
updateEnforceSSO: enterpriseProcedure
|
||||
.input(z.object({ enforceSSO: z.boolean() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
if (IS_CLOUD) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "This feature is only available for self-hosted instances",
|
||||
});
|
||||
}
|
||||
|
||||
await updateWebServerSettings({
|
||||
enforceSSO: input.enforceSSO,
|
||||
});
|
||||
|
||||
await audit(ctx, {
|
||||
action: "update",
|
||||
resourceType: "settings",
|
||||
resourceName: "enforce-sso",
|
||||
});
|
||||
return true;
|
||||
}),
|
||||
|
||||
readTraefikConfig: adminProcedure.query(() => {
|
||||
if (IS_CLOUD) {
|
||||
return true;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
apiUpdateUser,
|
||||
invitation,
|
||||
member,
|
||||
session,
|
||||
user,
|
||||
} from "@dokploy/server/db/schema";
|
||||
import {
|
||||
@@ -32,7 +33,7 @@ import {
|
||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import { and, asc, eq, gt } from "drizzle-orm";
|
||||
import { and, asc, eq, gt, ne } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { audit } from "@/server/api/utils/audit";
|
||||
import {
|
||||
@@ -229,6 +230,15 @@ export const userRouter = createTRPCRouter({
|
||||
password: bcrypt.hashSync(input.password, 10),
|
||||
})
|
||||
.where(eq(account.userId, ctx.user.id));
|
||||
|
||||
await db
|
||||
.delete(session)
|
||||
.where(
|
||||
and(
|
||||
eq(session.userId, ctx.user.id),
|
||||
ne(session.id, ctx.session.id),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -594,6 +604,13 @@ export const userRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
|
||||
if (input.role === "owner") {
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "Cannot create a user with the owner role",
|
||||
});
|
||||
}
|
||||
|
||||
return await createOrganizationUserWithCredentials({
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
email: input.email,
|
||||
|
||||
@@ -44,6 +44,13 @@ export const registryRelations = relations(registry, ({ many }) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
// Image references require a lowercase namespace (e.g. Docker Hub username).
|
||||
const registryUsernameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.transform((s) => s.toLowerCase());
|
||||
|
||||
// Registry URLs must be hostname[:port] only — no shell metacharacters
|
||||
// Empty string is allowed (means default/Docker Hub registry)
|
||||
const registryUrlSchema = z
|
||||
@@ -57,7 +64,7 @@ const registryUrlSchema = z
|
||||
|
||||
const createSchema = createInsertSchema(registry, {
|
||||
registryName: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
username: registryUsernameSchema,
|
||||
password: z.string().min(1),
|
||||
registryUrl: registryUrlSchema,
|
||||
organizationId: z.string().min(1),
|
||||
@@ -70,7 +77,7 @@ export const apiCreateRegistry = createSchema
|
||||
.pick({})
|
||||
.extend({
|
||||
registryName: z.string().min(1),
|
||||
username: z.string().min(1),
|
||||
username: registryUsernameSchema,
|
||||
password: z.string().min(1),
|
||||
registryUrl: registryUrlSchema,
|
||||
registryType: z.enum(["cloud"]),
|
||||
@@ -83,7 +90,7 @@ export const apiCreateRegistry = createSchema
|
||||
|
||||
export const apiTestRegistry = createSchema.pick({}).extend({
|
||||
registryName: z.string().optional(),
|
||||
username: z.string().min(1),
|
||||
username: registryUsernameSchema,
|
||||
password: z.string().min(1),
|
||||
registryUrl: registryUrlSchema,
|
||||
registryType: z.enum(["cloud"]),
|
||||
|
||||
@@ -3,11 +3,11 @@ import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { organization } from "./account";
|
||||
import { applications } from "./application";
|
||||
import { compose } from "./compose";
|
||||
import { deployments } from "./deployment";
|
||||
import { server } from "./server";
|
||||
import { user } from "./user";
|
||||
import { generateAppName } from "./utils";
|
||||
export const shellTypes = pgEnum("shellType", ["bash", "sh"]);
|
||||
|
||||
@@ -46,7 +46,7 @@ export const schedules = pgTable("schedule", {
|
||||
serverId: text("serverId").references(() => server.serverId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
userId: text("userId").references(() => user.id, {
|
||||
organizationId: text("organizationId").references(() => organization.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
@@ -71,9 +71,9 @@ export const schedulesRelations = relations(schedules, ({ one, many }) => ({
|
||||
fields: [schedules.serverId],
|
||||
references: [server.serverId],
|
||||
}),
|
||||
user: one(user, {
|
||||
fields: [schedules.userId],
|
||||
references: [user.id],
|
||||
organization: one(organization, {
|
||||
fields: [schedules.organizationId],
|
||||
references: [organization.id],
|
||||
}),
|
||||
deployments: many(deployments),
|
||||
}));
|
||||
|
||||
@@ -96,6 +96,10 @@ export const webServerSettings = pgTable("webServerSettings", {
|
||||
metaTitle: null,
|
||||
footerText: null,
|
||||
}),
|
||||
// Deployment Configuration (self-hosted only)
|
||||
remoteServersOnly: boolean("remoteServersOnly").notNull().default(false),
|
||||
// Auth Configuration (self-hosted only)
|
||||
enforceSSO: boolean("enforceSSO").notNull().default(false),
|
||||
// Cache Cleanup Configuration
|
||||
cleanupCacheApplications: boolean("cleanupCacheApplications")
|
||||
.notNull()
|
||||
@@ -155,6 +159,8 @@ export const apiUpdateWebServerSettings = createSchema.partial().extend({
|
||||
cleanupCacheApplications: z.boolean().optional(),
|
||||
cleanupCacheOnPreviews: z.boolean().optional(),
|
||||
cleanupCacheOnCompose: z.boolean().optional(),
|
||||
remoteServersOnly: z.boolean().optional(),
|
||||
enforceSSO: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const apiAssignDomain = z
|
||||
|
||||
@@ -100,6 +100,7 @@ export * from "./utils/docker/types";
|
||||
export * from "./utils/docker/utils";
|
||||
export * from "./utils/filesystem/directory";
|
||||
export * from "./utils/filesystem/ssh";
|
||||
export * from "./utils/git-branch-validation";
|
||||
export * from "./utils/gpu-setup";
|
||||
export * from "./utils/notifications/build-error";
|
||||
export * from "./utils/notifications/build-success";
|
||||
@@ -108,7 +109,6 @@ export * from "./utils/notifications/docker-cleanup";
|
||||
export * from "./utils/notifications/dokploy-restart";
|
||||
export * from "./utils/notifications/server-threshold";
|
||||
export * from "./utils/notifications/utils";
|
||||
export * from "./utils/git-branch-validation";
|
||||
export * from "./utils/process/execAsync";
|
||||
export * from "./utils/process/spawnAsync";
|
||||
export * from "./utils/providers/bitbucket";
|
||||
|
||||
@@ -80,9 +80,10 @@ export const checkPermission = async (
|
||||
const { id: userId } = ctx.user;
|
||||
const { activeOrganizationId: organizationId } = ctx.session;
|
||||
const memberRecord = await findMemberByUserId(userId, organizationId);
|
||||
const isStaticRole = memberRecord.role in staticRoles;
|
||||
|
||||
if (isStaticRole) {
|
||||
const isPrivilegedStaticRole =
|
||||
memberRecord.role === "owner" || memberRecord.role === "admin";
|
||||
if (isPrivilegedStaticRole) {
|
||||
const allEnterprise = Object.keys(permissions).every((r) =>
|
||||
enterpriseOnlyResources.has(r),
|
||||
);
|
||||
@@ -164,6 +165,8 @@ const getLegacyOverrides = (
|
||||
},
|
||||
sshKeys: {
|
||||
read: !!memberRecord.canAccessToSSHKeys,
|
||||
create: !!memberRecord.canAccessToSSHKeys,
|
||||
delete: !!memberRecord.canAccessToSSHKeys,
|
||||
},
|
||||
gitProviders: {
|
||||
read: !!memberRecord.canAccessToGitProviders,
|
||||
|
||||
@@ -85,6 +85,9 @@ export const findScheduleOrganizationId = async (scheduleId: string) => {
|
||||
if (schedule?.server) {
|
||||
return schedule?.server?.organization?.id;
|
||||
}
|
||||
if (schedule?.organizationId) {
|
||||
return schedule.organizationId;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ Compose Type: ${composeType} ✅`;
|
||||
cd "${projectPath}";
|
||||
|
||||
${compose.isolatedDeployment ? `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create ${compose.composeType === "stack" ? "--driver overlay" : ""} --attachable ${compose.appName}` : ""}
|
||||
env -i PATH="$PATH" ${exportEnvCommand} docker ${command.split(" ").join(" ")} 2>&1 || { echo "Error: ❌ Docker command failed"; exit 1; }
|
||||
env -i PATH="$PATH" HOME="$HOME" ${exportEnvCommand} docker ${command.split(" ").join(" ")} 2>&1 || { echo "Error: ❌ Docker command failed"; exit 1; }
|
||||
${compose.isolatedDeployment ? `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1` : ""}
|
||||
|
||||
echo "Docker Compose Deployed: ✅";
|
||||
|
||||
@@ -101,8 +101,8 @@ export const getRegistryTag = (registry: Registry, imageName: string) => {
|
||||
// Extract the repository name (last part after '/')
|
||||
const repositoryName = extractRepositoryName(imageName);
|
||||
|
||||
// Build the final tag using registry's username/prefix
|
||||
const targetPrefix = imagePrefix || username;
|
||||
// Build the final tag using registry's username/prefix (must be lowercase for valid image refs)
|
||||
const targetPrefix = (imagePrefix || username).toLowerCase();
|
||||
const finalRegistry = registryUrl || "";
|
||||
|
||||
return finalRegistry
|
||||
|
||||
@@ -337,6 +337,10 @@ export const createDomainLabels = (
|
||||
labels.push(
|
||||
`traefik.http.routers.${routerName}.tls.certresolver=${customCertResolver}`,
|
||||
);
|
||||
} else if (certificateType === "none" && https) {
|
||||
// No cert resolver, but HTTPS is enabled (default/custom certificate):
|
||||
// explicitly enable TLS so Traefik serves the router over HTTPS.
|
||||
labels.push(`traefik.http.routers.${routerName}.tls=true`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const initSchedules = async () => {
|
||||
server: true,
|
||||
application: true,
|
||||
compose: true,
|
||||
user: true,
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@ import micromatch from "micromatch";
|
||||
|
||||
export const shouldDeploy = (
|
||||
watchPaths: string[] | null,
|
||||
modifiedFiles: string[],
|
||||
modifiedFiles: (string | null | undefined)[] | null | undefined,
|
||||
): boolean => {
|
||||
if (!watchPaths || watchPaths?.length === 0) return true;
|
||||
return micromatch.some(modifiedFiles, watchPaths);
|
||||
const files = (modifiedFiles ?? []).filter(
|
||||
(file): file is string => typeof file === "string",
|
||||
);
|
||||
return micromatch.some(files, watchPaths);
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ export const readValidDirectory = (
|
||||
directory: string,
|
||||
serverId?: string | null,
|
||||
) => {
|
||||
if (!/^[\w/. :-]{1,500}$/.test(directory)) {
|
||||
if (!/^[\w/. :[\]-]{1,500}$/.test(directory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
219
pnpm-lock.yaml
generated
219
pnpm-lock.yaml
generated
@@ -51,7 +51,7 @@ importers:
|
||||
version: 4.12.2
|
||||
inngest:
|
||||
specifier: 3.40.1
|
||||
version: 3.40.1(encoding@0.1.13)(h3@1.15.1)(hono@4.12.2)(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3)
|
||||
version: 3.40.1(encoding@0.1.13)(h3@1.15.1)(hono@4.12.2)(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3)
|
||||
pino:
|
||||
specifier: 9.4.0
|
||||
version: 9.4.0
|
||||
@@ -115,10 +115,10 @@ importers:
|
||||
version: 2.0.30(zod@4.3.6)
|
||||
'@better-auth/api-key':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(4c76b7da14d170b1922a58fb44839507))
|
||||
'@better-auth/sso':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))(better-call@2.0.2(zod@4.3.6))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(4c76b7da14d170b1922a58fb44839507))(better-call@2.0.2(zod@4.3.6))
|
||||
'@codemirror/autocomplete':
|
||||
specifier: ^6.18.6
|
||||
version: 6.20.0
|
||||
@@ -244,7 +244,7 @@ importers:
|
||||
version: 11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/next':
|
||||
specifier: ^11.10.0
|
||||
version: 11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(react@18.2.0)(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
version: 11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(react@18.2.0)(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
'@trpc/react-query':
|
||||
specifier: ^11.10.0
|
||||
version: 11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(react@18.2.0)(typescript@5.9.3)
|
||||
@@ -280,7 +280,7 @@ importers:
|
||||
version: 5.1.1(encoding@0.1.13)
|
||||
better-auth:
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(71a760b327c31dd12606432855d01199)
|
||||
version: 1.5.4(4c76b7da14d170b1922a58fb44839507)
|
||||
bl:
|
||||
specifier: 6.0.11
|
||||
version: 6.0.11
|
||||
@@ -342,14 +342,14 @@ importers:
|
||||
specifier: 3.3.11
|
||||
version: 3.3.11
|
||||
next:
|
||||
specifier: ^16.2.0
|
||||
version: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
specifier: 16.2.6
|
||||
version: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next-themes:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.1(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
version: 0.2.1(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
nextjs-toploader:
|
||||
specifier: ^3.9.17
|
||||
version: 3.9.17(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
version: 3.9.17(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
node-os-utils:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1
|
||||
@@ -630,10 +630,10 @@ importers:
|
||||
version: 2.0.30(zod@4.3.6)
|
||||
'@better-auth/api-key':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(9312701f7238452a4ac943b2bb39c424))
|
||||
'@better-auth/sso':
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))(better-call@2.0.2(zod@4.3.6))
|
||||
version: 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(9312701f7238452a4ac943b2bb39c424))(better-call@2.0.2(zod@4.3.6))
|
||||
'@better-auth/utils':
|
||||
specifier: 0.3.1
|
||||
version: 0.3.1
|
||||
@@ -672,7 +672,7 @@ importers:
|
||||
version: 5.1.1(encoding@0.1.13)
|
||||
better-auth:
|
||||
specifier: 1.5.4
|
||||
version: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
||||
version: 1.5.4(9312701f7238452a4ac943b2bb39c424)
|
||||
better-call:
|
||||
specifier: 2.0.2
|
||||
version: 2.0.2(zod@4.3.6)
|
||||
@@ -775,7 +775,7 @@ importers:
|
||||
devDependencies:
|
||||
'@better-auth/cli':
|
||||
specifier: 1.4.21
|
||||
version: 1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0(socks@2.8.8))(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1))
|
||||
version: 1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0(socks@2.8.8))(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1))
|
||||
'@types/adm-zip':
|
||||
specifier: ^0.5.7
|
||||
version: 0.5.7
|
||||
@@ -1878,53 +1878,53 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@next/env@16.2.0':
|
||||
resolution: {integrity: sha512-OZIbODWWAi0epQRCRjNe1VO45LOFBzgiyqmTLzIqWq6u1wrxKnAyz1HH6tgY/Mc81YzIjRPoYsPAEr4QV4l9TA==}
|
||||
'@next/env@16.2.6':
|
||||
resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==}
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.0':
|
||||
resolution: {integrity: sha512-/JZsqKzKt01IFoiLLAzlNqys7qk2F3JkcUhj50zuRhKDQkZNOz9E5N6wAQWprXdsvjRP4lTFj+/+36NSv5AwhQ==}
|
||||
'@next/swc-darwin-arm64@16.2.6':
|
||||
resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@16.2.0':
|
||||
resolution: {integrity: sha512-/hV8erWq4SNlVgglUiW5UmQ5Hwy5EW/AbbXlJCn6zkfKxTy/E/U3V8U1Ocm2YCTUoFgQdoMxRyRMOW5jYy4ygg==}
|
||||
'@next/swc-darwin-x64@16.2.6':
|
||||
resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.0':
|
||||
resolution: {integrity: sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==}
|
||||
'@next/swc-linux-arm64-gnu@16.2.6':
|
||||
resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.0':
|
||||
resolution: {integrity: sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==}
|
||||
'@next/swc-linux-arm64-musl@16.2.6':
|
||||
resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.0':
|
||||
resolution: {integrity: sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==}
|
||||
'@next/swc-linux-x64-gnu@16.2.6':
|
||||
resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.0':
|
||||
resolution: {integrity: sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==}
|
||||
'@next/swc-linux-x64-musl@16.2.6':
|
||||
resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.0':
|
||||
resolution: {integrity: sha512-UMiFNQf5H7+1ZsZPxEsA064WEuFbRNq/kEXyepbCnSErp4f5iut75dBA8UeerFIG3vDaQNOfCpevnERPp2V+nA==}
|
||||
'@next/swc-win32-arm64-msvc@16.2.6':
|
||||
resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.0':
|
||||
resolution: {integrity: sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA==}
|
||||
'@next/swc-win32-x64-msvc@16.2.6':
|
||||
resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -4171,6 +4171,7 @@ packages:
|
||||
|
||||
'@ungap/structured-clone@1.3.0':
|
||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
|
||||
|
||||
'@vercel/oidc@3.1.0':
|
||||
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
||||
@@ -6565,8 +6566,8 @@ packages:
|
||||
react: '*'
|
||||
react-dom: '*'
|
||||
|
||||
next@16.2.0:
|
||||
resolution: {integrity: sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==}
|
||||
next@16.2.6:
|
||||
resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==}
|
||||
engines: {node: '>=20.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -8664,21 +8665,21 @@ snapshots:
|
||||
|
||||
'@balena/dockerignore@1.0.2': {}
|
||||
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))':
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(4c76b7da14d170b1922a58fb44839507))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
better-auth: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
||||
better-auth: 1.5.4(4c76b7da14d170b1922a58fb44839507)
|
||||
zod: 4.3.6
|
||||
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))':
|
||||
'@better-auth/api-key@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(9312701f7238452a4ac943b2bb39c424))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
better-auth: 1.5.4(71a760b327c31dd12606432855d01199)
|
||||
better-auth: 1.5.4(9312701f7238452a4ac943b2bb39c424)
|
||||
zod: 4.3.6
|
||||
|
||||
'@better-auth/cli@1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0(socks@2.8.8))(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1))':
|
||||
'@better-auth/cli@1.4.21(@better-fetch/fetch@1.1.21)(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(better-call@2.0.2(zod@4.3.6))(drizzle-kit@0.31.9)(jose@6.1.3)(kysely@0.28.11)(mongodb@7.1.0(socks@2.8.8))(mysql2@3.15.3)(nanostores@1.1.1)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/preset-react': 7.28.5(@babel/core@7.29.0)
|
||||
@@ -8690,7 +8691,7 @@ snapshots:
|
||||
'@mrleebo/prisma-ast': 0.13.1
|
||||
'@prisma/client': 5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
'@types/pg': 8.16.0
|
||||
better-auth: 1.4.21(97e31320bc7dc8a33b04861de973b388)
|
||||
better-auth: 1.4.21(23a7835d44d42e8df1218333453e2b24)
|
||||
better-sqlite3: 12.6.2
|
||||
c12: 3.3.3
|
||||
chalk: 5.6.2
|
||||
@@ -8824,24 +8825,24 @@ snapshots:
|
||||
'@prisma/client': 5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
prisma: 7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9))(better-call@2.0.2(zod@4.3.6))':
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(4c76b7da14d170b1922a58fb44839507))(better-call@2.0.2(zod@4.3.6))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
better-auth: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
||||
better-auth: 1.5.4(4c76b7da14d170b1922a58fb44839507)
|
||||
better-call: 2.0.2(zod@4.3.6)
|
||||
fast-xml-parser: 5.5.1
|
||||
jose: 6.1.3
|
||||
samlify: 2.10.2
|
||||
zod: 4.3.6
|
||||
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(71a760b327c31dd12606432855d01199))(better-call@2.0.2(zod@4.3.6))':
|
||||
'@better-auth/sso@1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(better-auth@1.5.4(9312701f7238452a4ac943b2bb39c424))(better-call@2.0.2(zod@4.3.6))':
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/utils': 0.3.1
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
better-auth: 1.5.4(71a760b327c31dd12606432855d01199)
|
||||
better-auth: 1.5.4(9312701f7238452a4ac943b2bb39c424)
|
||||
better-call: 2.0.2(zod@4.3.6)
|
||||
fast-xml-parser: 5.5.1
|
||||
jose: 6.1.3
|
||||
@@ -9512,30 +9513,30 @@ snapshots:
|
||||
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
||||
optional: true
|
||||
|
||||
'@next/env@16.2.0': {}
|
||||
'@next/env@16.2.6': {}
|
||||
|
||||
'@next/swc-darwin-arm64@16.2.0':
|
||||
'@next/swc-darwin-arm64@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@16.2.0':
|
||||
'@next/swc-darwin-x64@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@16.2.0':
|
||||
'@next/swc-linux-arm64-gnu@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.2.0':
|
||||
'@next/swc-linux-arm64-musl@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.2.0':
|
||||
'@next/swc-linux-x64-gnu@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@16.2.0':
|
||||
'@next/swc-linux-x64-musl@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.2.0':
|
||||
'@next/swc-win32-arm64-msvc@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@16.2.0':
|
||||
'@next/swc-win32-x64-msvc@16.2.6':
|
||||
optional: true
|
||||
|
||||
'@noble/ciphers@2.1.1': {}
|
||||
@@ -12026,11 +12027,11 @@ snapshots:
|
||||
'@trpc/server': 11.10.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
'@trpc/next@11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(react@18.2.0)(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)':
|
||||
'@trpc/next@11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/react-query@11.10.0(@tanstack/react-query@5.90.21(react@18.2.0))(@trpc/client@11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(react@18.2.0)(typescript@5.9.3))(@trpc/server@11.10.0(typescript@5.9.3))(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@trpc/client': 11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@trpc/server': 11.10.0(typescript@5.9.3)
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
typescript: 5.9.3
|
||||
@@ -12534,7 +12535,7 @@ snapshots:
|
||||
|
||||
before-after-hook@2.2.3: {}
|
||||
|
||||
better-auth@1.4.21(97e31320bc7dc8a33b04861de973b388):
|
||||
better-auth@1.4.21(23a7835d44d42e8df1218333453e2b24):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.0)
|
||||
'@better-auth/telemetry': 1.4.21(@better-auth/core@1.4.21(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))
|
||||
@@ -12555,14 +12556,14 @@ snapshots:
|
||||
drizzle-orm: 0.41.0(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
mysql2: 3.15.3
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
pg: 8.18.0
|
||||
prisma: 7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1)
|
||||
|
||||
better-auth@1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9):
|
||||
better-auth@1.5.4(4c76b7da14d170b1922a58fb44839507):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/drizzle-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))
|
||||
@@ -12588,42 +12589,7 @@ snapshots:
|
||||
drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
mysql2: 3.15.3
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
pg: 8.18.0
|
||||
prisma: 7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- '@cloudflare/workers-types'
|
||||
|
||||
better-auth@1.5.4(71a760b327c31dd12606432855d01199):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/drizzle-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))
|
||||
'@better-auth/kysely-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)
|
||||
'@better-auth/memory-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)
|
||||
'@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0(socks@2.8.8))
|
||||
'@better-auth/prisma-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
'@better-auth/telemetry': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))
|
||||
'@better-auth/utils': 0.3.1
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
'@noble/ciphers': 2.1.1
|
||||
'@noble/hashes': 2.0.1
|
||||
better-call: 1.3.2(zod@4.3.6)
|
||||
defu: 6.1.4
|
||||
jose: 6.1.3
|
||||
kysely: 0.28.11
|
||||
nanostores: 1.1.1
|
||||
zod: 4.3.6
|
||||
optionalDependencies:
|
||||
'@prisma/client': 5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
better-sqlite3: 12.6.2
|
||||
drizzle-kit: 0.31.9
|
||||
drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
mysql2: 3.15.3
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
pg: 8.18.0
|
||||
prisma: 7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
react: 18.2.0
|
||||
@@ -12632,6 +12598,41 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@cloudflare/workers-types'
|
||||
|
||||
better-auth@1.5.4(9312701f7238452a4ac943b2bb39c424):
|
||||
dependencies:
|
||||
'@better-auth/core': 1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.3.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)
|
||||
'@better-auth/drizzle-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(drizzle-orm@0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))
|
||||
'@better-auth/kysely-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(kysely@0.28.11)
|
||||
'@better-auth/memory-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)
|
||||
'@better-auth/mongo-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(mongodb@7.1.0(socks@2.8.8))
|
||||
'@better-auth/prisma-adapter': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))(@better-auth/utils@0.3.1)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
'@better-auth/telemetry': 1.5.4(@better-auth/core@1.5.4(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@2.0.2(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))
|
||||
'@better-auth/utils': 0.3.1
|
||||
'@better-fetch/fetch': 1.1.21
|
||||
'@noble/ciphers': 2.1.1
|
||||
'@noble/hashes': 2.0.1
|
||||
better-call: 1.3.2(zod@4.3.6)
|
||||
defu: 6.1.4
|
||||
jose: 6.1.3
|
||||
kysely: 0.28.11
|
||||
nanostores: 1.1.1
|
||||
zod: 4.3.6
|
||||
optionalDependencies:
|
||||
'@prisma/client': 5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
better-sqlite3: 12.6.2
|
||||
drizzle-kit: 0.31.9
|
||||
drizzle-orm: 0.45.1(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.11)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.4)(prisma@7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3))
|
||||
mongodb: 7.1.0(socks@2.8.8)
|
||||
mysql2: 3.15.3
|
||||
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
pg: 8.18.0
|
||||
prisma: 7.4.1(@types/react@18.3.5)(better-sqlite3@12.6.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.9.3)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
vitest: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(jiti@2.6.1)(tsx@4.16.2)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
- '@cloudflare/workers-types'
|
||||
|
||||
better-call@1.1.8(zod@4.3.6):
|
||||
dependencies:
|
||||
'@better-auth/utils': 0.3.1
|
||||
@@ -13881,7 +13882,7 @@ snapshots:
|
||||
|
||||
inline-style-parser@0.2.7: {}
|
||||
|
||||
inngest@3.40.1(encoding@0.1.13)(h3@1.15.1)(hono@4.12.2)(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3):
|
||||
inngest@3.40.1(encoding@0.1.13)(h3@1.15.1)(hono@4.12.2)(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@bufbuild/protobuf': 2.11.0
|
||||
'@inngest/ai': 0.1.7
|
||||
@@ -13908,7 +13909,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
h3: 1.15.1
|
||||
hono: 4.12.2
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -14704,15 +14705,15 @@ snapshots:
|
||||
|
||||
neotraverse@0.6.18: {}
|
||||
|
||||
next-themes@0.2.1(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
next-themes@0.2.1(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
next@16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
'@next/env': 16.2.0
|
||||
'@next/env': 16.2.6
|
||||
'@swc/helpers': 0.5.15
|
||||
baseline-browser-mapping: 2.10.0
|
||||
caniuse-lite: 1.0.30001774
|
||||
@@ -14721,23 +14722,23 @@ snapshots:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
styled-jsx: 5.1.6(@babel/core@7.29.0)(react@18.2.0)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 16.2.0
|
||||
'@next/swc-darwin-x64': 16.2.0
|
||||
'@next/swc-linux-arm64-gnu': 16.2.0
|
||||
'@next/swc-linux-arm64-musl': 16.2.0
|
||||
'@next/swc-linux-x64-gnu': 16.2.0
|
||||
'@next/swc-linux-x64-musl': 16.2.0
|
||||
'@next/swc-win32-arm64-msvc': 16.2.0
|
||||
'@next/swc-win32-x64-msvc': 16.2.0
|
||||
'@next/swc-darwin-arm64': 16.2.6
|
||||
'@next/swc-darwin-x64': 16.2.6
|
||||
'@next/swc-linux-arm64-gnu': 16.2.6
|
||||
'@next/swc-linux-arm64-musl': 16.2.6
|
||||
'@next/swc-linux-x64-gnu': 16.2.6
|
||||
'@next/swc-linux-x64-musl': 16.2.6
|
||||
'@next/swc-win32-arm64-msvc': 16.2.6
|
||||
'@next/swc-win32-x64-msvc': 16.2.6
|
||||
'@opentelemetry/api': 1.9.0
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
nextjs-toploader@3.9.17(next@16.2.0(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
nextjs-toploader@3.9.17(next@16.2.6(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
next: 16.2.0(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
nprogress: 0.2.0
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
|
||||
Reference in New Issue
Block a user