diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 58825f900..0b849afc0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,16 +6,13 @@ Please describe in a short paragraph what this PR is about. Before submitting this PR, please make sure that: -- [ ] You created a dedicated branch based on the `canary` branch. -- [ ] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request -- [ ] You have tested this PR in your local instance. +- [] You created a dedicated branch based on the `canary` branch. +- [] You have read the suggestions in the CONTRIBUTING.md file https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#pull-request +- [] You have tested this PR in your local instance. ## Issues related (if applicable) -Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`, `fixes #ISSUE_NUMBER`, `resolves #ISSUE_NUMBER` - -Example: `closes #123` +closes #123 ## Screenshots (if applicable) -If you include a video or screenshot, would be awesome so we can see the changes in action. \ No newline at end of file diff --git a/.github/sponsors/tuple.png b/.github/sponsors/tuple.png new file mode 100644 index 000000000..1d7be47ca Binary files /dev/null and b/.github/sponsors/tuple.png differ diff --git a/.github/workflows/dokploy.yml b/.github/workflows/dokploy.yml index afb4ba4d2..529cd8f7f 100644 --- a/.github/workflows/dokploy.yml +++ b/.github/workflows/dokploy.yml @@ -2,7 +2,7 @@ name: Dokploy Docker Build on: push: - branches: [main, canary] + branches: [main, canary, "fix/re-apply-database-migration-fix"] workflow_dispatch: env: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index e9591f3cc..6c74dbc02 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -4,9 +4,15 @@ on: pull_request: branches: [main, canary] +permissions: + contents: read + jobs: - lint-and-typecheck: + pr-check: runs-on: ubuntu-latest + strategy: + matrix: + job: [build, test, typecheck] steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -15,32 +21,5 @@ jobs: node-version: 20.16.0 cache: "pnpm" - run: pnpm install --frozen-lockfile - - run: pnpm run server:build - - run: pnpm typecheck - - build-and-test: - needs: lint-and-typecheck - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20.16.0 - cache: "pnpm" - - run: pnpm install --frozen-lockfile - - run: pnpm run server:build - - run: pnpm build - - parallel-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20.16.0 - cache: "pnpm" - - run: pnpm install --frozen-lockfile - - run: pnpm run server:build - - run: pnpm test + - run: pnpm server:build + - run: pnpm ${{ matrix.job }} diff --git a/README.md b/README.md index fb2ee82a5..8faf22a35 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,25 @@
+ + +
+ Special thanks to: +
+
+ + Tuple's sponsorship image + + +### [Tuple, the premier screen sharing app for developers](https://tuple.app/dokploy) +[Available for MacOS & Windows](https://tuple.app/dokploy)
+ +
+ + Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases. + ## ✨ Features Dokploy includes multiple features to make your life easier. diff --git a/apps/api/package.json b/apps/api/package.json index f0d22fedf..dfc2a355d 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -9,6 +9,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "inngest": "3.40.1", "@dokploy/server": "workspace:*", "@hono/node-server": "^1.14.3", "@hono/zod-validator": "0.3.0", diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 0db565995..aa7358335 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -2,21 +2,79 @@ import { serve } from "@hono/node-server"; import { Hono } from "hono"; import "dotenv/config"; import { zValidator } from "@hono/zod-validator"; -import { Queue } from "@nerimity/mimiqueue"; -import { createClient } from "redis"; +import { Inngest } from "inngest"; +import { serve as serveInngest } from "inngest/hono"; import { logger } from "./logger.js"; import { type DeployJob, deployJobSchema } from "./schema.js"; import { deploy } from "./utils.js"; const app = new Hono(); -const redisClient = createClient({ - url: process.env.REDIS_URL, + +// Initialize Inngest client +export const inngest = new Inngest({ + id: "dokploy-deployments", + name: "Dokploy Deployment Service", }); +export const deploymentFunction = inngest.createFunction( + { + id: "deploy-application", + name: "Deploy Application", + concurrency: [ + { + key: "event.data.serverId", + limit: 1, + }, + ], + retries: 0, + }, + { event: "deployment/requested" }, + + async ({ event, step }) => { + const jobData = event.data as DeployJob; + + return await step.run("execute-deployment", async () => { + logger.info("Deploying started"); + + try { + const result = await deploy(jobData); + logger.info("Deployment finished", result); + + // Send success event + await inngest.send({ + name: "deployment/completed", + data: { + ...jobData, + result, + status: "success", + }, + }); + + return result; + } catch (error) { + logger.error("Deployment failed", { jobData, error }); + + // Send failure event + await inngest.send({ + name: "deployment/failed", + data: { + ...jobData, + error: error instanceof Error ? error.message : String(error), + status: "failed", + }, + }); + + throw error; + } + }); + }, +); + app.use(async (c, next) => { - if (c.req.path === "/health") { + if (c.req.path === "/health" || c.req.path === "/api/inngest") { return next(); } + const authHeader = c.req.header("X-API-Key"); if (process.env.API_KEY !== authHeader) { @@ -26,36 +84,55 @@ app.use(async (c, next) => { return next(); }); -app.post("/deploy", zValidator("json", deployJobSchema), (c) => { +app.post("/deploy", zValidator("json", deployJobSchema), async (c) => { const data = c.req.valid("json"); - queue.add(data, { groupName: data.serverId }); - return c.json( - { - message: "Deployment Added", - }, - 200, - ); + logger.info("Received deployment request", data); + + try { + // Send event to Inngest instead of adding to Redis queue + await inngest.send({ + name: "deployment/requested", + data, + }); + + logger.info("Deployment event sent to Inngest", { + serverId: data.serverId, + }); + + return c.json( + { + message: "Deployment Added to Inngest Queue", + serverId: data.serverId, + }, + 200, + ); + } catch (error) { + console.log("error", error); + logger.error("Failed to send deployment event", error); + return c.json( + { + message: "Failed to queue deployment", + error: error instanceof Error ? error.message : String(error), + }, + 500, + ); + } }); app.get("/health", async (c) => { return c.json({ status: "ok" }); }); -const queue = new Queue({ - name: "deployments", - process: async (job: DeployJob) => { - logger.info("Deploying job", job); - return await deploy(job); - }, - redisClient, -}); - -(async () => { - await redisClient.connect(); - await redisClient.flushAll(); - logger.info("Redis Cleaned"); -})(); +// Serve Inngest functions endpoint +app.on( + ["GET", "POST", "PUT"], + "/api/inngest", + serveInngest({ + client: inngest, + functions: [deploymentFunction], + }), +); const port = Number.parseInt(process.env.PORT || "3000"); -logger.info("Starting Deployments Server ✅", port); +logger.info("Starting Deployments Server with Inngest ✅", port); serve({ fetch: app.fetch, port }); diff --git a/apps/api/src/utils.ts b/apps/api/src/utils.ts index 3f3c9698b..ee3943d34 100644 --- a/apps/api/src/utils.ts +++ b/apps/api/src/utils.ts @@ -64,7 +64,7 @@ export const deploy = async (job: DeployJob) => { } } } - } catch (_) { + } catch (e) { if (job.applicationType === "application") { await updateApplicationStatus(job.applicationId, "error"); } else if (job.applicationType === "compose") { @@ -76,6 +76,8 @@ export const deploy = async (job: DeployJob) => { previewStatus: "error", }); } + + throw e; } return true; diff --git a/apps/dokploy/__test__/drop/drop.test.ts b/apps/dokploy/__test__/drop/drop.test.ts index 79dd6c6cc..54a3fba4d 100644 --- a/apps/dokploy/__test__/drop/drop.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.ts @@ -27,6 +27,7 @@ if (typeof window === "undefined") { const baseApp: ApplicationNested = { railpackVersion: "0.2.2", applicationId: "", + previewLabels: [], herokuVersion: "", giteaBranch: "", giteaBuildPath: "", @@ -55,13 +56,21 @@ const baseApp: ApplicationNested = { previewPort: 3000, previewLimit: 0, previewWildcard: "", - project: { + environment: { env: "", - organizationId: "", + environmentId: "", name: "", - description: "", createdAt: "", + description: "", projectId: "", + project: { + env: "", + organizationId: "", + name: "", + description: "", + createdAt: "", + projectId: "", + }, }, buildArgs: null, buildPath: "/", @@ -91,6 +100,7 @@ const baseApp: ApplicationNested = { dockerfile: null, dockerImage: null, dropBuildPath: null, + environmentId: "", enabled: null, env: null, healthCheckSwarm: null, @@ -105,7 +115,6 @@ const baseApp: ApplicationNested = { password: null, placementSwarm: null, ports: [], - projectId: "", publishDirectory: null, isStaticSpa: null, redirects: [], diff --git a/apps/dokploy/__test__/env/environment.test.ts b/apps/dokploy/__test__/env/environment.test.ts new file mode 100644 index 000000000..95d46dcc0 --- /dev/null +++ b/apps/dokploy/__test__/env/environment.test.ts @@ -0,0 +1,335 @@ +import { prepareEnvironmentVariables } from "@dokploy/server/index"; +import { describe, expect, it } from "vitest"; + +const projectEnv = ` +ENVIRONMENT=staging +DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db +PORT=3000 +`; + +const environmentEnv = ` +NODE_ENV=development +API_URL=https://api.dev.example.com +REDIS_URL=redis://localhost:6379 +DATABASE_NAME=dev_database +SECRET_KEY=env-secret-123 +`; + +describe("prepareEnvironmentVariables (environment variables)", () => { + it("resolves environment variables correctly", () => { + const serviceWithEnvVars = ` +NODE_ENV=\${{environment.NODE_ENV}} +API_URL=\${{environment.API_URL}} +SERVICE_PORT=4000 +`; + + const resolved = prepareEnvironmentVariables( + serviceWithEnvVars, + "", + environmentEnv, + ); + + expect(resolved).toEqual([ + "NODE_ENV=development", + "API_URL=https://api.dev.example.com", + "SERVICE_PORT=4000", + ]); + }); + + it("resolves both project and environment variables", () => { + const serviceWithBoth = ` +ENVIRONMENT=\${{project.ENVIRONMENT}} +NODE_ENV=\${{environment.NODE_ENV}} +API_URL=\${{environment.API_URL}} +DATABASE_URL=\${{project.DATABASE_URL}} +SERVICE_PORT=4000 +`; + + const resolved = prepareEnvironmentVariables( + serviceWithBoth, + projectEnv, + environmentEnv, + ); + + expect(resolved).toEqual([ + "ENVIRONMENT=staging", + "NODE_ENV=development", + "API_URL=https://api.dev.example.com", + "DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db", + "SERVICE_PORT=4000", + ]); + }); + + it("handles undefined environment variables", () => { + const serviceWithUndefined = ` +UNDEFINED_VAR=\${{environment.UNDEFINED_VAR}} +`; + + expect(() => + prepareEnvironmentVariables(serviceWithUndefined, "", environmentEnv), + ).toThrow("Invalid environment variable: environment.UNDEFINED_VAR"); + }); + + it("allows service variables to override environment variables", () => { + const serviceOverrideEnv = ` +NODE_ENV=production +API_URL=\${{environment.API_URL}} +`; + + const resolved = prepareEnvironmentVariables( + serviceOverrideEnv, + "", + environmentEnv, + ); + + expect(resolved).toEqual([ + "NODE_ENV=production", // Overrides environment variable + "API_URL=https://api.dev.example.com", + ]); + }); + + it("resolves complex references with project, environment, and service variables", () => { + const complexServiceEnv = ` +FULL_DATABASE_URL=\${{project.DATABASE_URL}}/\${{environment.DATABASE_NAME}} +API_ENDPOINT=\${{environment.API_URL}}/\${{project.ENVIRONMENT}}/api +SERVICE_NAME=my-service +COMPLEX_VAR=\${{SERVICE_NAME}}-\${{environment.NODE_ENV}}-\${{project.ENVIRONMENT}} +`; + + const resolved = prepareEnvironmentVariables( + complexServiceEnv, + projectEnv, + environmentEnv, + ); + + expect(resolved).toEqual([ + "FULL_DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db/dev_database", + "API_ENDPOINT=https://api.dev.example.com/staging/api", + "SERVICE_NAME=my-service", + "COMPLEX_VAR=my-service-development-staging", + ]); + }); + + it("handles environment variables with special characters", () => { + const specialEnvVars = ` +SPECIAL_URL=https://special.com +COMPLEX_KEY="key-with-@#$%^&*()" +JWT_SECRET="secret-with-spaces and symbols!@#" +`; + + const serviceWithSpecial = ` +FULL_URL=\${{environment.SPECIAL_URL}}/path?key=\${{environment.COMPLEX_KEY}} +AUTH_SECRET=\${{environment.JWT_SECRET}} +`; + + const resolved = prepareEnvironmentVariables( + serviceWithSpecial, + "", + specialEnvVars, + ); + + expect(resolved).toEqual([ + "FULL_URL=https://special.com/path?key=key-with-@#$%^&*()", + "AUTH_SECRET=secret-with-spaces and symbols!@#", + ]); + }); + + it("maintains precedence: service > environment > project", () => { + const conflictingProjectEnv = ` +NODE_ENV=production-project +API_URL=https://project.api.com +DATABASE_NAME=project_db +`; + + const conflictingEnvironmentEnv = ` +NODE_ENV=development-environment +API_URL=https://environment.api.com +DATABASE_NAME=env_db +`; + + const serviceWithConflicts = ` +NODE_ENV=service-override +PROJECT_ENV=\${{project.NODE_ENV}} +ENV_VAR=\${{environment.API_URL}} +DB_NAME=\${{environment.DATABASE_NAME}} +`; + + const resolved = prepareEnvironmentVariables( + serviceWithConflicts, + conflictingProjectEnv, + conflictingEnvironmentEnv, + ); + + expect(resolved).toEqual([ + "NODE_ENV=service-override", // Service wins + "PROJECT_ENV=production-project", // Project reference + "ENV_VAR=https://environment.api.com", // Environment reference + "DB_NAME=env_db", // Environment reference + ]); + }); + + it("handles empty environment variables", () => { + const serviceWithEmpty = ` +SERVICE_VAR=test +PROJECT_VAR=\${{project.ENVIRONMENT}} +`; + + const resolved = prepareEnvironmentVariables( + serviceWithEmpty, + projectEnv, + "", + ); + + expect(resolved).toEqual(["SERVICE_VAR=test", "PROJECT_VAR=staging"]); + }); + + it("handles mixed quotes and environment variables", () => { + const envWithQuotes = ` +QUOTED_VAR="development" +SINGLE_QUOTED='https://api.dev.example.com' +MIXED_VAR="value with 'single' quotes" +`; + + const serviceWithQuotes = ` +NODE_ENV=\${{environment.QUOTED_VAR}} +API_URL=\${{environment.SINGLE_QUOTED}} +COMPLEX="Prefix-\${{environment.MIXED_VAR}}-Suffix" +`; + + const resolved = prepareEnvironmentVariables( + serviceWithQuotes, + "", + envWithQuotes, + ); + + expect(resolved).toEqual([ + "NODE_ENV=development", + "API_URL=https://api.dev.example.com", + "COMPLEX=Prefix-value with 'single' quotes-Suffix", + ]); + }); + + it("resolves multiple environment references in single value", () => { + const multiRefEnv = ` +HOST=localhost +PORT=5432 +USERNAME=postgres +PASSWORD=secret123 +`; + + const serviceWithMultiRefs = ` +DATABASE_URL=postgresql://\${{environment.USERNAME}}:\${{environment.PASSWORD}}@\${{environment.HOST}}:\${{environment.PORT}}/mydb +CONNECTION_STRING=\${{environment.HOST}}:\${{environment.PORT}} +`; + + const resolved = prepareEnvironmentVariables( + serviceWithMultiRefs, + "", + multiRefEnv, + ); + + expect(resolved).toEqual([ + "DATABASE_URL=postgresql://postgres:secret123@localhost:5432/mydb", + "CONNECTION_STRING=localhost:5432", + ]); + }); + + it("handles nested references with environment and project variables", () => { + const nestedProjectEnv = ` +BASE_DOMAIN=example.com +PROTOCOL=https +`; + + const nestedEnvironmentEnv = ` +SUBDOMAIN=api.dev +PATH_PREFIX=/v1 +`; + + const serviceWithNested = ` +FULL_URL=\${{project.PROTOCOL}}://\${{environment.SUBDOMAIN}}.\${{project.BASE_DOMAIN}}\${{environment.PATH_PREFIX}}/endpoint +API_BASE=\${{project.PROTOCOL}}://\${{environment.SUBDOMAIN}}.\${{project.BASE_DOMAIN}} +`; + + const resolved = prepareEnvironmentVariables( + serviceWithNested, + nestedProjectEnv, + nestedEnvironmentEnv, + ); + + expect(resolved).toEqual([ + "FULL_URL=https://api.dev.example.com/v1/endpoint", + "API_BASE=https://api.dev.example.com", + ]); + }); + + it("throws error for malformed environment variable references", () => { + const serviceWithMalformed = ` +MALFORMED1=\${{environment.}} +MALFORMED2=\${{environment}} +VALID=\${{environment.NODE_ENV}} +`; + + // Should throw error for empty variable name after environment. + expect(() => + prepareEnvironmentVariables(serviceWithMalformed, "", environmentEnv), + ).toThrow("Invalid environment variable: environment."); + }); + + it("handles environment variables with numeric values", () => { + const numericEnv = ` +PORT=8080 +TIMEOUT=30 +RETRY_COUNT=3 +PERCENTAGE=99.5 +`; + + const serviceWithNumeric = ` +SERVER_PORT=\${{environment.PORT}} +REQUEST_TIMEOUT=\${{environment.TIMEOUT}} +MAX_RETRIES=\${{environment.RETRY_COUNT}} +SUCCESS_RATE=\${{environment.PERCENTAGE}} +`; + + const resolved = prepareEnvironmentVariables( + serviceWithNumeric, + "", + numericEnv, + ); + + expect(resolved).toEqual([ + "SERVER_PORT=8080", + "REQUEST_TIMEOUT=30", + "MAX_RETRIES=3", + "SUCCESS_RATE=99.5", + ]); + }); + + it("handles boolean-like environment variables", () => { + const booleanEnv = ` +DEBUG=true +ENABLED=false +PRODUCTION=1 +DEVELOPMENT=0 +`; + + const serviceWithBoolean = ` +DEBUG_MODE=\${{environment.DEBUG}} +FEATURE_ENABLED=\${{environment.ENABLED}} +IS_PROD=\${{environment.PRODUCTION}} +IS_DEV=\${{environment.DEVELOPMENT}} +`; + + const resolved = prepareEnvironmentVariables( + serviceWithBoolean, + "", + booleanEnv, + ); + + expect(resolved).toEqual([ + "DEBUG_MODE=true", + "FEATURE_ENABLED=false", + "IS_PROD=1", + "IS_DEV=0", + ]); + }); +}); diff --git a/apps/dokploy/__test__/env/shared.test.ts b/apps/dokploy/__test__/env/shared.test.ts index 4a8448aa9..5e231a5cc 100644 --- a/apps/dokploy/__test__/env/shared.test.ts +++ b/apps/dokploy/__test__/env/shared.test.ts @@ -177,3 +177,77 @@ COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{project.APP_NAME}}'" ]); }); }); + +describe("prepareEnvironmentVariables (self references)", () => { + it("resolves self references correctly", () => { + const serviceEnv = ` +ENVIRONMENT=staging +DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db +SELF_REF=\${{ENVIRONMENT}} +`; + + const resolved = prepareEnvironmentVariables(serviceEnv, ""); + + expect(resolved).toEqual([ + "ENVIRONMENT=staging", + "DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db", + "SELF_REF=staging", + ]); + }); + + it("throws on undefined self references", () => { + const serviceEnv = ` +MISSING_VAR=\${{UNDEFINED_VAR}} +`; + + expect(() => prepareEnvironmentVariables(serviceEnv, "")).toThrow( + "Invalid service environment variable: UNDEFINED_VAR", + ); + }); + + it("allows overriding and still resolving from self", () => { + const serviceEnv = ` +ENVIRONMENT=production +OVERRIDE_ENV=\${{ENVIRONMENT}} +`; + + const resolved = prepareEnvironmentVariables(serviceEnv, ""); + + expect(resolved).toEqual([ + "ENVIRONMENT=production", + "OVERRIDE_ENV=production", + ]); + }); + + it("resolves multiple self references inside one value", () => { + const serviceEnv = ` +ENVIRONMENT=staging +APP_NAME=MyApp +COMPLEX=\${{APP_NAME}}-\${{ENVIRONMENT}}-\${{APP_NAME}} +`; + + const resolved = prepareEnvironmentVariables(serviceEnv, ""); + + expect(resolved).toEqual([ + "ENVIRONMENT=staging", + "APP_NAME=MyApp", + "COMPLEX=MyApp-staging-MyApp", + ]); + }); + + it("handles quotes with self references", () => { + const serviceEnv = ` +ENVIRONMENT=production +QUOTED="'\${{ENVIRONMENT}}'" +MIXED="\"Double \${{ENVIRONMENT}}\"" +`; + + const resolved = prepareEnvironmentVariables(serviceEnv, ""); + + expect(resolved).toEqual([ + "ENVIRONMENT=production", + "QUOTED='production'", + 'MIXED="Double production"', + ]); + }); +}); diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index bbedd286f..88c6c3b38 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -6,6 +6,7 @@ const baseApp: ApplicationNested = { railpackVersion: "0.2.2", rollbackActive: false, applicationId: "", + previewLabels: [], herokuVersion: "", giteaRepository: "", giteaOwner: "", @@ -35,13 +36,22 @@ const baseApp: ApplicationNested = { previewLimit: 0, previewCustomCertResolver: null, previewWildcard: "", - project: { + environmentId: "", + environment: { env: "", - organizationId: "", + environmentId: "", name: "", - description: "", createdAt: "", + description: "", projectId: "", + project: { + env: "", + organizationId: "", + name: "", + description: "", + createdAt: "", + projectId: "", + }, }, buildPath: "/", gitlabPathNamespace: "", @@ -84,7 +94,6 @@ const baseApp: ApplicationNested = { password: null, placementSwarm: null, ports: [], - projectId: "", publishDirectory: null, isStaticSpa: null, redirects: [], diff --git a/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx b/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx index 50e36ad76..1bf69394a 100644 --- a/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/general/add-command.tsx @@ -1,3 +1,8 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Card, @@ -16,11 +21,7 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; + interface Props { applicationId: string; } diff --git a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx index 29033f6b6..17d033cf2 100644 --- a/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/import/show-import.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { Code2, Globe2, HardDrive } from "lucide-react"; +import { useEffect, 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"; @@ -27,12 +33,6 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Code2, Globe2, HardDrive } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const ImportSchema = z.object({ base64: z.string(), diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx index 7d79b268a..816949f2b 100644 --- a/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/ports/show-port.tsx @@ -1,3 +1,5 @@ +import { Rss, Trash2 } from "lucide-react"; +import { toast } from "sonner"; import { AlertBlock } from "@/components/shared/alert-block"; import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; @@ -9,9 +11,8 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { Rss, Trash2 } from "lucide-react"; -import { toast } from "sonner"; import { HandlePorts } from "./handle-ports"; + interface Props { applicationId: string; } diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx index 253a8c24d..c4d38ef18 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/handle-redirect.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon, PlusIcon } from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -30,12 +36,6 @@ import { import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, PlusIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const AddRedirectchema = z.object({ regex: z.string().min(1, "Regex required"), diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx index 5c2c5943c..f1b14bfc0 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/show-redirects.tsx @@ -1,3 +1,5 @@ +import { Split, Trash2 } from "lucide-react"; +import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; import { Button } from "@/components/ui/button"; import { @@ -8,8 +10,6 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { Split, Trash2 } from "lucide-react"; -import { toast } from "sonner"; import { HandleRedirect } from "./handle-redirect"; interface Props { diff --git a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx index 2fc50c7c7..c52976eb1 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/handle-security.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon, PlusIcon } from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -19,12 +25,6 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, PlusIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const AddSecuritychema = z.object({ username: z.string().min(1, "Username is required"), diff --git a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx index 552a186a1..5676e6f00 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/show-security.tsx @@ -1,4 +1,7 @@ +import { LockKeyhole, Trash2 } from "lucide-react"; +import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; import { Card, @@ -7,12 +10,9 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { Label } from "@/components/ui/label"; -import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; -import { LockKeyhole, Trash2 } from "lucide-react"; -import { toast } from "sonner"; import { HandleSecurity } from "./handle-security"; interface Props { diff --git a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx index 3d26716fc..25040067b 100644 --- a/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/show-resources.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { InfoIcon } from "lucide-react"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -23,12 +29,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { InfoIcon } from "lucide-react"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const addResourcesSchema = z.object({ memoryReservation: z.string().optional(), diff --git a/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx b/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx index 58601fb49..ae23f1866 100644 --- a/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/traefik/show-traefik-config.tsx @@ -1,3 +1,4 @@ +import { File, Loader2 } from "lucide-react"; import { CodeEditor } from "@/components/shared/code-editor"; import { Card, @@ -7,8 +8,8 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { File, Loader2 } from "lucide-react"; import { UpdateTraefikConfig } from "./update-traefik-config"; + interface Props { applicationId: string; } diff --git a/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx b/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx index c73ed5b3d..73512837f 100644 --- a/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/traefik/update-traefik-config.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import jsyaml from "js-yaml"; +import { useEffect, 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"; @@ -19,12 +25,6 @@ import { FormMessage, } from "@/components/ui/form"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import jsyaml from "js-yaml"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const UpdateTraefikConfigSchema = z.object({ traefikConfig: z.string(), diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx index 84c864e3a..00be8a1e1 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/add-volumes.tsx @@ -1,3 +1,10 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PlusIcon } from "lucide-react"; +import type React from "react"; +import { useEffect, 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"; @@ -22,13 +29,7 @@ import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PlusIcon } from "lucide-react"; -import type React from "react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; + interface Props { serviceId: string; serviceType: diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx index 2a692f100..38d02ec90 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useEffect, 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"; @@ -20,12 +26,6 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const mountSchema = z.object({ mountPath: z.string().min(1, "Mount path required"), diff --git a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx index eb85f383b..e957a496c 100644 --- a/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/cancel-queues.tsx @@ -1,3 +1,5 @@ +import { Paintbrush } from "lucide-react"; +import { toast } from "sonner"; import { AlertDialog, AlertDialogAction, @@ -11,8 +13,6 @@ import { } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { api } from "@/utils/api"; -import { Paintbrush } from "lucide-react"; -import { toast } from "sonner"; interface Props { id: string; diff --git a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx index abfe37c3c..c49331ed7 100644 --- a/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/refresh-token.tsx @@ -1,3 +1,5 @@ +import { RefreshCcw } from "lucide-react"; +import { toast } from "sonner"; import { AlertDialog, AlertDialogAction, @@ -10,8 +12,6 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import { toast } from "sonner"; interface Props { id: string; diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx index 8733c745b..69c697721 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx @@ -1,3 +1,5 @@ +import { Loader2 } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { @@ -7,8 +9,6 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { Loader2 } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; import { TerminalLine } from "../../docker/logs/terminal-line"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments-modal.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments-modal.tsx index 4631a066e..7c937883e 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments-modal.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments-modal.tsx @@ -1,8 +1,7 @@ +import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; - import type { RouterOutputs } from "@/utils/api"; -import { useState } from "react"; import { ShowDeployment } from "../deployments/show-deployment"; import { ShowDeployments } from "./show-deployments"; diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx index 5790c129c..13694a283 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployments.tsx @@ -1,3 +1,6 @@ +import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react"; +import React, { useEffect, useState } from "react"; +import { toast } from "sonner"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { DialogAction } from "@/components/shared/dialog-action"; import { StatusTooltip } from "@/components/shared/status-tooltip"; @@ -10,10 +13,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; -import { type RouterOutputs, api } from "@/utils/api"; -import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react"; -import React, { useEffect, useState } from "react"; -import { toast } from "sonner"; +import { api, type RouterOutputs } from "@/utils/api"; import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings"; import { CancelQueues } from "./cancel-queues"; import { RefreshToken } from "./refresh-token"; @@ -77,7 +77,7 @@ export const ShowDeployments = ({
Deployments - See all the 10 last deployments for this {type} + See the last 10 deployments for this {type}
@@ -104,7 +104,9 @@ export const ShowDeployments = ({ Webhook URL:
- {`${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`} + {`${url}/api/deploy${ + type === "compose" ? "/compose" : "" + }/${refreshToken}`} {(type === "application" || type === "compose") && ( diff --git a/apps/dokploy/components/dashboard/application/domains/dns-helper-modal.tsx b/apps/dokploy/components/dashboard/application/domains/dns-helper-modal.tsx index c67c2fbfc..768ece858 100644 --- a/apps/dokploy/components/dashboard/application/domains/dns-helper-modal.tsx +++ b/apps/dokploy/components/dashboard/application/domains/dns-helper-modal.tsx @@ -1,3 +1,5 @@ +import { Copy, HelpCircle, Server } from "lucide-react"; +import { toast } from "sonner"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -8,8 +10,6 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; -import { Copy, HelpCircle, Server } from "lucide-react"; -import { toast } from "sonner"; interface Props { domain: { diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index 7bb58dfbe..1fd3d82e9 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -1,3 +1,18 @@ +import { + CheckCircle2, + ExternalLink, + GlobeIcon, + InfoIcon, + Loader2, + PenBoxIcon, + RefreshCw, + Server, + Trash2, + XCircle, +} from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -15,21 +30,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import { - CheckCircle2, - ExternalLink, - GlobeIcon, - InfoIcon, - Loader2, - PenBoxIcon, - RefreshCw, - Server, - Trash2, - XCircle, -} from "lucide-react"; -import Link from "next/link"; -import { useState } from "react"; -import { toast } from "sonner"; import { DnsHelperModal } from "./dns-helper-modal"; import { AddDomain } from "./handle-domain"; diff --git a/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx b/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx index 8a78c2745..4a5d0270b 100644 --- a/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx +++ b/apps/dokploy/components/dashboard/application/environment/show-enviroment.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { EyeIcon, EyeOffIcon } from "lucide-react"; +import { type CSSProperties, useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { CodeEditor } from "@/components/shared/code-editor"; import { Button } from "@/components/ui/button"; import { @@ -16,12 +22,6 @@ import { } from "@/components/ui/form"; import { Toggle } from "@/components/ui/toggle"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { EyeIcon, EyeOffIcon } from "lucide-react"; -import { type CSSProperties, useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; import type { ServiceType } from "../advanced/show-resources"; const addEnvironmentSchema = z.object({ diff --git a/apps/dokploy/components/dashboard/application/environment/show.tsx b/apps/dokploy/components/dashboard/application/environment/show.tsx index 6f504959c..78edb1aaa 100644 --- a/apps/dokploy/components/dashboard/application/environment/show.tsx +++ b/apps/dokploy/components/dashboard/application/environment/show.tsx @@ -1,13 +1,13 @@ -import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; -import { Form } from "@/components/ui/form"; -import { Secrets } from "@/components/ui/secrets"; -import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Form } from "@/components/ui/form"; +import { Secrets } from "@/components/ui/secrets"; +import { api } from "@/utils/api"; const addEnvironmentSchema = z.object({ env: z.string(), diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx index befc85957..6f6db5dd1 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-bitbucket-provider.tsx @@ -1,3 +1,10 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { BitbucketIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; import { Badge } from "@/components/ui/badge"; @@ -40,13 +47,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown, X } from "lucide-react"; -import Link from "next/link"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const BitbucketProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx index 72b2578c5..fcdcf0a93 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-docker-provider.tsx @@ -1,3 +1,8 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Form, @@ -9,11 +14,6 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const DockerProviderSchema = z.object({ dockerImage: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx index 3732860d4..00e18c2ab 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-drag-n-drop.tsx @@ -1,3 +1,8 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { TrashIcon } from "lucide-react"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dropzone } from "@/components/ui/dropzone"; import { @@ -11,11 +16,6 @@ import { import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { type UploadFile, uploadFileSchema } from "@/utils/schema"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { TrashIcon } from "lucide-react"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; interface Props { applicationId: string; diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx index f3e8116e6..61690e740 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-git-provider.tsx @@ -1,3 +1,13 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { KeyRoundIcon, LockIcon, X } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { GitIcon } from "@/components/icons/data-tools-icons"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Form, @@ -25,17 +35,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { KeyRoundIcon, LockIcon, X } from "lucide-react"; -import Link from "next/link"; -import { useRouter } from "next/router"; - -import { GitIcon } from "@/components/icons/data-tools-icons"; -import { Badge } from "@/components/ui/badge"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const GitProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx index 55fbfebda..2198f4a97 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitea-provider.tsx @@ -1,3 +1,10 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { GiteaIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; import { Badge } from "@/components/ui/badge"; @@ -40,13 +47,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; -import Link from "next/link"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; interface GiteaRepository { name: string; diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx index c76b9ae58..9a4b92ce1 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-github-provider.tsx @@ -1,3 +1,10 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { GithubIcon } from "@/components/icons/data-tools-icons"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -39,13 +46,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; -import Link from "next/link"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const GithubProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx index 2995e45f3..cb7209f8a 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/save-gitlab-provider.tsx @@ -1,3 +1,10 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; +import Link from "next/link"; +import { useEffect, useMemo } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { GitlabIcon } from "@/components/icons/data-tools-icons"; import { AlertBlock } from "@/components/shared/alert-block"; import { Badge } from "@/components/ui/badge"; @@ -40,13 +47,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react"; -import Link from "next/link"; -import { useEffect, useMemo } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const GitlabProviderSchema = z.object({ buildPath: z.string().min(1, "Path is required").default("/"), diff --git a/apps/dokploy/components/dashboard/application/general/generic/show.tsx b/apps/dokploy/components/dashboard/application/general/generic/show.tsx index 786c79e5c..a60db800c 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/show.tsx @@ -1,3 +1,7 @@ +import { GitBranch, Loader2, UploadCloud } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { toast } from "sonner"; import { SaveDockerProvider } from "@/components/dashboard/application/general/generic/save-docker-provider"; import { SaveGitProvider } from "@/components/dashboard/application/general/generic/save-git-provider"; import { SaveGiteaProvider } from "@/components/dashboard/application/general/generic/save-gitea-provider"; @@ -5,18 +9,14 @@ import { SaveGithubProvider } from "@/components/dashboard/application/general/g import { BitbucketIcon, DockerIcon, - GitIcon, GiteaIcon, GithubIcon, + GitIcon, GitlabIcon, } from "@/components/icons/data-tools-icons"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { api } from "@/utils/api"; -import { GitBranch, Loader2, UploadCloud } from "lucide-react"; -import Link from "next/link"; -import { useState } from "react"; -import { toast } from "sonner"; import { SaveBitbucketProvider } from "./save-bitbucket-provider"; import { SaveDragNDrop } from "./save-drag-n-drop"; import { SaveGitlabProvider } from "./save-gitlab-provider"; diff --git a/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx b/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx index 4dbdf7a69..de3fbff06 100644 --- a/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx +++ b/apps/dokploy/components/dashboard/application/general/generic/unauthorized-git-provider.tsx @@ -1,8 +1,9 @@ +import { AlertCircle, GitBranch, Unlink } from "lucide-react"; import { BitbucketIcon, - GitIcon, GiteaIcon, GithubIcon, + GitIcon, GitlabIcon, } from "@/components/icons/data-tools-icons"; import { DialogAction } from "@/components/shared/dialog-action"; @@ -10,7 +11,6 @@ import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import type { RouterOutputs } from "@/utils/api"; -import { AlertCircle, GitBranch, Unlink } from "lucide-react"; interface Props { service: diff --git a/apps/dokploy/components/dashboard/application/general/show.tsx b/apps/dokploy/components/dashboard/application/general/show.tsx index c917d7ab7..5387659ad 100644 --- a/apps/dokploy/components/dashboard/application/general/show.tsx +++ b/apps/dokploy/components/dashboard/application/general/show.tsx @@ -1,3 +1,14 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { + Ban, + CheckCircle2, + Hammer, + RefreshCcw, + Rocket, + Terminal, +} from "lucide-react"; +import { useRouter } from "next/router"; +import { toast } from "sonner"; import { ShowBuildChooseForm } from "@/components/dashboard/application/build/show"; import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show"; import { DialogAction } from "@/components/shared/dialog-action"; @@ -11,18 +22,8 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { - Ban, - CheckCircle2, - Hammer, - RefreshCcw, - Rocket, - Terminal, -} from "lucide-react"; -import { useRouter } from "next/router"; -import { toast } from "sonner"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; + interface Props { applicationId: string; } @@ -68,7 +69,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => { toast.success("Application deployed successfully"); refetch(); router.push( - `/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`, + `/dashboard/project/${data?.environment.projectId}/environment/${data?.environmentId}/services/application/${applicationId}?tab=deployments`, ); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/application/logs/show.tsx b/apps/dokploy/components/dashboard/application/logs/show.tsx index a73b99d25..e5dff075e 100644 --- a/apps/dokploy/components/dashboard/application/logs/show.tsx +++ b/apps/dokploy/components/dashboard/application/logs/show.tsx @@ -1,3 +1,6 @@ +import { Loader2 } from "lucide-react"; +import dynamic from "next/dynamic"; +import { useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Card, @@ -18,9 +21,6 @@ import { } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { Loader2 } from "lucide-react"; -import dynamic from "next/dynamic"; -import { useEffect, useState } from "react"; export const DockerLogs = dynamic( () => import("@/components/dashboard/docker/logs/docker-logs-id").then( diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/add-preview-domain.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/add-preview-domain.tsx index bb6f0e0a7..eac4559f1 100644 --- a/apps/dokploy/components/dashboard/application/preview-deployments/add-preview-domain.tsx +++ b/apps/dokploy/components/dashboard/application/preview-deployments/add-preview-domain.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { Dices } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import type z from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -33,15 +39,8 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { api } from "@/utils/api"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; - import { domain } from "@/server/db/validations/domain"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Dices } from "lucide-react"; -import type z from "zod"; +import { api } from "@/utils/api"; type Domain = z.infer; diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx index bf93af718..d93bbd1c8 100644 --- a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx +++ b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-deployments.tsx @@ -1,3 +1,13 @@ +import { + ExternalLink, + FileText, + GitPullRequest, + Loader2, + PenSquare, + RocketIcon, + Trash2, +} from "lucide-react"; +import { toast } from "sonner"; import { GithubIcon } from "@/components/icons/data-tools-icons"; import { DateTooltip } from "@/components/shared/date-tooltip"; import { DialogAction } from "@/components/shared/dialog-action"; @@ -13,16 +23,6 @@ import { } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { - ExternalLink, - FileText, - GitPullRequest, - Loader2, - PenSquare, - RocketIcon, - Trash2, -} from "lucide-react"; -import { toast } from "sonner"; import { ShowModalLogs } from "../../settings/web-server/show-modal-logs"; import { ShowDeploymentsModal } from "../deployments/show-deployments-modal"; import { AddPreviewDomain } from "./add-preview-domain"; diff --git a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx index a0f6ae0e4..16c916d93 100644 --- a/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx +++ b/apps/dokploy/components/dashboard/application/preview-deployments/show-preview-settings.tsx @@ -1,3 +1,10 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { HelpCircle, Plus, Settings2, X } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -27,13 +34,13 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Settings2 } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const schema = z .object({ @@ -42,6 +49,7 @@ const schema = z wildcardDomain: z.string(), port: z.number(), previewLimit: z.number(), + previewLabels: z.array(z.string()).optional(), previewHttps: z.boolean(), previewPath: z.string(), previewCertificateType: z.enum(["letsencrypt", "none", "custom"]), @@ -81,6 +89,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { wildcardDomain: "*.traefik.me", port: 3000, previewLimit: 3, + previewLabels: [], previewHttps: false, previewPath: "/", previewCertificateType: "none", @@ -102,6 +111,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { buildArgs: data.previewBuildArgs || "", wildcardDomain: data.previewWildcard || "*.traefik.me", port: data.previewPort || 3000, + previewLabels: data.previewLabels || [], previewLimit: data.previewLimit || 3, previewHttps: data.previewHttps || false, previewPath: data.previewPath || "/", @@ -119,6 +129,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { previewBuildArgs: formData.buildArgs, previewWildcard: formData.wildcardDomain, previewPort: formData.port, + previewLabels: formData.previewLabels, applicationId, previewLimit: formData.previewLimit, previewHttps: formData.previewHttps, @@ -200,6 +211,90 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => { )} /> + ( + +
+ Preview Labels + + + + + + +

+ Add a labels that will trigger a preview + deployment for a pull request. If no labels + are specified, all pull requests will trigger + a preview deployment. +

+
+
+
+
+
+ {field.value?.map((label, index) => ( + + {label} + { + const newLabels = [...(field.value || [])]; + newLabels.splice(index, 1); + field.onChange(newLabels); + }} + /> + + ))} +
+
+ + { + if (e.key === "Enter") { + e.preventDefault(); + const input = e.currentTarget; + const label = input.value.trim(); + if (label) { + field.onChange([ + ...(field.value || []), + label, + ]); + input.value = ""; + } + } + }} + /> + + +
+ +
+ )} + /> { return ( -
+
Scheduled Tasks @@ -91,15 +91,15 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => { return (
-
+
-
-

+
+

{schedule.name}

{ {schedule.enabled ? "Enabled" : "Disabled"}
-
+
{
-
+
{ })}
) : ( -
+

No scheduled tasks diff --git a/apps/dokploy/components/dashboard/application/update-application.tsx b/apps/dokploy/components/dashboard/application/update-application.tsx index 4d4190fa2..754074d75 100644 --- a/apps/dokploy/components/dashboard/application/update-application.tsx +++ b/apps/dokploy/components/dashboard/application/update-application.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -20,12 +26,6 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const updateApplicationSchema = z.object({ name: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/application/volume-backups/handle-volume-backups.tsx b/apps/dokploy/components/dashboard/application/volume-backups/handle-volume-backups.tsx index c66b05850..f00b91a9d 100644 --- a/apps/dokploy/components/dashboard/application/volume-backups/handle-volume-backups.tsx +++ b/apps/dokploy/components/dashboard/application/volume-backups/handle-volume-backups.tsx @@ -1,3 +1,15 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { + DatabaseZap, + Info, + PenBoxIcon, + PlusCircle, + RefreshCw, +} from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -34,18 +46,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { - DatabaseZap, - Info, - PenBoxIcon, - PlusCircle, - RefreshCw, -} from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; import type { CacheType } from "../domains/handle-domain"; import { commonCronExpressions } from "../schedules/handle-schedules"; @@ -55,7 +55,12 @@ const formSchema = z cronExpression: z.string().min(1, "Cron expression is required"), volumeName: z.string().min(1, "Volume name is required"), prefix: z.string(), - // keepLatestCount: z.coerce.number().optional(), + keepLatestCount: z.coerce + .number() + .int() + .gte(1, "Must be at least 1") + .optional() + .nullable(), turnOff: z.boolean().default(false), enabled: z.boolean().default(true), serviceType: z.enum([ @@ -108,6 +113,7 @@ export const HandleVolumeBackups = ({ }: Props) => { const [isOpen, setIsOpen] = useState(false); const [cacheType, setCacheType] = useState("cache"); + const [keepLatestCountInput, setKeepLatestCountInput] = useState(""); const utils = api.useUtils(); const form = useForm>({ @@ -117,7 +123,7 @@ export const HandleVolumeBackups = ({ cronExpression: "", volumeName: "", prefix: "", - // keepLatestCount: undefined, + keepLatestCount: undefined, turnOff: false, enabled: true, serviceName: "", @@ -173,13 +179,19 @@ export const HandleVolumeBackups = ({ cronExpression: volumeBackup.cronExpression, volumeName: volumeBackup.volumeName || "", prefix: volumeBackup.prefix, - // keepLatestCount: volumeBackup.keepLatestCount || undefined, + keepLatestCount: volumeBackup.keepLatestCount || undefined, turnOff: volumeBackup.turnOff, enabled: volumeBackup.enabled || false, serviceName: volumeBackup.serviceName || "", destinationId: volumeBackup.destinationId, serviceType: volumeBackup.serviceType, }); + setKeepLatestCountInput( + volumeBackup.keepLatestCount !== null && + volumeBackup.keepLatestCount !== undefined + ? String(volumeBackup.keepLatestCount) + : "", + ); } }, [form, volumeBackup, volumeBackupId]); @@ -190,8 +202,12 @@ export const HandleVolumeBackups = ({ const onSubmit = async (values: z.infer) => { if (!id && !volumeBackupId) return; + const preparedKeepLatestCount = + keepLatestCountInput === "" ? null : (values.keepLatestCount ?? null); + await mutateAsync({ ...values, + keepLatestCount: preparedKeepLatestCount, destinationId: values.destinationId, volumeBackupId: volumeBackupId || "", serviceType: volumeBackupType, @@ -257,9 +273,8 @@ export const HandleVolumeBackups = ({ @@ -600,29 +615,38 @@ export const HandleVolumeBackups = ({ )} /> - {/* ( - Keep Latest Count + Keep Latest Backups - field.onChange(Number(e.target.value) || undefined) - } + type="number" + min={1} + autoComplete="off" + placeholder="Leave empty to keep all" + value={keepLatestCountInput} + onChange={(e) => { + const raw = e.target.value; + setKeepLatestCountInput(raw); + if (raw === "") { + field.onChange(undefined); + } else if (/^\d+$/.test(raw)) { + field.onChange(Number(raw)); + } + }} /> - Number of backup files to keep (optional) + How many recent backups to keep. Empty means no cleanup. )} - /> */} + /> ; + +export const IsolatedDeploymentTab = ({ composeId }: Props) => { + const utils = api.useUtils(); + const [compose, setCompose] = useState(""); + const [isPreviewLoading, setIsPreviewLoading] = useState(false); + const { mutateAsync, error, isError } = + api.compose.isolatedDeployment.useMutation(); + + const [isOpenPreview, setIsOpenPreview] = useState(false); + + const { mutateAsync: updateCompose } = api.compose.update.useMutation(); + + const { data, refetch } = api.compose.one.useQuery( + { composeId }, + { enabled: !!composeId }, + ); + + const form = useForm({ + defaultValues: { + isolatedDeployment: false, + }, + resolver: zodResolver(isolatedSchema), + }); + + useEffect(() => { + if (data) { + form.reset({ + isolatedDeployment: data?.isolatedDeployment || false, + }); + } + }, [form, form.reset, form.formState.isSubmitSuccessful, data]); + + const onSubmit = async (formData: IsolatedSchema) => { + await updateCompose({ + composeId, + isolatedDeployment: formData?.isolatedDeployment || false, + }) + .then(async (_data) => { + await refetch(); + toast.success("Compose updated"); + }) + .catch(() => { + toast.error("Error updating the compose"); + }); + }; + + const generatePreview = async () => { + setIsOpenPreview(true); + setIsPreviewLoading(true); + try { + await mutateAsync({ + composeId, + suffix: data?.appName || "", + }).then(async (data) => { + await utils.project.all.invalidate(); + setCompose(data); + }); + } catch { + toast.error("Error generating preview"); + setIsOpenPreview(false); + } finally { + setIsPreviewLoading(false); + } + }; + + return ( + + + Enable Isolated Deployment + + Configure isolated deployment to the compose file. +

+ + This feature creates an isolated environment for your deployment + by adding unique prefixes to all resources. It establishes a + dedicated network based on your compose file's name, ensuring your + services run in isolation. This prevents conflicts when running + multiple instances of the same template or services with identical + names. + +
+
+

+ Resources that will be isolated: +

+
    +
  • Docker networks
  • +
+
+
+
+ + + +
+ {isError && {error?.message}} +
+ + {isError && ( +
+ + + {error?.message} + +
+ )} + +
+
+ ( + +
+ + Enable Isolated Deployment ({data?.appName}) + + + Enable isolated deployment to the compose file. + +
+ + + +
+ )} + /> +
+ +
+ +
+
+ +
+ + + + + Isolated Deployment Preview + + Preview of the compose file with isolated deployment + configuration + + +
+ {isPreviewLoading ? ( +
+ +

+ Generating compose preview... +

+
+ ) : ( +
+													
+												
+ )} +
+
+
+
+
+ +
+
+ + ); +}; diff --git a/apps/dokploy/components/dashboard/compose/delete-service.tsx b/apps/dokploy/components/dashboard/compose/delete-service.tsx index 65689afd1..e75aad5e5 100644 --- a/apps/dokploy/components/dashboard/compose/delete-service.tsx +++ b/apps/dokploy/components/dashboard/compose/delete-service.tsx @@ -1,3 +1,13 @@ +import type { ServiceType } from "@dokploy/server/db/schema"; +import { zodResolver } from "@hookform/resolvers/zod"; +import copy from "copy-to-clipboard"; +import { Copy, Trash2 } from "lucide-react"; +import { useRouter } from "next/router"; +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 { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; @@ -20,15 +30,6 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import type { ServiceType } from "@dokploy/server/db/schema"; -import { zodResolver } from "@hookform/resolvers/zod"; -import copy from "copy-to-clipboard"; -import { Copy, Trash2 } from "lucide-react"; -import { useRouter } from "next/router"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const deleteComposeSchema = z.object({ projectName: z.string().min(1, { @@ -100,7 +101,9 @@ export const DeleteService = ({ id, type }: Props) => { deleteVolumes, }) .then((result) => { - push(`/dashboard/project/${result?.projectId}`); + push( + `/dashboard/project/${result?.environment?.projectId}/environment/${result?.environment?.environmentId}`, + ); toast.success("deleted successfully"); setIsOpen(false); }) @@ -114,6 +117,12 @@ export const DeleteService = ({ id, type }: Props) => { } }; + const isDisabled = + (data && + "applicationStatus" in data && + data?.applicationStatus === "running") || + (data && "composeStatus" in data && data?.composeStatus === "running"); + return ( @@ -202,6 +211,12 @@ export const DeleteService = ({ id, type }: Props) => {
+ {isDisabled && ( + + Cannot delete the service while it is running. Please wait for the + build to finish and then try again. + + )} + -
-
-
- -
-							
-						
-
- - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx index 5ac67e0c8..2c488aefe 100644 --- a/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/randomize-compose.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { AlertTriangle } from "lucide-react"; +import { useEffect, 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"; @@ -18,12 +24,6 @@ import { import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { AlertTriangle } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; interface Props { composeId: string; diff --git a/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx index 253a5fde3..fac6c2a34 100644 --- a/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx @@ -62,7 +62,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => { {isError && {error?.message}} - + Preview your docker-compose file with added domains. Note: At least one domain must be specified for this conversion to take effect. diff --git a/apps/dokploy/components/dashboard/compose/general/show-utilities.tsx b/apps/dokploy/components/dashboard/compose/general/show-utilities.tsx deleted file mode 100644 index 6df800494..000000000 --- a/apps/dokploy/components/dashboard/compose/general/show-utilities.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useState } from "react"; -import { IsolatedDeployment } from "./isolated-deployment"; -import { RandomizeCompose } from "./randomize-compose"; - -interface Props { - composeId: string; -} - -export const ShowUtilities = ({ composeId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - return ( - - - - - - - Utilities - Modify the application data - - - - Isolated Deployment - Randomize Compose - - - - - - - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/compose/general/show.tsx b/apps/dokploy/components/dashboard/compose/general/show.tsx index 71752525c..4199363d8 100644 --- a/apps/dokploy/components/dashboard/compose/general/show.tsx +++ b/apps/dokploy/components/dashboard/compose/general/show.tsx @@ -9,6 +9,7 @@ import { import { api } from "@/utils/api"; import { ComposeActions } from "./actions"; import { ShowProviderFormCompose } from "./generic/show"; + interface Props { composeId: string; } diff --git a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx index d166f933f..4c004918b 100644 --- a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx +++ b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx @@ -1,3 +1,6 @@ +import { Loader2 } from "lucide-react"; +import dynamic from "next/dynamic"; +import { useEffect, useState } from "react"; import { badgeStateColor } from "@/components/dashboard/application/logs/show"; import { Badge } from "@/components/ui/badge"; import { @@ -19,9 +22,6 @@ import { } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; -import { Loader2 } from "lucide-react"; -import dynamic from "next/dynamic"; -import { useEffect, useState } from "react"; export const DockerLogs = dynamic( () => import("@/components/dashboard/docker/logs/docker-logs-id").then( diff --git a/apps/dokploy/components/dashboard/compose/logs/show.tsx b/apps/dokploy/components/dashboard/compose/logs/show.tsx index 571190549..a4551f415 100644 --- a/apps/dokploy/components/dashboard/compose/logs/show.tsx +++ b/apps/dokploy/components/dashboard/compose/logs/show.tsx @@ -1,3 +1,6 @@ +import { Loader2 } from "lucide-react"; +import dynamic from "next/dynamic"; +import { useEffect, useState } from "react"; import { badgeStateColor } from "@/components/dashboard/application/logs/show"; import { Badge } from "@/components/ui/badge"; import { @@ -18,9 +21,6 @@ import { SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; -import { Loader2 } from "lucide-react"; -import dynamic from "next/dynamic"; -import { useEffect, useState } from "react"; export const DockerLogs = dynamic( () => import("@/components/dashboard/docker/logs/docker-logs-id").then( diff --git a/apps/dokploy/components/dashboard/compose/update-compose.tsx b/apps/dokploy/components/dashboard/compose/update-compose.tsx index f9c38a6bc..7564988e2 100644 --- a/apps/dokploy/components/dashboard/compose/update-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/update-compose.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -20,12 +26,6 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const updateComposeSchema = z.object({ name: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/database/backups/handle-backup.tsx b/apps/dokploy/components/dashboard/database/backups/handle-backup.tsx index 4c5bbe628..a0449a155 100644 --- a/apps/dokploy/components/dashboard/database/backups/handle-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/handle-backup.tsx @@ -1,3 +1,17 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { + CheckIcon, + ChevronsUpDown, + DatabaseZap, + Info, + PenBoxIcon, + PlusIcon, + RefreshCw, +} from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -48,19 +62,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { - DatabaseZap, - Info, - PenBoxIcon, - PlusIcon, - RefreshCw, -} from "lucide-react"; -import { CheckIcon, ChevronsUpDown } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; import { commonCronExpressions } from "../../application/schedules/handle-schedules"; type CacheType = "cache" | "fetch"; diff --git a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx index a173f85ad..6a0fb030a 100644 --- a/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx +++ b/apps/dokploy/components/dashboard/database/backups/restore-backup.tsx @@ -1,3 +1,18 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import copy from "copy-to-clipboard"; +import { debounce } from "lodash"; +import { + CheckIcon, + ChevronsUpDown, + Copy, + DatabaseZap, + RefreshCw, + RotateCcw, +} from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { DrawerLogs } from "@/components/shared/drawer-logs"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -47,21 +62,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import copy from "copy-to-clipboard"; -import { debounce } from "lodash"; -import { - CheckIcon, - ChevronsUpDown, - Copy, - DatabaseZap, - RefreshCw, - RotateCcw, -} from "lucide-react"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; import type { ServiceType } from "../../application/advanced/show-resources"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; diff --git a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx index 28ee68a9c..55a09b25f 100644 --- a/apps/dokploy/components/dashboard/database/backups/show-backups.tsx +++ b/apps/dokploy/components/dashboard/database/backups/show-backups.tsx @@ -1,3 +1,13 @@ +import { + ClipboardList, + Database, + DatabaseBackup, + Play, + Trash2, +} from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { toast } from "sonner"; import { MariadbIcon, MongodbIcon, @@ -22,16 +32,6 @@ import { } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import { - ClipboardList, - Database, - DatabaseBackup, - Play, - Trash2, -} from "lucide-react"; -import Link from "next/link"; -import { useState } from "react"; -import { toast } from "sonner"; import type { ServiceType } from "../../application/advanced/show-resources"; import { ShowDeploymentsModal } from "../../application/deployments/show-deployments-modal"; import { HandleBackup } from "./handle-backup"; diff --git a/apps/dokploy/components/dashboard/docker/logs/line-count-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/line-count-filter.tsx index dd7b63af5..906c65a0c 100644 --- a/apps/dokploy/components/dashboard/docker/logs/line-count-filter.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/line-count-filter.tsx @@ -1,3 +1,7 @@ +import { Command as CommandPrimitive } from "cmdk"; +import { debounce } from "lodash"; +import { CheckIcon, Hash } from "lucide-react"; +import React, { useCallback, useRef } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -7,10 +11,6 @@ import { } from "@/components/ui/popover"; import { Separator } from "@/components/ui/separator"; import { cn } from "@/lib/utils"; -import { Command as CommandPrimitive } from "cmdk"; -import { debounce } from "lodash"; -import { CheckIcon, Hash } from "lucide-react"; -import React, { useCallback, useRef } from "react"; const lineCountOptions = [ { label: "100 lines", value: 100 }, diff --git a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx index fc2fb8f67..2c1c25bba 100644 --- a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-logs.tsx @@ -1,3 +1,5 @@ +import dynamic from "next/dynamic"; +import type React from "react"; import { Dialog, DialogContent, @@ -7,8 +9,6 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import dynamic from "next/dynamic"; -import type React from "react"; export const DockerLogsId = dynamic( () => import("@/components/dashboard/docker/logs/docker-logs-id").then( diff --git a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-stack-logs.tsx b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-stack-logs.tsx index 669369348..0399e2c67 100644 --- a/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-stack-logs.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/show-docker-modal-stack-logs.tsx @@ -1,3 +1,5 @@ +import dynamic from "next/dynamic"; +import type React from "react"; import { Dialog, DialogContent, @@ -7,8 +9,6 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import dynamic from "next/dynamic"; -import type React from "react"; export const DockerLogsId = dynamic( () => import("@/components/dashboard/docker/logs/docker-logs-id").then( diff --git a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx index 44f2cdfc3..986a19059 100644 --- a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx @@ -1,3 +1,4 @@ +import { CheckIcon } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -14,7 +15,6 @@ import { import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import { cn } from "@/lib/utils"; -import { CheckIcon } from "lucide-react"; export type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; diff --git a/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx index 3ef11517a..22c1ed648 100644 --- a/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx @@ -1,3 +1,5 @@ +import { CheckIcon } from "lucide-react"; +import type React from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -13,8 +15,6 @@ import { } from "@/components/ui/popover"; import { Separator } from "@/components/ui/separator"; import { cn } from "@/lib/utils"; -import { CheckIcon } from "lucide-react"; -import type React from "react"; interface StatusLogsFilterProps { value?: string[]; diff --git a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx index 48ec4557b..5b929f3b6 100644 --- a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx @@ -1,3 +1,5 @@ +import { FancyAnsi } from "fancy-ansi"; +import { escapeRegExp } from "lodash"; import { Badge } from "@/components/ui/badge"; import { Tooltip, @@ -7,9 +9,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; -import { FancyAnsi } from "fancy-ansi"; -import { escapeRegExp } from "lodash"; -import { type LogLine, getLogType } from "./utils"; +import { getLogType, type LogLine } from "./utils"; interface LogLineProps { log: LogLine; diff --git a/apps/dokploy/components/dashboard/docker/show/colums.tsx b/apps/dokploy/components/dashboard/docker/show/colums.tsx index 1cf0200f2..74fe6819e 100644 --- a/apps/dokploy/components/dashboard/docker/show/colums.tsx +++ b/apps/dokploy/components/dashboard/docker/show/colums.tsx @@ -1,6 +1,6 @@ import type { ColumnDef } from "@tanstack/react-table"; import { ArrowUpDown, MoreHorizontal } from "lucide-react"; - +import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -8,8 +8,6 @@ import { DropdownMenuLabel, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; - -import { Badge } from "@/components/ui/badge"; import { ShowContainerConfig } from "../config/show-container-config"; import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs"; import { DockerTerminalModal } from "../terminal/docker-terminal-modal"; diff --git a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx index 024b00618..52398aabe 100644 --- a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx +++ b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx @@ -1,3 +1,16 @@ +import { + type ColumnFiltersState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + type SortingState, + useReactTable, + type VisibilityState, +} from "@tanstack/react-table"; +import { ChevronDown, Container } from "lucide-react"; +import * as React from "react"; import { Button } from "@/components/ui/button"; import { Card, @@ -21,20 +34,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { type RouterOutputs, api } from "@/utils/api"; -import { - type ColumnFiltersState, - type SortingState, - type VisibilityState, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; -import { ChevronDown, Container } from "lucide-react"; -import * as React from "react"; +import { api, type RouterOutputs } from "@/utils/api"; import { columns } from "./colums"; export type Container = NonNullable< RouterOutputs["docker"]["getContainers"] diff --git a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx index 97d9f16e8..62c1347e4 100644 --- a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx +++ b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal-modal.tsx @@ -1,3 +1,5 @@ +import dynamic from "next/dynamic"; +import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -9,8 +11,6 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import dynamic from "next/dynamic"; -import { useState } from "react"; const Terminal = dynamic( () => import("./docker-terminal").then((e) => e.DockerTerminal), diff --git a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx index bf14680a4..ad34d69ce 100644 --- a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx +++ b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx @@ -2,9 +2,9 @@ import { Terminal } from "@xterm/xterm"; import React, { useEffect, useRef } from "react"; import { FitAddon } from "xterm-addon-fit"; import "@xterm/xterm/css/xterm.css"; -import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AttachAddon } from "@xterm/addon-attach"; import { useTheme } from "next-themes"; +import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; interface Props { id: string; diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx index fb5fe8f5c..8c848a0dc 100644 --- a/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx +++ b/apps/dokploy/components/dashboard/file-system/show-traefik-file.tsx @@ -1,7 +1,12 @@ -import { Button } from "@/components/ui/button"; - +import { zodResolver } from "@hookform/resolvers/zod"; +import { Loader2 } from "lucide-react"; +import { useEffect, 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 { Form, FormControl, @@ -12,12 +17,6 @@ import { FormMessage, } from "@/components/ui/form"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Loader2 } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; import { validateAndFormatYAML } from "../application/advanced/traefik/update-traefik-config"; const UpdateServerMiddlewareConfigSchema = z.object({ diff --git a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx index c9272f293..94a5c72a6 100644 --- a/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx +++ b/apps/dokploy/components/dashboard/file-system/show-traefik-system.tsx @@ -1,3 +1,5 @@ +import { FileIcon, Folder, Loader2, Workflow } from "lucide-react"; +import React from "react"; import { AlertBlock } from "@/components/shared/alert-block"; import { Card, @@ -8,8 +10,6 @@ import { } from "@/components/ui/card"; import { Tree } from "@/components/ui/file-tree"; import { api } from "@/utils/api"; -import { FileIcon, Folder, Loader2, Workflow } from "lucide-react"; -import React from "react"; import { ShowTraefikFile } from "./show-traefik-file"; interface Props { diff --git a/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx b/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx index 8a9f55c90..7804e9add 100644 --- a/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx +++ b/apps/dokploy/components/dashboard/impersonation/impersonation-bar.tsx @@ -1,5 +1,24 @@ "use client"; +import copy from "copy-to-clipboard"; +import { format } from "date-fns"; +import { + Building2, + Calendar, + CheckIcon, + ChevronsUpDown, + Copy, + CreditCard, + Fingerprint, + Key, + Server, + Settings2, + Shield, + UserIcon, + XIcon, +} from "lucide-react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { Logo } from "@/components/shared/logo"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; @@ -26,25 +45,6 @@ import { import { authClient } from "@/lib/auth-client"; import { cn } from "@/lib/utils"; import { api } from "@/utils/api"; -import copy from "copy-to-clipboard"; -import { format } from "date-fns"; -import { - Building2, - Calendar, - CheckIcon, - ChevronsUpDown, - Copy, - CreditCard, - Fingerprint, - Key, - Server, - Settings2, - Shield, - UserIcon, - XIcon, -} from "lucide-react"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; type User = typeof authClient.$Infer.Session.user; diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx index c00af42be..0b31d4962 100644 --- a/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx +++ b/apps/dokploy/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import Link from "next/link"; +import { useEffect, 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 { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; @@ -19,12 +25,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const DockerProviderSchema = z.object({ externalPort: z.preprocess((a) => { diff --git a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx index 2f8bab77b..8e996846f 100644 --- a/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx +++ b/apps/dokploy/components/dashboard/mariadb/general/show-general-mariadb.tsx @@ -1,3 +1,7 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; import { DrawerLogs } from "@/components/shared/drawer-logs"; import { Button } from "@/components/ui/button"; @@ -9,10 +13,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; diff --git a/apps/dokploy/components/dashboard/mariadb/update-mariadb.tsx b/apps/dokploy/components/dashboard/mariadb/update-mariadb.tsx index 9d29d1ac4..62486e015 100644 --- a/apps/dokploy/components/dashboard/mariadb/update-mariadb.tsx +++ b/apps/dokploy/components/dashboard/mariadb/update-mariadb.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -20,12 +26,6 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon } from "lucide-react"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const updateMariadbSchema = z.object({ name: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx index 75772bfdf..51765de9f 100644 --- a/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx +++ b/apps/dokploy/components/dashboard/mongo/general/show-external-mongo-credentials.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import Link from "next/link"; +import { useEffect, 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 { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; @@ -19,12 +25,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const DockerProviderSchema = z.object({ externalPort: z.preprocess((a) => { diff --git a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx index fdc28adc3..23fbe51d3 100644 --- a/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx +++ b/apps/dokploy/components/dashboard/mongo/general/show-general-mongo.tsx @@ -1,3 +1,7 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; import { DrawerLogs } from "@/components/shared/drawer-logs"; import { Button } from "@/components/ui/button"; @@ -9,12 +13,9 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; + interface Props { mongoId: string; } diff --git a/apps/dokploy/components/dashboard/mongo/update-mongo.tsx b/apps/dokploy/components/dashboard/mongo/update-mongo.tsx index 48dbcf4d7..e78abddbd 100644 --- a/apps/dokploy/components/dashboard/mongo/update-mongo.tsx +++ b/apps/dokploy/components/dashboard/mongo/update-mongo.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -20,12 +26,6 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const updateMongoSchema = z.object({ name: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/monitoring/free/container/docker-memory-chart.tsx b/apps/dokploy/components/dashboard/monitoring/free/container/docker-memory-chart.tsx index 82a1ff3d5..34a3913a7 100644 --- a/apps/dokploy/components/dashboard/monitoring/free/container/docker-memory-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/free/container/docker-memory-chart.tsx @@ -10,6 +10,7 @@ import { } from "recharts"; import type { DockerStatsJSON } from "./show-free-container-monitoring"; import { convertMemoryToBytes } from "./show-free-container-monitoring"; + interface Props { acummulativeData: DockerStatsJSON["memory"]; memoryLimitGB: number; diff --git a/apps/dokploy/components/dashboard/monitoring/free/container/docker-network-chart.tsx b/apps/dokploy/components/dashboard/monitoring/free/container/docker-network-chart.tsx index cd6b7dfde..5e2414cea 100644 --- a/apps/dokploy/components/dashboard/monitoring/free/container/docker-network-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/free/container/docker-network-chart.tsx @@ -9,6 +9,7 @@ import { YAxis, } from "recharts"; import type { DockerStatsJSON } from "./show-free-container-monitoring"; + interface Props { acummulativeData: DockerStatsJSON["network"]; } diff --git a/apps/dokploy/components/dashboard/monitoring/free/container/show-free-compose-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/free/container/show-free-compose-monitoring.tsx index 84510154c..246ae296d 100644 --- a/apps/dokploy/components/dashboard/monitoring/free/container/show-free-compose-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/free/container/show-free-compose-monitoring.tsx @@ -1,3 +1,6 @@ +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { badgeStateColor } from "@/components/dashboard/application/logs/show"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -18,9 +21,6 @@ import { SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; -import { Loader2 } from "lucide-react"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; import { ContainerFreeMonitoring } from "./show-free-container-monitoring"; interface Props { diff --git a/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx index 117fae388..b28c4d9b6 100644 --- a/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/free/container/show-free-container-monitoring.tsx @@ -1,7 +1,7 @@ +import { useEffect, useState } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { api } from "@/utils/api"; -import { useEffect, useState } from "react"; import { DockerBlockChart } from "./docker-block-chart"; import { DockerCpuChart } from "./docker-cpu-chart"; import { DockerDiskChart } from "./docker-disk-chart"; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx index 12af6b91d..32e30f62a 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/container-block-chart.tsx @@ -1,3 +1,4 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Card, CardContent, @@ -13,7 +14,6 @@ import { ChartTooltip, } from "@/components/ui/chart"; import { formatTimestamp } from "@/lib/utils"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; interface ContainerMetric { timestamp: string; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/container-cpu-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/container-cpu-chart.tsx index 445e03e12..76b010c7c 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/container-cpu-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/container-cpu-chart.tsx @@ -1,3 +1,4 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Card, CardContent, @@ -13,7 +14,6 @@ import { ChartTooltip, } from "@/components/ui/chart"; import { formatTimestamp } from "@/lib/utils"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; interface ContainerMetric { timestamp: string; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/container-memory-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/container-memory-chart.tsx index 4da864285..ff5e85843 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/container-memory-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/container-memory-chart.tsx @@ -1,3 +1,4 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Card, CardContent, @@ -13,7 +14,6 @@ import { ChartTooltip, } from "@/components/ui/chart"; import { formatTimestamp } from "@/lib/utils"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; interface ContainerMetric { timestamp: string; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/container-network-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/container-network-chart.tsx index d51e89687..f962e2ae3 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/container-network-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/container-network-chart.tsx @@ -1,3 +1,4 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Card, CardContent, @@ -13,7 +14,6 @@ import { ChartTooltip, } from "@/components/ui/chart"; import { formatTimestamp } from "@/lib/utils"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; interface ContainerMetric { timestamp: string; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring.tsx index 4ca461c21..026043806 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-compose-monitoring.tsx @@ -1,3 +1,6 @@ +import { Loader2 } from "lucide-react"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { badgeStateColor } from "@/components/dashboard/application/logs/show"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -19,9 +22,6 @@ import { SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; -import { Loader2 } from "lucide-react"; -import { useEffect, useState } from "react"; -import { toast } from "sonner"; import { ContainerPaidMonitoring } from "./show-paid-container-monitoring"; interface Props { diff --git a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx index c9cefa4c3..db087afa0 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/container/show-paid-container-monitoring.tsx @@ -1,3 +1,5 @@ +import { Cpu, HardDrive, Loader2, MemoryStick, Network } from "lucide-react"; +import { useEffect, useState } from "react"; import { Card } from "@/components/ui/card"; import { Select, @@ -7,8 +9,6 @@ import { SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; -import { Cpu, HardDrive, Loader2, MemoryStick, Network } from "lucide-react"; -import { useEffect, useState } from "react"; import { ContainerBlockChart } from "./container-block-chart"; import { ContainerCPUChart } from "./container-cpu-chart"; import { ContainerMemoryChart } from "./container-memory-chart"; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/cpu-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/cpu-chart.tsx index 8c9602ee2..efa84ffc4 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/cpu-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/cpu-chart.tsx @@ -1,3 +1,4 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Card, CardContent, @@ -13,7 +14,6 @@ import { ChartTooltip, } from "@/components/ui/chart"; import { formatTimestamp } from "@/lib/utils"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; interface CPUChartProps { data: any[]; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/memory-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/memory-chart.tsx index f4079c46d..1981dace3 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/memory-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/memory-chart.tsx @@ -1,3 +1,4 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Card, CardContent, @@ -11,7 +12,6 @@ import { ChartTooltip, } from "@/components/ui/chart"; import { formatTimestamp } from "@/lib/utils"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; interface MemoryChartProps { data: any[]; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/network-chart.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/network-chart.tsx index b84af0952..bbb522fdc 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/network-chart.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/network-chart.tsx @@ -1,3 +1,4 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Card, CardContent, @@ -13,7 +14,6 @@ import { ChartTooltip, } from "@/components/ui/chart"; import { formatTimestamp } from "@/lib/utils"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; interface NetworkChartProps { data: any[]; diff --git a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx index 492abc9e0..af0dacc1d 100644 --- a/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx +++ b/apps/dokploy/components/dashboard/monitoring/paid/servers/show-paid-monitoring.tsx @@ -1,3 +1,5 @@ +import { Clock, Cpu, HardDrive, Loader2, MemoryStick } from "lucide-react"; +import { useEffect, useState } from "react"; import { Select, SelectContent, @@ -6,8 +8,6 @@ import { SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; -import { Clock, Cpu, HardDrive, Loader2, MemoryStick } from "lucide-react"; -import { useEffect, useState } from "react"; import { CPUChart } from "./cpu-chart"; import { DiskChart } from "./disk-chart"; import { MemoryChart } from "./memory-chart"; diff --git a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx index 73f99b7d0..a767b70c0 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-external-mysql-credentials.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import Link from "next/link"; +import { useEffect, 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 { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; @@ -19,12 +25,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const DockerProviderSchema = z.object({ externalPort: z.preprocess((a) => { diff --git a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx index 590127fa7..045a717b7 100644 --- a/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx +++ b/apps/dokploy/components/dashboard/mysql/general/show-general-mysql.tsx @@ -1,3 +1,7 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; import { DrawerLogs } from "@/components/shared/drawer-logs"; import { Button } from "@/components/ui/button"; @@ -9,12 +13,9 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; + interface Props { mysqlId: string; } diff --git a/apps/dokploy/components/dashboard/mysql/update-mysql.tsx b/apps/dokploy/components/dashboard/mysql/update-mysql.tsx index 9b1296478..353523aa0 100644 --- a/apps/dokploy/components/dashboard/mysql/update-mysql.tsx +++ b/apps/dokploy/components/dashboard/mysql/update-mysql.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon } from "lucide-react"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { @@ -20,12 +26,6 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon } from "lucide-react"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const updateMysqlSchema = z.object({ name: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/organization/handle-organization.tsx b/apps/dokploy/components/dashboard/organization/handle-organization.tsx index 394f3d018..c676e0233 100644 --- a/apps/dokploy/components/dashboard/organization/handle-organization.tsx +++ b/apps/dokploy/components/dashboard/organization/handle-organization.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBoxIcon, Plus } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -18,13 +24,8 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { authClient } from "@/lib/auth-client"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBoxIcon, Plus } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const organizationSchema = z.object({ name: z.string().min(1, { @@ -54,6 +55,8 @@ export function AddOrganization({ organizationId }: Props) { const { mutateAsync, isLoading } = organizationId ? api.organization.update.useMutation() : api.organization.create.useMutation(); + const { refetch: refetchActiveOrganization } = + authClient.useActiveOrganization(); const form = useForm({ resolver: zodResolver(organizationSchema), @@ -84,6 +87,10 @@ export function AddOrganization({ organizationId }: Props) { `Organization ${organizationId ? "updated" : "created"} successfully`, ); utils.organization.all.invalidate(); + if (organizationId) { + utils.organization.one.invalidate({ organizationId }); + refetchActiveOrganization(); + } setOpen(false); }) .catch((error) => { diff --git a/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx b/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx index 40e84844f..febaa8644 100644 --- a/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx +++ b/apps/dokploy/components/dashboard/postgres/advanced/show-custom-command.tsx @@ -1,3 +1,8 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { @@ -10,11 +15,6 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; import type { ServiceType } from "../../application/advanced/show-resources"; const addDockerImage = z.object({ diff --git a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx index 444fa0cee..1e4842d13 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-external-postgres-credentials.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import Link from "next/link"; +import { useEffect, 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 { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; @@ -19,12 +25,6 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const DockerProviderSchema = z.object({ externalPort: z.preprocess((a) => { diff --git a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx index fec51b5a2..de520053d 100644 --- a/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx +++ b/apps/dokploy/components/dashboard/postgres/general/show-general-postgres.tsx @@ -1,3 +1,7 @@ +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; import { DialogAction } from "@/components/shared/dialog-action"; import { DrawerLogs } from "@/components/shared/drawer-logs"; import { Button } from "@/components/ui/button"; @@ -9,10 +13,6 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { api } from "@/utils/api"; -import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { Ban, CheckCircle2, RefreshCcw, Rocket, Terminal } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; import { type LogLine, parseLogs } from "../../docker/logs/utils"; import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal"; diff --git a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx index 2695953cd..d4485862e 100644 --- a/apps/dokploy/components/dashboard/postgres/update-postgres.tsx +++ b/apps/dokploy/components/dashboard/postgres/update-postgres.tsx @@ -1,3 +1,9 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { PenBox } from "lucide-react"; +import { useEffect, 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 { Button } from "@/components/ui/button"; import { @@ -20,12 +26,6 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { PenBox } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; const updatePostgresSchema = z.object({ name: z.string().min(1, { diff --git a/apps/dokploy/components/dashboard/project/add-ai-assistant.tsx b/apps/dokploy/components/dashboard/project/add-ai-assistant.tsx index 2bb47618e..88fd1d111 100644 --- a/apps/dokploy/components/dashboard/project/add-ai-assistant.tsx +++ b/apps/dokploy/components/dashboard/project/add-ai-assistant.tsx @@ -1,10 +1,10 @@ import { TemplateGenerator } from "@/components/dashboard/project/ai/template-generator"; interface Props { - projectId: string; + environmentId: string; projectName?: string; } -export const AddAiAssistant = ({ projectId }: Props) => { - return ; +export const AddAiAssistant = ({ environmentId }: Props) => { + return ; }; diff --git a/apps/dokploy/components/dashboard/project/add-application.tsx b/apps/dokploy/components/dashboard/project/add-application.tsx index 137f75a51..dd4effb0f 100644 --- a/apps/dokploy/components/dashboard/project/add-application.tsx +++ b/apps/dokploy/components/dashboard/project/add-application.tsx @@ -64,11 +64,11 @@ const AddTemplateSchema = z.object({ type AddTemplate = z.infer; interface Props { - projectId: string; + environmentId: string; projectName?: string; } -export const AddApplication = ({ projectId, projectName }: Props) => { +export const AddApplication = ({ environmentId, projectName }: Props) => { const utils = api.useUtils(); const { data: isCloud } = api.settings.isCloud.useQuery(); const [visible, setVisible] = useState(false); @@ -94,15 +94,15 @@ export const AddApplication = ({ projectId, projectName }: Props) => { name: data.name, appName: data.appName, description: data.description, - projectId, + environmentId, serverId: data.serverId, }) .then(async () => { toast.success("Service Created"); form.reset(); setVisible(false); - await utils.project.one.invalidate({ - projectId, + await utils.environment.one.invalidate({ + environmentId, }); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/project/add-compose.tsx b/apps/dokploy/components/dashboard/project/add-compose.tsx index c32e55c16..d565b5bfd 100644 --- a/apps/dokploy/components/dashboard/project/add-compose.tsx +++ b/apps/dokploy/components/dashboard/project/add-compose.tsx @@ -65,11 +65,11 @@ const AddComposeSchema = z.object({ type AddCompose = z.infer; interface Props { - projectId: string; + environmentId: string; projectName?: string; } -export const AddCompose = ({ projectId, projectName }: Props) => { +export const AddCompose = ({ environmentId, projectName }: Props) => { const utils = api.useUtils(); const [visible, setVisible] = useState(false); const slug = slugify(projectName); @@ -78,6 +78,9 @@ export const AddCompose = ({ projectId, projectName }: Props) => { const { mutateAsync, isLoading, error, isError } = api.compose.create.useMutation(); + // Get environment data to extract projectId + const { data: environment } = api.environment.one.useQuery({ environmentId }); + const hasServers = servers && servers.length > 0; const form = useForm({ @@ -98,7 +101,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => { await mutateAsync({ name: data.name, description: data.description, - projectId, + environmentId, composeType: data.composeType, appName: data.appName, serverId: data.serverId, @@ -106,8 +109,9 @@ export const AddCompose = ({ projectId, projectName }: Props) => { .then(async () => { toast.success("Compose Created"); setVisible(false); - await utils.project.one.invalidate({ - projectId, + // Invalidate the project query to refresh the environment data + await utils.environment.one.invalidate({ + environmentId, }); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/project/add-database.tsx b/apps/dokploy/components/dashboard/project/add-database.tsx index 6b07baa81..c7d114c93 100644 --- a/apps/dokploy/components/dashboard/project/add-database.tsx +++ b/apps/dokploy/components/dashboard/project/add-database.tsx @@ -83,7 +83,12 @@ const baseDatabaseSchema = z.object({ message: "App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", }), - databasePassword: z.string(), + databasePassword: z + .string() + .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { + message: + "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + }), dockerImage: z.string(), description: z.string().nullable(), serverId: z.string().nullable(), @@ -112,7 +117,13 @@ const mySchema = z.discriminatedUnion("type", [ z .object({ type: z.literal("mysql"), - databaseRootPassword: z.string().default(""), + databaseRootPassword: z + .string() + .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { + message: + "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + }) + .optional(), databaseUser: z.string().default("mysql"), databaseName: z.string().default("mysql"), }) @@ -121,7 +132,13 @@ const mySchema = z.discriminatedUnion("type", [ .object({ type: z.literal("mariadb"), dockerImage: z.string().default("mariadb:4"), - databaseRootPassword: z.string().default(""), + databaseRootPassword: z + .string() + .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, { + message: + "Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility", + }) + .optional(), databaseUser: z.string().default("mariadb"), databaseName: z.string().default("mariadb"), }) @@ -154,11 +171,11 @@ const databasesMap = { type AddDatabase = z.infer; interface Props { - projectId: string; + environmentId: string; projectName?: string; } -export const AddDatabase = ({ projectId, projectName }: Props) => { +export const AddDatabase = ({ environmentId, projectName }: Props) => { const utils = api.useUtils(); const [visible, setVisible] = useState(false); const slug = slugify(projectName); @@ -169,6 +186,9 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { const mariadbMutation = api.mariadb.create.useMutation(); const mysqlMutation = api.mysql.create.useMutation(); + // Get environment data to extract projectId + const { data: environment } = api.environment.one.useQuery({ environmentId }); + const hasServers = servers && servers.length > 0; const form = useForm({ @@ -203,7 +223,8 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { name: data.name, appName: data.appName, dockerImage: defaultDockerImage, - projectId, + projectId: environment?.projectId || "", + environmentId, serverId: data.serverId, description: data.description, }; @@ -232,13 +253,12 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { ...commonParams, databasePassword: data.databasePassword, serverId: data.serverId, - projectId, }); } else if (data.type === "mariadb") { promise = mariadbMutation.mutateAsync({ ...commonParams, databasePassword: data.databasePassword, - databaseRootPassword: data.databaseRootPassword, + databaseRootPassword: data.databaseRootPassword || "", databaseName: data.databaseName || "mariadb", databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], @@ -251,7 +271,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { databaseName: data.databaseName || "mysql", databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - databaseRootPassword: data.databaseRootPassword, + databaseRootPassword: data.databaseRootPassword || "", serverId: data.serverId, }); } @@ -271,8 +291,9 @@ export const AddDatabase = ({ projectId, projectName }: Props) => { databaseUser: "", }); setVisible(false); - await utils.project.one.invalidate({ - projectId, + // Invalidate the project query to refresh the environment data + await utils.environment.one.invalidate({ + environmentId, }); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/project/add-template.tsx b/apps/dokploy/components/dashboard/project/add-template.tsx index 5eb994fc4..06d0adf39 100644 --- a/apps/dokploy/components/dashboard/project/add-template.tsx +++ b/apps/dokploy/components/dashboard/project/add-template.tsx @@ -73,11 +73,11 @@ import { api } from "@/utils/api"; const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url"; interface Props { - projectId: string; + environmentId: string; baseUrl?: string; } -export const AddTemplate = ({ projectId, baseUrl }: Props) => { +export const AddTemplate = ({ environmentId, baseUrl }: Props) => { const [query, setQuery] = useState(""); const [open, setOpen] = useState(false); const [viewMode, setViewMode] = useState<"detailed" | "icon">("detailed"); @@ -91,6 +91,9 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => { return undefined; }); + // Get environment data to extract projectId + const { data: environment } = api.environment.one.useQuery({ environmentId }); + // Save to localStorage when customBaseUrl changes useEffect(() => { if (customBaseUrl) { @@ -307,9 +310,9 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => { : "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6", )} > - {templates?.map((template) => ( + {templates?.map((template, idx) => (
{ disabled={isLoading} onClick={async () => { const promise = mutateAsync({ - projectId, + environmentId, serverId: serverId || undefined, id: template.id, baseUrl: customBaseUrl, @@ -498,8 +501,9 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => { toast.promise(promise, { loading: "Setting up...", success: () => { - utils.project.one.invalidate({ - projectId, + // Invalidate the project query to refresh the environment data + utils.environment.one.invalidate({ + environmentId, }); setOpen(false); return `${template.name} template created successfully`; diff --git a/apps/dokploy/components/dashboard/project/advanced-environment-selector.tsx b/apps/dokploy/components/dashboard/project/advanced-environment-selector.tsx new file mode 100644 index 000000000..d6497fd0f --- /dev/null +++ b/apps/dokploy/components/dashboard/project/advanced-environment-selector.tsx @@ -0,0 +1,446 @@ +import type { findEnvironmentsByProjectId } from "@dokploy/server"; +import { + ChevronDownIcon, + PencilIcon, + PlusIcon, + Terminal, + TrashIcon, +} from "lucide-react"; +import { useRouter } from "next/router"; +import { useState } from "react"; +import { toast } from "sonner"; +import { EnvironmentVariables } from "@/components/dashboard/project/environment-variables"; +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { api } from "@/utils/api"; + +type Environment = Awaited< + ReturnType +>[number]; +interface AdvancedEnvironmentSelectorProps { + projectId: string; + currentEnvironmentId?: string; +} + +export const AdvancedEnvironmentSelector = ({ + projectId, + currentEnvironmentId, +}: AdvancedEnvironmentSelectorProps) => { + const router = useRouter(); + const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [selectedEnvironment, setSelectedEnvironment] = + useState(null); + + const { data: environments } = api.environment.byProjectId.useQuery( + { projectId: projectId }, + { + enabled: !!projectId, + }, + ); + + // Form states + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + + // API mutations + const { data: environment } = api.environment.one.useQuery( + { environmentId: currentEnvironmentId || "" }, + { + enabled: !!currentEnvironmentId, + }, + ); + + const haveServices = + selectedEnvironment && + ((selectedEnvironment?.mariadb?.length || 0) > 0 || + (selectedEnvironment?.mongo?.length || 0) > 0 || + (selectedEnvironment?.mysql?.length || 0) > 0 || + (selectedEnvironment?.postgres?.length || 0) > 0 || + (selectedEnvironment?.redis?.length || 0) > 0 || + (selectedEnvironment?.applications?.length || 0) > 0 || + (selectedEnvironment?.compose?.length || 0) > 0); + const createEnvironment = api.environment.create.useMutation(); + const updateEnvironment = api.environment.update.useMutation(); + const deleteEnvironment = api.environment.remove.useMutation(); + const duplicateEnvironment = api.environment.duplicate.useMutation(); + + // Refetch project data + const utils = api.useUtils(); + + const handleCreateEnvironment = async () => { + try { + await createEnvironment.mutateAsync({ + projectId, + name: name.trim(), + description: description.trim() || null, + }); + + toast.success("Environment created successfully"); + utils.environment.byProjectId.invalidate({ projectId }); + setIsCreateDialogOpen(false); + setName(""); + setDescription(""); + } catch (error) { + toast.error("Failed to create environment"); + } + }; + + const handleUpdateEnvironment = async () => { + if (!selectedEnvironment) return; + + try { + await updateEnvironment.mutateAsync({ + environmentId: selectedEnvironment.environmentId, + name: name.trim(), + description: description.trim() || null, + }); + + toast.success("Environment updated successfully"); + utils.environment.byProjectId.invalidate({ projectId }); + setIsEditDialogOpen(false); + setSelectedEnvironment(null); + setName(""); + setDescription(""); + } catch (error) { + toast.error("Failed to update environment"); + } + }; + + const handleDeleteEnvironment = async () => { + if (!selectedEnvironment) return; + + try { + await deleteEnvironment.mutateAsync({ + environmentId: selectedEnvironment.environmentId, + }); + + toast.success("Environment deleted successfully"); + utils.environment.byProjectId.invalidate({ projectId }); + setIsDeleteDialogOpen(false); + setSelectedEnvironment(null); + + // Redirect to production if we deleted the current environment + if (selectedEnvironment.environmentId === currentEnvironmentId) { + const productionEnv = environments?.find( + (env) => env.name === "production", + ); + if (productionEnv) { + router.push( + `/dashboard/project/${projectId}/environment/${productionEnv.environmentId}`, + ); + } + } + } catch (error) { + toast.error("Failed to delete environment"); + } + }; + + const handleDuplicateEnvironment = async (environment: Environment) => { + try { + const result = await duplicateEnvironment.mutateAsync({ + environmentId: environment.environmentId, + name: `${environment.name}-copy`, + description: environment.description, + }); + + toast.success("Environment duplicated successfully"); + utils.project.one.invalidate({ projectId }); + + // Navigate to the new duplicated environment + router.push( + `/dashboard/project/${projectId}/environment/${result.environmentId}`, + ); + } catch (error) { + toast.error("Failed to duplicate environment"); + } + }; + + const openEditDialog = (environment: Environment) => { + setSelectedEnvironment(environment); + setName(environment.name); + setDescription(environment.description || ""); + setIsEditDialogOpen(true); + }; + + const openDeleteDialog = (environment: Environment) => { + setSelectedEnvironment(environment); + setIsDeleteDialogOpen(true); + }; + + const currentEnv = environments?.find( + (env) => env.environmentId === currentEnvironmentId, + ); + + return ( + <> + + + + + + Environments + + + {environments?.map((environment) => { + const servicesCount = + environment.mariadb.length + + environment.mongo.length + + environment.mysql.length + + environment.postgres.length + + environment.redis.length + + environment.applications.length + + environment.compose.length; + return ( +
+ { + router.push( + `/dashboard/project/${projectId}/environment/${environment.environmentId}`, + ); + }} + > +
+ + {environment.name} ({servicesCount}) + + {environment.environmentId === currentEnvironmentId && ( +
+ )} +
+ + + {/* Action buttons for non-production environments */} + + + + {environment.name !== "production" && ( +
+ + + +
+ )} +
+ ); + })} + + + setIsCreateDialogOpen(true)} + > + + Create Environment + + + + + + + + Create Environment + + Create a new environment for your project. + + + +
+
+ + setName(e.target.value)} + placeholder="Environment name" + /> +
+
+ +