mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-06 14:35:26 +02:00
Compare commits
75 Commits
| 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]
|
needs: [combine-manifests]
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.get_version.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -160,3 +162,80 @@ jobs:
|
|||||||
prerelease: false
|
prerelease: false
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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 () => {
|
it("should handle different ports correctly", async () => {
|
||||||
const customPortDomain = { ...baseDomain, port: 3000 };
|
const customPortDomain = { ...baseDomain, port: 3000 };
|
||||||
const labels = await createDomainLabels(appName, customPortDomain, "web");
|
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();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("static roles bypass enterprise resources", () => {
|
describe("owner and admin bypass enterprise resources", () => {
|
||||||
it("owner bypasses deployment.read", async () => {
|
it("owner bypasses deployment.read", async () => {
|
||||||
memberToReturn = mockMemberData("owner");
|
memberToReturn = mockMemberData("owner");
|
||||||
await expect(
|
await expect(
|
||||||
@@ -73,15 +73,8 @@ describe("static roles bypass enterprise resources", () => {
|
|||||||
).resolves.toBeUndefined();
|
).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("member bypasses schedule.delete", async () => {
|
it("owner bypasses multiple enterprise permissions at once", async () => {
|
||||||
memberToReturn = mockMemberData("member");
|
memberToReturn = mockMemberData("owner");
|
||||||
await expect(
|
|
||||||
checkPermission(ctx, { schedule: ["delete"] }),
|
|
||||||
).resolves.toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("member bypasses multiple enterprise permissions at once", async () => {
|
|
||||||
memberToReturn = mockMemberData("member");
|
|
||||||
await expect(
|
await expect(
|
||||||
checkPermission(ctx, {
|
checkPermission(ctx, {
|
||||||
deployment: ["read"],
|
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", () => {
|
describe("static roles validate free-tier resources", () => {
|
||||||
it("owner passes project.create", async () => {
|
it("owner passes project.create", async () => {
|
||||||
memberToReturn = mockMemberData("owner");
|
memberToReturn = mockMemberData("owner");
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ const baseSettings: WebServerSettings = {
|
|||||||
cleanupCacheApplications: false,
|
cleanupCacheApplications: false,
|
||||||
cleanupCacheOnCompose: false,
|
cleanupCacheOnCompose: false,
|
||||||
cleanupCacheOnPreviews: false,
|
cleanupCacheOnPreviews: false,
|
||||||
|
remoteServersOnly: false,
|
||||||
|
enforceSSO: false,
|
||||||
createdAt: null,
|
createdAt: null,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -78,4 +78,20 @@ describe("readValidDirectory (path traversal)", () => {
|
|||||||
it("returns false for empty string (resolves to cwd)", () => {
|
it("returns false for empty string (resolves to cwd)", () => {
|
||||||
expect(readValidDirectory("")).toBe(false);
|
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>
|
<FormLabel>Memory Limit</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip delayDuration={0}>
|
<Tooltip delayDuration={0}>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -263,7 +263,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
|||||||
<FormLabel>Memory Reservation</FormLabel>
|
<FormLabel>Memory Reservation</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip delayDuration={0}>
|
<Tooltip delayDuration={0}>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -303,7 +303,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
|||||||
<FormLabel>CPU Limit</FormLabel>
|
<FormLabel>CPU Limit</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip delayDuration={0}>
|
<Tooltip delayDuration={0}>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -343,7 +343,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
|||||||
<FormLabel>CPU Reservation</FormLabel>
|
<FormLabel>CPU Reservation</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip delayDuration={0}>
|
<Tooltip delayDuration={0}>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
@@ -379,7 +379,7 @@ export const ShowResources = ({ id, type }: Props) => {
|
|||||||
<FormLabel className="text-base">Ulimits</FormLabel>
|
<FormLabel className="text-base">Ulimits</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip delayDuration={0}>
|
<Tooltip delayDuration={0}>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
<InfoIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent className="max-w-xs">
|
<TooltipContent className="max-w-xs">
|
||||||
|
|||||||
@@ -806,7 +806,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
<FormLabel>Middlewares</FormLabel>
|
<FormLabel>Middlewares</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||||
?
|
?
|
||||||
</div>
|
</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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { HelpCircle, KeyRoundIcon, LockIcon, X } from "lucide-react";
|
import { HelpCircle, KeyRoundIcon, LockIcon, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -6,7 +7,6 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -5,7 +6,6 @@ import { useEffect, useMemo } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
import { BitbucketIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -422,7 +422,7 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
|
|||||||
<FormLabel>Watch Paths</FormLabel>
|
<FormLabel>Watch Paths</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||||
?
|
?
|
||||||
</div>
|
</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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { HelpCircle, KeyRoundIcon, LockIcon, X } from "lucide-react";
|
import { HelpCircle, KeyRoundIcon, LockIcon, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -6,7 +7,6 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { GitIcon } from "@/components/icons/data-tools-icons";
|
import { GitIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -5,7 +6,6 @@ import { useEffect } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
import { GiteaIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
|
|||||||
<FormLabel>Watch Paths</FormLabel>
|
<FormLabel>Watch Paths</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||||
?
|
?
|
||||||
</div>
|
</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 { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -5,7 +6,6 @@ import { useEffect, useMemo } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { VALID_BRANCH_REGEX } from "@dokploy/server/utils/git-branch-validation";
|
|
||||||
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
import { GitlabIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@@ -440,7 +440,7 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
|
|||||||
<FormLabel>Watch Paths</FormLabel>
|
<FormLabel>Watch Paths</FormLabel>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger type="button">
|
||||||
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
<div className="size-4 rounded-full bg-muted flex items-center justify-center text-[10px] font-bold">
|
||||||
?
|
?
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function AnalyzeLogs({ logs, context }: Props) {
|
|||||||
disabled={logs.length === 0}
|
disabled={logs.length === 0}
|
||||||
title="Analyze logs with AI"
|
title="Analyze logs with AI"
|
||||||
>
|
>
|
||||||
<Bot className="mr-2 h-4 w-4" />
|
<Bot className="mr-2 size-4" />
|
||||||
AI
|
AI
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|||||||
@@ -347,11 +347,13 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
title={isPaused ? "Resume logs" : "Pause logs"}
|
title={isPaused ? "Resume logs" : "Pause logs"}
|
||||||
>
|
>
|
||||||
{isPaused ? (
|
{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>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -362,11 +364,13 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
title="Copy logs to clipboard"
|
title="Copy logs to clipboard"
|
||||||
>
|
>
|
||||||
{copied ? (
|
{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>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -374,17 +378,18 @@ export const DockerLogsId: React.FC<Props> = ({
|
|||||||
className="h-9 sm:w-auto w-full"
|
className="h-9 sm:w-auto w-full"
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
disabled={filteredLogs.length === 0 || !data?.Name}
|
disabled={filteredLogs.length === 0 || !data?.Name}
|
||||||
|
title="Download logs as text file"
|
||||||
>
|
>
|
||||||
<DownloadIcon className="mr-2 h-4 w-4" />
|
<DownloadIcon className="size-4" />
|
||||||
Download logs
|
<span className="hidden lg:ml-2 lg:inline">Download logs</span>
|
||||||
</Button>
|
</Button>
|
||||||
<AnalyzeLogs logs={filteredLogs} context="runtime" />
|
<AnalyzeLogs logs={filteredLogs} context="runtime" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isPaused && (
|
{isPaused && (
|
||||||
<AlertBlock type="warning">
|
<AlertBlock type="warning" className="items-center">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Pause className="h-4 w-4" />
|
<Pause className="size-4" />
|
||||||
<span>
|
<span>
|
||||||
Logs paused
|
Logs paused
|
||||||
{messageBuffer.length > 0 && (
|
{messageBuffer.length > 0 && (
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ interface Props {
|
|||||||
export const AddApplication = ({ environmentId, projectName }: Props) => {
|
export const AddApplication = ({ environmentId, projectName }: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
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 [visible, setVisible] = useState(false);
|
||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
@@ -171,7 +174,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
<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" />
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -191,17 +195,19 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
field.value || (!isCloud ? "dokploy" : undefined)
|
field.value || (showLocalOption ? "dokploy" : undefined)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue
|
<SelectValue
|
||||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
placeholder={
|
||||||
|
showLocalOption ? "Dokploy" : "Select a Server"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
{showLocalOption && (
|
||||||
<SelectItem value="dokploy">
|
<SelectItem value="dokploy">
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span>Dokploy</span>
|
<span>Dokploy</span>
|
||||||
@@ -225,7 +231,8 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
Servers (
|
||||||
|
{servers?.length + (showLocalOption ? 1 : 0)})
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
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 { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const { mutateAsync, isPending, error, isError } =
|
const { mutateAsync, isPending, error, isError } =
|
||||||
api.compose.create.useMutation();
|
api.compose.create.useMutation();
|
||||||
@@ -182,7 +185,8 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
<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" />
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
@@ -202,17 +206,19 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
field.value || (!isCloud ? "dokploy" : undefined)
|
field.value || (showLocalOption ? "dokploy" : undefined)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue
|
<SelectValue
|
||||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
placeholder={
|
||||||
|
showLocalOption ? "Dokploy" : "Select a Server"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
{showLocalOption && (
|
||||||
<SelectItem value="dokploy">
|
<SelectItem value="dokploy">
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span>Dokploy</span>
|
<span>Dokploy</span>
|
||||||
@@ -236,7 +242,8 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
Servers (
|
||||||
|
{servers?.length + (showLocalOption ? 1 : 0)})
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -219,6 +219,9 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
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 { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const libsqlMutation = api.libsql.create.useMutation();
|
const libsqlMutation = api.libsql.create.useMutation();
|
||||||
const mariadbMutation = api.mariadb.create.useMutation();
|
const mariadbMutation = api.mariadb.create.useMutation();
|
||||||
@@ -470,19 +473,20 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
field.value || (!isCloud ? "dokploy" : undefined)
|
field.value ||
|
||||||
|
(showLocalOption ? "dokploy" : undefined)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue
|
<SelectValue
|
||||||
placeholder={
|
placeholder={
|
||||||
!isCloud ? "Dokploy" : "Select a Server"
|
showLocalOption ? "Dokploy" : "Select a Server"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
{showLocalOption && (
|
||||||
<SelectItem value="dokploy">
|
<SelectItem value="dokploy">
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span>Dokploy</span>
|
<span>Dokploy</span>
|
||||||
@@ -501,7 +505,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
Servers (
|
||||||
|
{servers?.length + (showLocalOption ? 1 : 0)})
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</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"
|
className="relative hover:shadow-lg transition-shadow flex flex-col bg-transparent"
|
||||||
>
|
>
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<ServerIcon className="size-5 text-muted-foreground" />
|
<ServerIcon className="size-5 shrink-0 text-muted-foreground" />
|
||||||
<CardTitle className="text-lg">
|
<CardTitle className="text-lg break-words min-w-0">
|
||||||
{server.name}
|
{server.name}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,7 +145,7 @@ export const ShowServers = () => {
|
|||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 shrink-0 p-0"
|
||||||
>
|
>
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
More options
|
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 {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -49,8 +51,17 @@ export const WebServer = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center flex-wrap justify-between gap-4">
|
<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}
|
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>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
Version: {dokployVersion}
|
Version: {dokployVersion}
|
||||||
|
|||||||
@@ -29,10 +29,15 @@ type SSOEmailForm = z.infer<typeof ssoEmailSchema>;
|
|||||||
|
|
||||||
interface SignInWithSSOProps {
|
interface SignInWithSSOProps {
|
||||||
/** Content shown when SSO is collapsed (e.g. email/password form) */
|
/** 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 [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
const form = useForm<SSOEmailForm>({
|
const form = useForm<SSOEmailForm>({
|
||||||
@@ -72,7 +77,7 @@ export function SignInWithSSO({ children }: SignInWithSSOProps) {
|
|||||||
<LogIn className="mr-2 size-4" />
|
<LogIn className="mr-2 size-4" />
|
||||||
Sign in with SSO
|
Sign in with SSO
|
||||||
</Button>
|
</Button>
|
||||||
{children}
|
{!enforce && children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -113,13 +118,15 @@ export function SignInWithSSO({ children }: SignInWithSSOProps) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<button
|
{!enforce && (
|
||||||
type="button"
|
<button
|
||||||
onClick={() => setExpanded(false)}
|
type="button"
|
||||||
className="text-xs text-muted-foreground hover:underline"
|
onClick={() => setExpanded(false)}
|
||||||
>
|
className="text-xs text-muted-foreground hover:underline"
|
||||||
Use email and password instead
|
>
|
||||||
</button>
|
Use email and password instead
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -167,7 +167,13 @@ export const CodeEditor = ({
|
|||||||
? css()
|
? css()
|
||||||
: language === "shell"
|
: language === "shell"
|
||||||
? StreamLanguage.define(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 : [],
|
props.lineWrapping ? EditorView.lineWrapping : [],
|
||||||
language === "yaml"
|
language === "yaml"
|
||||||
? autocompletion({
|
? 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,
|
"when": 1778303519111,
|
||||||
"tag": "0166_nosy_slapstick",
|
"tag": "0166_nosy_slapstick",
|
||||||
"breakpoints": true
|
"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",
|
"name": "dokploy",
|
||||||
"version": "v0.29.4",
|
"version": "v0.29.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
"lucide-react": "^0.469.0",
|
"lucide-react": "^0.469.0",
|
||||||
"micromatch": "4.0.8",
|
"micromatch": "4.0.8",
|
||||||
"nanoid": "3.3.11",
|
"nanoid": "3.3.11",
|
||||||
"next": "^16.2.0",
|
"next": "16.2.6",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"nextjs-toploader": "^3.9.17",
|
"nextjs-toploader": "^3.9.17",
|
||||||
"node-os-utils": "2.0.1",
|
"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 { AddApplication } from "@/components/dashboard/project/add-application";
|
||||||
import { AddCompose } from "@/components/dashboard/project/add-compose";
|
import { AddCompose } from "@/components/dashboard/project/add-compose";
|
||||||
import { AddDatabase } from "@/components/dashboard/project/add-database";
|
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 { AddTemplate } from "@/components/dashboard/project/add-template";
|
||||||
import { AdvancedEnvironmentSelector } from "@/components/dashboard/project/advanced-environment-selector";
|
import { AdvancedEnvironmentSelector } from "@/components/dashboard/project/advanced-environment-selector";
|
||||||
import { DuplicateProject } from "@/components/dashboard/project/duplicate-project";
|
import { DuplicateProject } from "@/components/dashboard/project/duplicate-project";
|
||||||
@@ -1091,6 +1092,10 @@ const EnvironmentPage = (
|
|||||||
projectName={projectData?.name}
|
projectName={projectData?.name}
|
||||||
environmentId={environmentId}
|
environmentId={environmentId}
|
||||||
/>
|
/>
|
||||||
|
<AddImport
|
||||||
|
projectName={projectData?.name}
|
||||||
|
environmentId={environmentId}
|
||||||
|
/>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ const Service = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
|
|
||||||
@@ -147,8 +148,9 @@ const Service = (
|
|||||||
<Badge
|
<Badge
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (data?.server?.ipAddress) {
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
copy(data.server.ipAddress);
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
toast.success("IP Address Copied!");
|
toast.success("IP Address Copied!");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ const Service = (
|
|||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||||
projectId: data?.environment?.projectId || "",
|
projectId: data?.environment?.projectId || "",
|
||||||
});
|
});
|
||||||
@@ -134,8 +135,9 @@ const Service = (
|
|||||||
<Badge
|
<Badge
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (data?.server?.ipAddress) {
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
copy(data.server.ipAddress);
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
toast.success("IP Address Copied!");
|
toast.success("IP Address Copied!");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
import { HelpCircle, ServerOff } from "lucide-react";
|
import { HelpCircle, ServerOff } from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type ReactElement, useState } from "react";
|
import { type ReactElement, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||||
@@ -61,6 +63,7 @@ const Libsql = (
|
|||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pb-10">
|
<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-col h-fit w-fit gap-2">
|
||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
|
toast.success("IP Address Copied!");
|
||||||
|
}
|
||||||
|
}}
|
||||||
variant={
|
variant={
|
||||||
!data?.serverId
|
!data?.serverId
|
||||||
? "default"
|
? "default"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
import { HelpCircle, ServerOff } from "lucide-react";
|
import { HelpCircle, ServerOff } from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type ReactElement, useState } from "react";
|
import { type ReactElement, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||||
@@ -63,6 +65,7 @@ const Mariadb = (
|
|||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
|
|
||||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||||
projectId: data?.environment?.projectId || "",
|
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-col h-fit w-fit gap-2">
|
||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
|
toast.success("IP Address Copied!");
|
||||||
|
}
|
||||||
|
}}
|
||||||
variant={
|
variant={
|
||||||
!data?.serverId
|
!data?.serverId
|
||||||
? "default"
|
? "default"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
import { HelpCircle, ServerOff } from "lucide-react";
|
import { HelpCircle, ServerOff } from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type ReactElement, useState } from "react";
|
import { type ReactElement, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||||
@@ -63,6 +65,7 @@ const Mongo = (
|
|||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||||
projectId: data?.environment?.projectId || "",
|
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-col h-fit w-fit gap-2">
|
||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
|
toast.success("IP Address Copied!");
|
||||||
|
}
|
||||||
|
}}
|
||||||
variant={
|
variant={
|
||||||
!data?.serverId
|
!data?.serverId
|
||||||
? "default"
|
? "default"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
import { HelpCircle, ServerOff } from "lucide-react";
|
import { HelpCircle, ServerOff } from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type ReactElement, useState } from "react";
|
import { type ReactElement, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||||
@@ -62,6 +64,7 @@ const MySql = (
|
|||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||||
projectId: data?.environment?.projectId || "",
|
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-col h-fit w-fit gap-2">
|
||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
|
toast.success("IP Address Copied!");
|
||||||
|
}
|
||||||
|
}}
|
||||||
variant={
|
variant={
|
||||||
!data?.serverId
|
!data?.serverId
|
||||||
? "default"
|
? "default"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
import { HelpCircle, ServerOff } from "lucide-react";
|
import { HelpCircle, ServerOff } from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type ReactElement, useState } from "react";
|
import { type ReactElement, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||||
@@ -62,6 +64,7 @@ const Postgresql = (
|
|||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||||
projectId: data?.environment?.projectId || "",
|
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-col h-fit w-fit gap-2">
|
||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
|
toast.success("IP Address Copied!");
|
||||||
|
}
|
||||||
|
}}
|
||||||
variant={
|
variant={
|
||||||
!data?.serverId
|
!data?.serverId
|
||||||
? "default"
|
? "default"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { validateRequest } from "@dokploy/server/lib/auth";
|
import { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
|
import copy from "copy-to-clipboard";
|
||||||
import { HelpCircle, ServerOff } from "lucide-react";
|
import { HelpCircle, ServerOff } from "lucide-react";
|
||||||
import type {
|
import type {
|
||||||
GetServerSidePropsContext,
|
GetServerSidePropsContext,
|
||||||
@@ -9,6 +10,7 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { type ReactElement, useState } from "react";
|
import { type ReactElement, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-environment";
|
||||||
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
|
||||||
@@ -62,6 +64,7 @@ const Redis = (
|
|||||||
const { data: permissions } = api.user.getPermissions.useQuery();
|
const { data: permissions } = api.user.getPermissions.useQuery();
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: serverIp } = api.settings.getIp.useQuery();
|
||||||
const { data: environments } = api.environment.byProjectId.useQuery({
|
const { data: environments } = api.environment.byProjectId.useQuery({
|
||||||
projectId: data?.environment?.projectId || "",
|
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-col h-fit w-fit gap-2">
|
||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
const ip = data?.server?.ipAddress || serverIp;
|
||||||
|
if (ip) {
|
||||||
|
copy(ip);
|
||||||
|
toast.success("IP Address Copied!");
|
||||||
|
}
|
||||||
|
}}
|
||||||
variant={
|
variant={
|
||||||
!data?.serverId
|
!data?.serverId
|
||||||
? "default"
|
? "default"
|
||||||
|
|||||||
@@ -5,18 +5,13 @@ import type { ReactElement } from "react";
|
|||||||
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
import { ShowSchedules } from "@/components/dashboard/application/schedules/show-schedules";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
|
||||||
|
|
||||||
function SchedulesPage() {
|
function SchedulesPage() {
|
||||||
const { data: user } = api.user.get.useQuery();
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-8xl mx-auto min-h-[45vh]">
|
<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">
|
<div className="rounded-xl bg-background shadow-md h-full">
|
||||||
<ShowSchedules
|
<ShowSchedules scheduleType="dokploy-server" id="dokploy-server" />
|
||||||
scheduleType="dokploy-server"
|
|
||||||
id={user?.user.id || ""}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</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 { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import type { ReactElement } from "react";
|
import type { ReactElement } from "react";
|
||||||
import superjson from "superjson";
|
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 { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-feature-gate";
|
import { EnterpriseFeatureGate } from "@/components/proprietary/enterprise-feature-gate";
|
||||||
import { SSOSettings } from "@/components/proprietary/sso/sso-settings";
|
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";
|
import { appRouter } from "@/server/api/root";
|
||||||
|
|
||||||
const Page = () => {
|
interface Props {
|
||||||
|
isCloud: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Page = ({ isCloud }: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
<div className="h-full rounded-xl max-w-5xl mx-auto flex flex-col gap-4">
|
||||||
@@ -29,6 +41,33 @@ const Page = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -76,6 +115,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
trpcState: helpers.dehydrate(),
|
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 { validateRequest } from "@dokploy/server/lib/auth";
|
||||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||||
import { REGEXP_ONLY_DIGITS } from "input-otp";
|
import { REGEXP_ONLY_DIGITS } from "input-otp";
|
||||||
@@ -52,8 +56,9 @@ type LoginForm = z.infer<typeof LoginSchema>;
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
IS_CLOUD: boolean;
|
IS_CLOUD: boolean;
|
||||||
|
enforceSSO: boolean;
|
||||||
}
|
}
|
||||||
export default function Home({ IS_CLOUD }: Props) {
|
export default function Home({ IS_CLOUD, enforceSSO }: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { config: whitelabeling } = useWhitelabelingPublic();
|
const { config: whitelabeling } = useWhitelabelingPublic();
|
||||||
const { data: showSignInWithSSO } = api.sso.showSignInWithSSO.useQuery();
|
const { data: showSignInWithSSO } = api.sso.showSignInWithSSO.useQuery();
|
||||||
@@ -247,7 +252,9 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
<CardContent className="p-0">
|
<CardContent className="p-0">
|
||||||
{!isTwoFactor ? (
|
{!isTwoFactor ? (
|
||||||
<>
|
<>
|
||||||
{showSignInWithSSO ? (
|
{enforceSSO ? (
|
||||||
|
<SignInWithSSO enforce />
|
||||||
|
) : showSignInWithSSO ? (
|
||||||
<SignInWithSSO>{loginContent}</SignInWithSSO>
|
<SignInWithSSO>{loginContent}</SignInWithSSO>
|
||||||
) : (
|
) : (
|
||||||
loginContent
|
loginContent
|
||||||
@@ -417,6 +424,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
IS_CLOUD: IS_CLOUD,
|
IS_CLOUD: IS_CLOUD,
|
||||||
|
enforceSSO: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -442,9 +450,12 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const webServerSettings = await getWebServerSettings();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
hasAdmin,
|
hasAdmin,
|
||||||
|
enforceSSO: webServerSettings?.enforceSSO ?? false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ async function main() {
|
|||||||
|
|
||||||
if (records.length === 0) {
|
if (records.length === 0) {
|
||||||
console.log("✅ No 2FA records found, nothing to migrate.");
|
console.log("✅ No 2FA records found, nothing to migrate.");
|
||||||
return;
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`📦 Found ${records.length} 2FA record(s) to migrate.`);
|
console.log(`📦 Found ${records.length} 2FA record(s) to migrate.`);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getAccessibleServerIds,
|
getAccessibleServerIds,
|
||||||
getApplicationStats,
|
getApplicationStats,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
|
getWebServerSettings,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
mechanizeDockerContainer,
|
mechanizeDockerContainer,
|
||||||
readConfig,
|
readConfig,
|
||||||
@@ -87,7 +88,11 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkServiceAccess(ctx, project.projectId, "create");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create an application",
|
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");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a compose",
|
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");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a compose",
|
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
|
import: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
@@ -151,6 +151,14 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
deployment: ["cancel"],
|
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) {
|
if (!deployment.pid) {
|
||||||
@@ -188,6 +196,14 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
await checkServicePermissionAndAccess(ctx, serviceId, {
|
await checkServicePermissionAndAccess(ctx, serviceId, {
|
||||||
deployment: ["cancel"],
|
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);
|
const result = await removeDeployment(input.deploymentId);
|
||||||
await audit(ctx, {
|
await audit(ctx, {
|
||||||
@@ -197,4 +213,47 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
return result;
|
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,
|
findProjectById,
|
||||||
getAccessibleServerIds,
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
|
getWebServerSettings,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
removeLibsqlById,
|
removeLibsqlById,
|
||||||
@@ -51,7 +52,11 @@ export const libsqlRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkServiceAccess(ctx, project.projectId, "create");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a Libsql",
|
message: "You need to use a server to create a Libsql",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getAccessibleServerIds,
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
|
getWebServerSettings,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
removeMariadbById,
|
removeMariadbById,
|
||||||
@@ -62,7 +63,11 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkServiceAccess(ctx, project.projectId, "create");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a Mariadb",
|
message: "You need to use a server to create a Mariadb",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getAccessibleServerIds,
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
|
getWebServerSettings,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
removeMongoById,
|
removeMongoById,
|
||||||
@@ -61,7 +62,11 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkServiceAccess(ctx, project.projectId, "create");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a mongo",
|
message: "You need to use a server to create a mongo",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
getAccessibleServerIds,
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
|
getWebServerSettings,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
removeMySqlById,
|
removeMySqlById,
|
||||||
@@ -62,7 +63,11 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkServiceAccess(ctx, project.projectId, "create");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a MySQL",
|
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 assigning a custom role, verify it exists
|
||||||
if (!["owner", "admin", "member"].includes(input.role)) {
|
if (!["owner", "admin", "member"].includes(input.role)) {
|
||||||
const customRole = await db.query.organizationRole.findFirst({
|
const customRole = await db.query.organizationRole.findFirst({
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getMountPath,
|
getMountPath,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
|
getWebServerSettings,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
removePostgresById,
|
removePostgresById,
|
||||||
@@ -63,7 +64,11 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkServiceAccess(ctx, project.projectId, "create");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a Postgres",
|
message: "You need to use a server to create a Postgres",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
requestToHeaders,
|
requestToHeaders,
|
||||||
} from "@dokploy/server/index";
|
} from "@dokploy/server/index";
|
||||||
import { auth } from "@dokploy/server/lib/auth";
|
import { auth } from "@dokploy/server/lib/auth";
|
||||||
|
import { getWebServerSettings } from "@dokploy/server/services/web-server-settings";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { and, asc, eq } from "drizzle-orm";
|
import { and, asc, eq } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -43,6 +44,13 @@ export const ssoRouter = createTRPCRouter({
|
|||||||
owner.user.enableEnterpriseFeatures && owner.user.isValidEnterpriseLicense
|
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 }) => {
|
listProviders: enterpriseProcedure.query(async ({ ctx }) => {
|
||||||
const providers = await db.query.ssoProvider.findMany({
|
const providers = await db.query.ssoProvider.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
getAccessibleServerIds,
|
getAccessibleServerIds,
|
||||||
getContainerLogs,
|
getContainerLogs,
|
||||||
getServiceContainerCommand,
|
getServiceContainerCommand,
|
||||||
|
getWebServerSettings,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
removeRedisById,
|
removeRedisById,
|
||||||
@@ -59,7 +60,11 @@ export const redisRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await checkServiceAccess(ctx, project.projectId, "create");
|
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({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You need to use a server to create a Redis",
|
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 (newSchedule?.enabled) {
|
||||||
if (IS_CLOUD) {
|
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);
|
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);
|
await deleteSchedule(input.scheduleId);
|
||||||
|
|
||||||
@@ -323,21 +306,27 @@ export const scheduleRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (input.scheduleType === "dokploy-server") {
|
||||||
input.scheduleType === "dokploy-server" &&
|
const member = await findMemberByUserId(
|
||||||
input.id !== ctx.user.id
|
ctx.user.id,
|
||||||
) {
|
ctx.session.activeOrganizationId,
|
||||||
throw new TRPCError({
|
);
|
||||||
code: "UNAUTHORIZED",
|
if (member.role !== "owner" && member.role !== "admin") {
|
||||||
message: "You can only list your own host-level schedules.",
|
throw new TRPCError({
|
||||||
});
|
code: "FORBIDDEN",
|
||||||
|
message: "Only owners and admins can list host-level schedules.",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const where = {
|
const where = {
|
||||||
application: eq(schedules.applicationId, input.id),
|
application: eq(schedules.applicationId, input.id),
|
||||||
compose: eq(schedules.composeId, input.id),
|
compose: eq(schedules.composeId, input.id),
|
||||||
server: eq(schedules.serverId, 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({
|
return db.query.schedules.findMany({
|
||||||
where: where[input.scheduleType],
|
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;
|
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 {
|
try {
|
||||||
await runCommand(input.scheduleId);
|
await runCommand(input.scheduleId);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const securityRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const security = await findSecurityById(input.securityId);
|
const security = await findSecurityById(input.securityId);
|
||||||
await checkServicePermissionAndAccess(ctx, security.applicationId, {
|
await checkServicePermissionAndAccess(ctx, security.applicationId, {
|
||||||
service: ["delete"],
|
service: ["create"],
|
||||||
});
|
});
|
||||||
const result = await deleteSecurityById(input.securityId);
|
const result = await deleteSecurityById(input.securityId);
|
||||||
await audit(ctx, {
|
await audit(ctx, {
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import { appRouter } from "../root";
|
|||||||
import {
|
import {
|
||||||
adminProcedure,
|
adminProcedure,
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
|
enterpriseProcedure,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
publicProcedure,
|
publicProcedure,
|
||||||
} from "../trpc";
|
} from "../trpc";
|
||||||
@@ -445,6 +446,50 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
return true;
|
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(() => {
|
readTraefikConfig: adminProcedure.query(() => {
|
||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
apiUpdateUser,
|
apiUpdateUser,
|
||||||
invitation,
|
invitation,
|
||||||
member,
|
member,
|
||||||
|
session,
|
||||||
user,
|
user,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import {
|
import {
|
||||||
@@ -32,7 +33,7 @@ import {
|
|||||||
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import * as bcrypt from "bcrypt";
|
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 { z } from "zod";
|
||||||
import { audit } from "@/server/api/utils/audit";
|
import { audit } from "@/server/api/utils/audit";
|
||||||
import {
|
import {
|
||||||
@@ -229,6 +230,15 @@ export const userRouter = createTRPCRouter({
|
|||||||
password: bcrypt.hashSync(input.password, 10),
|
password: bcrypt.hashSync(input.password, 10),
|
||||||
})
|
})
|
||||||
.where(eq(account.userId, ctx.user.id));
|
.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 {
|
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({
|
return await createOrganizationUserWithCredentials({
|
||||||
organizationId: ctx.session.activeOrganizationId,
|
organizationId: ctx.session.activeOrganizationId,
|
||||||
email: input.email,
|
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
|
// Registry URLs must be hostname[:port] only — no shell metacharacters
|
||||||
// Empty string is allowed (means default/Docker Hub registry)
|
// Empty string is allowed (means default/Docker Hub registry)
|
||||||
const registryUrlSchema = z
|
const registryUrlSchema = z
|
||||||
@@ -57,7 +64,7 @@ const registryUrlSchema = z
|
|||||||
|
|
||||||
const createSchema = createInsertSchema(registry, {
|
const createSchema = createInsertSchema(registry, {
|
||||||
registryName: z.string().min(1),
|
registryName: z.string().min(1),
|
||||||
username: z.string().min(1),
|
username: registryUsernameSchema,
|
||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
registryUrl: registryUrlSchema,
|
registryUrl: registryUrlSchema,
|
||||||
organizationId: z.string().min(1),
|
organizationId: z.string().min(1),
|
||||||
@@ -70,7 +77,7 @@ export const apiCreateRegistry = createSchema
|
|||||||
.pick({})
|
.pick({})
|
||||||
.extend({
|
.extend({
|
||||||
registryName: z.string().min(1),
|
registryName: z.string().min(1),
|
||||||
username: z.string().min(1),
|
username: registryUsernameSchema,
|
||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
registryUrl: registryUrlSchema,
|
registryUrl: registryUrlSchema,
|
||||||
registryType: z.enum(["cloud"]),
|
registryType: z.enum(["cloud"]),
|
||||||
@@ -83,7 +90,7 @@ export const apiCreateRegistry = createSchema
|
|||||||
|
|
||||||
export const apiTestRegistry = createSchema.pick({}).extend({
|
export const apiTestRegistry = createSchema.pick({}).extend({
|
||||||
registryName: z.string().optional(),
|
registryName: z.string().optional(),
|
||||||
username: z.string().min(1),
|
username: registryUsernameSchema,
|
||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
registryUrl: registryUrlSchema,
|
registryUrl: registryUrlSchema,
|
||||||
registryType: z.enum(["cloud"]),
|
registryType: z.enum(["cloud"]),
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
|||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { organization } from "./account";
|
||||||
import { applications } from "./application";
|
import { applications } from "./application";
|
||||||
import { compose } from "./compose";
|
import { compose } from "./compose";
|
||||||
import { deployments } from "./deployment";
|
import { deployments } from "./deployment";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import { user } from "./user";
|
|
||||||
import { generateAppName } from "./utils";
|
import { generateAppName } from "./utils";
|
||||||
export const shellTypes = pgEnum("shellType", ["bash", "sh"]);
|
export const shellTypes = pgEnum("shellType", ["bash", "sh"]);
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export const schedules = pgTable("schedule", {
|
|||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
userId: text("userId").references(() => user.id, {
|
organizationId: text("organizationId").references(() => organization.id, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
enabled: boolean("enabled").notNull().default(true),
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
@@ -71,9 +71,9 @@ export const schedulesRelations = relations(schedules, ({ one, many }) => ({
|
|||||||
fields: [schedules.serverId],
|
fields: [schedules.serverId],
|
||||||
references: [server.serverId],
|
references: [server.serverId],
|
||||||
}),
|
}),
|
||||||
user: one(user, {
|
organization: one(organization, {
|
||||||
fields: [schedules.userId],
|
fields: [schedules.organizationId],
|
||||||
references: [user.id],
|
references: [organization.id],
|
||||||
}),
|
}),
|
||||||
deployments: many(deployments),
|
deployments: many(deployments),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ export const webServerSettings = pgTable("webServerSettings", {
|
|||||||
metaTitle: null,
|
metaTitle: null,
|
||||||
footerText: 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
|
// Cache Cleanup Configuration
|
||||||
cleanupCacheApplications: boolean("cleanupCacheApplications")
|
cleanupCacheApplications: boolean("cleanupCacheApplications")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -155,6 +159,8 @@ export const apiUpdateWebServerSettings = createSchema.partial().extend({
|
|||||||
cleanupCacheApplications: z.boolean().optional(),
|
cleanupCacheApplications: z.boolean().optional(),
|
||||||
cleanupCacheOnPreviews: z.boolean().optional(),
|
cleanupCacheOnPreviews: z.boolean().optional(),
|
||||||
cleanupCacheOnCompose: z.boolean().optional(),
|
cleanupCacheOnCompose: z.boolean().optional(),
|
||||||
|
remoteServersOnly: z.boolean().optional(),
|
||||||
|
enforceSSO: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiAssignDomain = z
|
export const apiAssignDomain = z
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export * from "./utils/docker/types";
|
|||||||
export * from "./utils/docker/utils";
|
export * from "./utils/docker/utils";
|
||||||
export * from "./utils/filesystem/directory";
|
export * from "./utils/filesystem/directory";
|
||||||
export * from "./utils/filesystem/ssh";
|
export * from "./utils/filesystem/ssh";
|
||||||
|
export * from "./utils/git-branch-validation";
|
||||||
export * from "./utils/gpu-setup";
|
export * from "./utils/gpu-setup";
|
||||||
export * from "./utils/notifications/build-error";
|
export * from "./utils/notifications/build-error";
|
||||||
export * from "./utils/notifications/build-success";
|
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/dokploy-restart";
|
||||||
export * from "./utils/notifications/server-threshold";
|
export * from "./utils/notifications/server-threshold";
|
||||||
export * from "./utils/notifications/utils";
|
export * from "./utils/notifications/utils";
|
||||||
export * from "./utils/git-branch-validation";
|
|
||||||
export * from "./utils/process/execAsync";
|
export * from "./utils/process/execAsync";
|
||||||
export * from "./utils/process/spawnAsync";
|
export * from "./utils/process/spawnAsync";
|
||||||
export * from "./utils/providers/bitbucket";
|
export * from "./utils/providers/bitbucket";
|
||||||
|
|||||||
@@ -80,9 +80,10 @@ export const checkPermission = async (
|
|||||||
const { id: userId } = ctx.user;
|
const { id: userId } = ctx.user;
|
||||||
const { activeOrganizationId: organizationId } = ctx.session;
|
const { activeOrganizationId: organizationId } = ctx.session;
|
||||||
const memberRecord = await findMemberByUserId(userId, organizationId);
|
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) =>
|
const allEnterprise = Object.keys(permissions).every((r) =>
|
||||||
enterpriseOnlyResources.has(r),
|
enterpriseOnlyResources.has(r),
|
||||||
);
|
);
|
||||||
@@ -164,6 +165,8 @@ const getLegacyOverrides = (
|
|||||||
},
|
},
|
||||||
sshKeys: {
|
sshKeys: {
|
||||||
read: !!memberRecord.canAccessToSSHKeys,
|
read: !!memberRecord.canAccessToSSHKeys,
|
||||||
|
create: !!memberRecord.canAccessToSSHKeys,
|
||||||
|
delete: !!memberRecord.canAccessToSSHKeys,
|
||||||
},
|
},
|
||||||
gitProviders: {
|
gitProviders: {
|
||||||
read: !!memberRecord.canAccessToGitProviders,
|
read: !!memberRecord.canAccessToGitProviders,
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ export const findScheduleOrganizationId = async (scheduleId: string) => {
|
|||||||
if (schedule?.server) {
|
if (schedule?.server) {
|
||||||
return schedule?.server?.organization?.id;
|
return schedule?.server?.organization?.id;
|
||||||
}
|
}
|
||||||
|
if (schedule?.organizationId) {
|
||||||
|
return schedule.organizationId;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Compose Type: ${composeType} ✅`;
|
|||||||
cd "${projectPath}";
|
cd "${projectPath}";
|
||||||
|
|
||||||
${compose.isolatedDeployment ? `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create ${compose.composeType === "stack" ? "--driver overlay" : ""} --attachable ${compose.appName}` : ""}
|
${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` : ""}
|
${compose.isolatedDeployment ? `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1` : ""}
|
||||||
|
|
||||||
echo "Docker Compose Deployed: ✅";
|
echo "Docker Compose Deployed: ✅";
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ export const getRegistryTag = (registry: Registry, imageName: string) => {
|
|||||||
// Extract the repository name (last part after '/')
|
// Extract the repository name (last part after '/')
|
||||||
const repositoryName = extractRepositoryName(imageName);
|
const repositoryName = extractRepositoryName(imageName);
|
||||||
|
|
||||||
// Build the final tag using registry's username/prefix
|
// Build the final tag using registry's username/prefix (must be lowercase for valid image refs)
|
||||||
const targetPrefix = imagePrefix || username;
|
const targetPrefix = (imagePrefix || username).toLowerCase();
|
||||||
const finalRegistry = registryUrl || "";
|
const finalRegistry = registryUrl || "";
|
||||||
|
|
||||||
return finalRegistry
|
return finalRegistry
|
||||||
|
|||||||
@@ -337,6 +337,10 @@ export const createDomainLabels = (
|
|||||||
labels.push(
|
labels.push(
|
||||||
`traefik.http.routers.${routerName}.tls.certresolver=${customCertResolver}`,
|
`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,
|
server: true,
|
||||||
application: true,
|
application: true,
|
||||||
compose: true,
|
compose: true,
|
||||||
user: true,
|
organization: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import micromatch from "micromatch";
|
|||||||
|
|
||||||
export const shouldDeploy = (
|
export const shouldDeploy = (
|
||||||
watchPaths: string[] | null,
|
watchPaths: string[] | null,
|
||||||
modifiedFiles: string[],
|
modifiedFiles: (string | null | undefined)[] | null | undefined,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (!watchPaths || watchPaths?.length === 0) return true;
|
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,
|
directory: string,
|
||||||
serverId?: string | null,
|
serverId?: string | null,
|
||||||
) => {
|
) => {
|
||||||
if (!/^[\w/. :-]{1,500}$/.test(directory)) {
|
if (!/^[\w/. :[\]-]{1,500}$/.test(directory)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
219
pnpm-lock.yaml
generated
219
pnpm-lock.yaml
generated
@@ -51,7 +51,7 @@ importers:
|
|||||||
version: 4.12.2
|
version: 4.12.2
|
||||||
inngest:
|
inngest:
|
||||||
specifier: 3.40.1
|
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:
|
pino:
|
||||||
specifier: 9.4.0
|
specifier: 9.4.0
|
||||||
version: 9.4.0
|
version: 9.4.0
|
||||||
@@ -115,10 +115,10 @@ importers:
|
|||||||
version: 2.0.30(zod@4.3.6)
|
version: 2.0.30(zod@4.3.6)
|
||||||
'@better-auth/api-key':
|
'@better-auth/api-key':
|
||||||
specifier: 1.5.4
|
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':
|
'@better-auth/sso':
|
||||||
specifier: 1.5.4
|
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':
|
'@codemirror/autocomplete':
|
||||||
specifier: ^6.18.6
|
specifier: ^6.18.6
|
||||||
version: 6.20.0
|
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)
|
version: 11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3)
|
||||||
'@trpc/next':
|
'@trpc/next':
|
||||||
specifier: ^11.10.0
|
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':
|
'@trpc/react-query':
|
||||||
specifier: ^11.10.0
|
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)
|
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)
|
version: 5.1.1(encoding@0.1.13)
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.5.4
|
specifier: 1.5.4
|
||||||
version: 1.5.4(71a760b327c31dd12606432855d01199)
|
version: 1.5.4(4c76b7da14d170b1922a58fb44839507)
|
||||||
bl:
|
bl:
|
||||||
specifier: 6.0.11
|
specifier: 6.0.11
|
||||||
version: 6.0.11
|
version: 6.0.11
|
||||||
@@ -342,14 +342,14 @@ importers:
|
|||||||
specifier: 3.3.11
|
specifier: 3.3.11
|
||||||
version: 3.3.11
|
version: 3.3.11
|
||||||
next:
|
next:
|
||||||
specifier: ^16.2.0
|
specifier: 16.2.6
|
||||||
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)
|
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:
|
next-themes:
|
||||||
specifier: ^0.2.1
|
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:
|
nextjs-toploader:
|
||||||
specifier: ^3.9.17
|
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:
|
node-os-utils:
|
||||||
specifier: 2.0.1
|
specifier: 2.0.1
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
@@ -630,10 +630,10 @@ importers:
|
|||||||
version: 2.0.30(zod@4.3.6)
|
version: 2.0.30(zod@4.3.6)
|
||||||
'@better-auth/api-key':
|
'@better-auth/api-key':
|
||||||
specifier: 1.5.4
|
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':
|
'@better-auth/sso':
|
||||||
specifier: 1.5.4
|
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':
|
'@better-auth/utils':
|
||||||
specifier: 0.3.1
|
specifier: 0.3.1
|
||||||
version: 0.3.1
|
version: 0.3.1
|
||||||
@@ -672,7 +672,7 @@ importers:
|
|||||||
version: 5.1.1(encoding@0.1.13)
|
version: 5.1.1(encoding@0.1.13)
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.5.4
|
specifier: 1.5.4
|
||||||
version: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
version: 1.5.4(9312701f7238452a4ac943b2bb39c424)
|
||||||
better-call:
|
better-call:
|
||||||
specifier: 2.0.2
|
specifier: 2.0.2
|
||||||
version: 2.0.2(zod@4.3.6)
|
version: 2.0.2(zod@4.3.6)
|
||||||
@@ -775,7 +775,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@better-auth/cli':
|
'@better-auth/cli':
|
||||||
specifier: 1.4.21
|
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':
|
'@types/adm-zip':
|
||||||
specifier: ^0.5.7
|
specifier: ^0.5.7
|
||||||
version: 0.5.7
|
version: 0.5.7
|
||||||
@@ -1878,53 +1878,53 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/env@16.2.0':
|
'@next/env@16.2.6':
|
||||||
resolution: {integrity: sha512-OZIbODWWAi0epQRCRjNe1VO45LOFBzgiyqmTLzIqWq6u1wrxKnAyz1HH6tgY/Mc81YzIjRPoYsPAEr4QV4l9TA==}
|
resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==}
|
||||||
|
|
||||||
'@next/swc-darwin-arm64@16.2.0':
|
'@next/swc-darwin-arm64@16.2.6':
|
||||||
resolution: {integrity: sha512-/JZsqKzKt01IFoiLLAzlNqys7qk2F3JkcUhj50zuRhKDQkZNOz9E5N6wAQWprXdsvjRP4lTFj+/+36NSv5AwhQ==}
|
resolution: {integrity: sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-darwin-x64@16.2.0':
|
'@next/swc-darwin-x64@16.2.6':
|
||||||
resolution: {integrity: sha512-/hV8erWq4SNlVgglUiW5UmQ5Hwy5EW/AbbXlJCn6zkfKxTy/E/U3V8U1Ocm2YCTUoFgQdoMxRyRMOW5jYy4ygg==}
|
resolution: {integrity: sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@16.2.0':
|
'@next/swc-linux-arm64-gnu@16.2.6':
|
||||||
resolution: {integrity: sha512-GkjL/Q7MWOwqWR9zoxu1TIHzkOI2l2BHCf7FzeQG87zPgs+6WDh+oC9Sw9ARuuL/FUk6JNCgKRkA6rEQYadUaw==}
|
resolution: {integrity: sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@16.2.0':
|
'@next/swc-linux-arm64-musl@16.2.6':
|
||||||
resolution: {integrity: sha512-1ffhC6KY5qWLg5miMlKJp3dZbXelEfjuXt1qcp5WzSCQy36CV3y+JT7OC1WSFKizGQCDOcQbfkH/IjZP3cdRNA==}
|
resolution: {integrity: sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@16.2.0':
|
'@next/swc-linux-x64-gnu@16.2.6':
|
||||||
resolution: {integrity: sha512-FmbDcZQ8yJRq93EJSL6xaE0KK/Rslraf8fj1uViGxg7K4CKBCRYSubILJPEhjSgZurpcPQq12QNOJQ0DRJl6Hg==}
|
resolution: {integrity: sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@16.2.0':
|
'@next/swc-linux-x64-musl@16.2.6':
|
||||||
resolution: {integrity: sha512-HzjIHVkmGAwRbh/vzvoBWWEbb8BBZPxBvVbDQDvzHSf3D8RP/4vjw7MNLDXFF9Q1WEzeQyEj2zdxBtVAHu5Oyw==}
|
resolution: {integrity: sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@16.2.0':
|
'@next/swc-win32-arm64-msvc@16.2.6':
|
||||||
resolution: {integrity: sha512-UMiFNQf5H7+1ZsZPxEsA064WEuFbRNq/kEXyepbCnSErp4f5iut75dBA8UeerFIG3vDaQNOfCpevnERPp2V+nA==}
|
resolution: {integrity: sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@16.2.0':
|
'@next/swc-win32-x64-msvc@16.2.6':
|
||||||
resolution: {integrity: sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA==}
|
resolution: {integrity: sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -4171,6 +4171,7 @@ packages:
|
|||||||
|
|
||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
|
||||||
|
|
||||||
'@vercel/oidc@3.1.0':
|
'@vercel/oidc@3.1.0':
|
||||||
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
||||||
@@ -6565,8 +6566,8 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-dom: '*'
|
react-dom: '*'
|
||||||
|
|
||||||
next@16.2.0:
|
next@16.2.6:
|
||||||
resolution: {integrity: sha512-NLBVrJy1pbV1Yn00L5sU4vFyAHt5XuSjzrNyFnxo6Com0M0KrL6hHM5B99dbqXb2bE9pm4Ow3Zl1xp6HVY9edQ==}
|
resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==}
|
||||||
engines: {node: '>=20.9.0'}
|
engines: {node: '>=20.9.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -8664,21 +8665,21 @@ snapshots:
|
|||||||
|
|
||||||
'@balena/dockerignore@1.0.2': {}
|
'@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:
|
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/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/utils': 0.3.1
|
||||||
better-auth: 1.5.4(48b68ecaf84f5e14652b8d87fbbd7ca9)
|
better-auth: 1.5.4(4c76b7da14d170b1922a58fb44839507)
|
||||||
zod: 4.3.6
|
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:
|
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/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/utils': 0.3.1
|
||||||
better-auth: 1.5.4(71a760b327c31dd12606432855d01199)
|
better-auth: 1.5.4(9312701f7238452a4ac943b2bb39c424)
|
||||||
zod: 4.3.6
|
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:
|
dependencies:
|
||||||
'@babel/core': 7.29.0
|
'@babel/core': 7.29.0
|
||||||
'@babel/preset-react': 7.28.5(@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
|
'@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))
|
'@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
|
'@types/pg': 8.16.0
|
||||||
better-auth: 1.4.21(97e31320bc7dc8a33b04861de973b388)
|
better-auth: 1.4.21(23a7835d44d42e8df1218333453e2b24)
|
||||||
better-sqlite3: 12.6.2
|
better-sqlite3: 12.6.2
|
||||||
c12: 3.3.3
|
c12: 3.3.3
|
||||||
chalk: 5.6.2
|
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/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)
|
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:
|
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/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/utils': 0.3.1
|
||||||
'@better-fetch/fetch': 1.1.21
|
'@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)
|
better-call: 2.0.2(zod@4.3.6)
|
||||||
fast-xml-parser: 5.5.1
|
fast-xml-parser: 5.5.1
|
||||||
jose: 6.1.3
|
jose: 6.1.3
|
||||||
samlify: 2.10.2
|
samlify: 2.10.2
|
||||||
zod: 4.3.6
|
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:
|
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/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/utils': 0.3.1
|
||||||
'@better-fetch/fetch': 1.1.21
|
'@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)
|
better-call: 2.0.2(zod@4.3.6)
|
||||||
fast-xml-parser: 5.5.1
|
fast-xml-parser: 5.5.1
|
||||||
jose: 6.1.3
|
jose: 6.1.3
|
||||||
@@ -9512,30 +9513,30 @@ snapshots:
|
|||||||
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
||||||
optional: true
|
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
|
optional: true
|
||||||
|
|
||||||
'@next/swc-darwin-x64@16.2.0':
|
'@next/swc-darwin-x64@16.2.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-gnu@16.2.0':
|
'@next/swc-linux-arm64-gnu@16.2.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@16.2.0':
|
'@next/swc-linux-arm64-musl@16.2.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@16.2.0':
|
'@next/swc-linux-x64-gnu@16.2.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@16.2.0':
|
'@next/swc-linux-x64-musl@16.2.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@16.2.0':
|
'@next/swc-win32-arm64-msvc@16.2.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@next/swc-win32-x64-msvc@16.2.0':
|
'@next/swc-win32-x64-msvc@16.2.6':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@noble/ciphers@2.1.1': {}
|
'@noble/ciphers@2.1.1': {}
|
||||||
@@ -12026,11 +12027,11 @@ snapshots:
|
|||||||
'@trpc/server': 11.10.0(typescript@5.9.3)
|
'@trpc/server': 11.10.0(typescript@5.9.3)
|
||||||
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:
|
dependencies:
|
||||||
'@trpc/client': 11.10.0(@trpc/server@11.10.0(typescript@5.9.3))(typescript@5.9.3)
|
'@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)
|
'@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: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
@@ -12534,7 +12535,7 @@ snapshots:
|
|||||||
|
|
||||||
before-after-hook@2.2.3: {}
|
before-after-hook@2.2.3: {}
|
||||||
|
|
||||||
better-auth@1.4.21(97e31320bc7dc8a33b04861de973b388):
|
better-auth@1.4.21(23a7835d44d42e8df1218333453e2b24):
|
||||||
dependencies:
|
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/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))
|
'@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))
|
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)
|
mongodb: 7.1.0(socks@2.8.8)
|
||||||
mysql2: 3.15.3
|
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
|
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)
|
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: 18.2.0
|
||||||
react-dom: 18.2.0(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)
|
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:
|
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/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/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))
|
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)
|
mongodb: 7.1.0(socks@2.8.8)
|
||||||
mysql2: 3.15.3
|
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)
|
|
||||||
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)
|
|
||||||
pg: 8.18.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)
|
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: 18.2.0
|
||||||
@@ -12632,6 +12598,41 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@cloudflare/workers-types'
|
- '@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):
|
better-call@1.1.8(zod@4.3.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/utils': 0.3.1
|
'@better-auth/utils': 0.3.1
|
||||||
@@ -13881,7 +13882,7 @@ snapshots:
|
|||||||
|
|
||||||
inline-style-parser@0.2.7: {}
|
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:
|
dependencies:
|
||||||
'@bufbuild/protobuf': 2.11.0
|
'@bufbuild/protobuf': 2.11.0
|
||||||
'@inngest/ai': 0.1.7
|
'@inngest/ai': 0.1.7
|
||||||
@@ -13908,7 +13909,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
h3: 1.15.1
|
h3: 1.15.1
|
||||||
hono: 4.12.2
|
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
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
@@ -14704,15 +14705,15 @@ snapshots:
|
|||||||
|
|
||||||
neotraverse@0.6.18: {}
|
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:
|
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: 18.2.0
|
||||||
react-dom: 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:
|
dependencies:
|
||||||
'@next/env': 16.2.0
|
'@next/env': 16.2.6
|
||||||
'@swc/helpers': 0.5.15
|
'@swc/helpers': 0.5.15
|
||||||
baseline-browser-mapping: 2.10.0
|
baseline-browser-mapping: 2.10.0
|
||||||
caniuse-lite: 1.0.30001774
|
caniuse-lite: 1.0.30001774
|
||||||
@@ -14721,23 +14722,23 @@ snapshots:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
styled-jsx: 5.1.6(@babel/core@7.29.0)(react@18.2.0)
|
styled-jsx: 5.1.6(@babel/core@7.29.0)(react@18.2.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 16.2.0
|
'@next/swc-darwin-arm64': 16.2.6
|
||||||
'@next/swc-darwin-x64': 16.2.0
|
'@next/swc-darwin-x64': 16.2.6
|
||||||
'@next/swc-linux-arm64-gnu': 16.2.0
|
'@next/swc-linux-arm64-gnu': 16.2.6
|
||||||
'@next/swc-linux-arm64-musl': 16.2.0
|
'@next/swc-linux-arm64-musl': 16.2.6
|
||||||
'@next/swc-linux-x64-gnu': 16.2.0
|
'@next/swc-linux-x64-gnu': 16.2.6
|
||||||
'@next/swc-linux-x64-musl': 16.2.0
|
'@next/swc-linux-x64-musl': 16.2.6
|
||||||
'@next/swc-win32-arm64-msvc': 16.2.0
|
'@next/swc-win32-arm64-msvc': 16.2.6
|
||||||
'@next/swc-win32-x64-msvc': 16.2.0
|
'@next/swc-win32-x64-msvc': 16.2.6
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
sharp: 0.34.5
|
sharp: 0.34.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- 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:
|
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
|
nprogress: 0.2.0
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user