mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-22 14:25:24 +02:00
Compare commits
1 Commits
v0.25.1
...
2234-datab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51c81968e9 |
11
.github/pull_request_template.md
vendored
11
.github/pull_request_template.md
vendored
@@ -6,13 +6,16 @@ Please describe in a short paragraph what this PR is about.
|
|||||||
|
|
||||||
Before submitting this PR, please make sure that:
|
Before submitting this PR, please make sure that:
|
||||||
|
|
||||||
- [] You created a dedicated branch based on the `canary` branch.
|
- [ ] 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 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 have tested this PR in your local instance.
|
||||||
|
|
||||||
## Issues related (if applicable)
|
## Issues related (if applicable)
|
||||||
|
|
||||||
closes #123
|
Close automatically the related issues using the keywords: `closes #ISSUE_NUMBER`, `fixes #ISSUE_NUMBER`, `resolves #ISSUE_NUMBER`
|
||||||
|
|
||||||
|
Example: `closes #123`
|
||||||
|
|
||||||
## Screenshots (if applicable)
|
## Screenshots (if applicable)
|
||||||
|
|
||||||
|
If you include a video or screenshot, would be awesome so we can see the changes in action.
|
||||||
BIN
.github/sponsors/tuple.png
vendored
BIN
.github/sponsors/tuple.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 264 KiB |
2
.github/workflows/dokploy.yml
vendored
2
.github/workflows/dokploy.yml
vendored
@@ -2,7 +2,7 @@ name: Dokploy Docker Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, canary, "fix/re-apply-database-migration-fix"]
|
branches: [main, canary]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -11,25 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center" markdown="1">
|
|
||||||
<sup>Special thanks to:</sup>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
<a href="https://tuple.app/dokploy">
|
|
||||||
<img src=".github/sponsors/tuple.png" alt="Tuple's sponsorship image" width="400"/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
### [Tuple, the premier screen sharing app for developers](https://tuple.app/dokploy)
|
|
||||||
[Available for MacOS & Windows](https://tuple.app/dokploy)<br>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
||||||
|
|
||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
Dokploy includes multiple features to make your life easier.
|
Dokploy includes multiple features to make your life easier.
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { zValidator } from "@hono/zod-validator";
|
|||||||
import { Inngest } from "inngest";
|
import { Inngest } from "inngest";
|
||||||
import { serve as serveInngest } from "inngest/hono";
|
import { serve as serveInngest } from "inngest/hono";
|
||||||
import { logger } from "./logger.js";
|
import { logger } from "./logger.js";
|
||||||
import {
|
import { type DeployJob, deployJobSchema } from "./schema.js";
|
||||||
cancelDeploymentSchema,
|
|
||||||
type DeployJob,
|
|
||||||
deployJobSchema,
|
|
||||||
} from "./schema.js";
|
|
||||||
import { deploy } from "./utils.js";
|
import { deploy } from "./utils.js";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
@@ -31,13 +27,6 @@ export const deploymentFunction = inngest.createFunction(
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
retries: 0,
|
retries: 0,
|
||||||
cancelOn: [
|
|
||||||
{
|
|
||||||
event: "deployment/cancelled",
|
|
||||||
if: "async.data.applicationId == event.data.applicationId || async.data.composeId == event.data.composeId",
|
|
||||||
timeout: "1h", // Allow cancellation for up to 1 hour
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{ event: "deployment/requested" },
|
{ event: "deployment/requested" },
|
||||||
|
|
||||||
@@ -130,48 +119,6 @@ app.post("/deploy", zValidator("json", deployJobSchema), async (c) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post(
|
|
||||||
"/cancel-deployment",
|
|
||||||
zValidator("json", cancelDeploymentSchema),
|
|
||||||
async (c) => {
|
|
||||||
const data = c.req.valid("json");
|
|
||||||
logger.info("Received cancel deployment request", data);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Send cancellation event to Inngest
|
|
||||||
|
|
||||||
await inngest.send({
|
|
||||||
name: "deployment/cancelled",
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
const identifier =
|
|
||||||
data.applicationType === "application"
|
|
||||||
? `applicationId: ${data.applicationId}`
|
|
||||||
: `composeId: ${data.composeId}`;
|
|
||||||
|
|
||||||
logger.info("Deployment cancellation event sent", {
|
|
||||||
...data,
|
|
||||||
identifier,
|
|
||||||
});
|
|
||||||
|
|
||||||
return c.json({
|
|
||||||
message: "Deployment cancellation requested",
|
|
||||||
applicationType: data.applicationType,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Failed to send deployment cancellation event", error);
|
|
||||||
return c.json(
|
|
||||||
{
|
|
||||||
message: "Failed to cancel deployment",
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
},
|
|
||||||
500,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
app.get("/health", async (c) => {
|
app.get("/health", async (c) => {
|
||||||
return c.json({ status: "ok" });
|
return c.json({ status: "ok" });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { z } from "zod";
|
|||||||
export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
||||||
z.object({
|
z.object({
|
||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
titleLog: z.string().optional(),
|
titleLog: z.string(),
|
||||||
descriptionLog: z.string().optional(),
|
descriptionLog: z.string(),
|
||||||
server: z.boolean().optional(),
|
server: z.boolean().optional(),
|
||||||
type: z.enum(["deploy", "redeploy"]),
|
type: z.enum(["deploy", "redeploy"]),
|
||||||
applicationType: z.literal("application"),
|
applicationType: z.literal("application"),
|
||||||
@@ -12,8 +12,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
composeId: z.string(),
|
composeId: z.string(),
|
||||||
titleLog: z.string().optional(),
|
titleLog: z.string(),
|
||||||
descriptionLog: z.string().optional(),
|
descriptionLog: z.string(),
|
||||||
server: z.boolean().optional(),
|
server: z.boolean().optional(),
|
||||||
type: z.enum(["deploy", "redeploy"]),
|
type: z.enum(["deploy", "redeploy"]),
|
||||||
applicationType: z.literal("compose"),
|
applicationType: z.literal("compose"),
|
||||||
@@ -22,8 +22,8 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
|||||||
z.object({
|
z.object({
|
||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
previewDeploymentId: z.string(),
|
previewDeploymentId: z.string(),
|
||||||
titleLog: z.string().optional(),
|
titleLog: z.string(),
|
||||||
descriptionLog: z.string().optional(),
|
descriptionLog: z.string(),
|
||||||
server: z.boolean().optional(),
|
server: z.boolean().optional(),
|
||||||
type: z.enum(["deploy"]),
|
type: z.enum(["deploy"]),
|
||||||
applicationType: z.literal("application-preview"),
|
applicationType: z.literal("application-preview"),
|
||||||
@@ -32,16 +32,3 @@ export const deployJobSchema = z.discriminatedUnion("applicationType", [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export type DeployJob = z.infer<typeof deployJobSchema>;
|
export type DeployJob = z.infer<typeof deployJobSchema>;
|
||||||
|
|
||||||
export const cancelDeploymentSchema = z.discriminatedUnion("applicationType", [
|
|
||||||
z.object({
|
|
||||||
applicationId: z.string(),
|
|
||||||
applicationType: z.literal("application"),
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
composeId: z.string(),
|
|
||||||
applicationType: z.literal("compose"),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export type CancelDeploymentJob = z.infer<typeof cancelDeploymentSchema>;
|
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
if (job.type === "redeploy") {
|
if (job.type === "redeploy") {
|
||||||
await rebuildRemoteApplication({
|
await rebuildRemoteApplication({
|
||||||
applicationId: job.applicationId,
|
applicationId: job.applicationId,
|
||||||
titleLog: job.titleLog || "Rebuild deployment",
|
titleLog: job.titleLog,
|
||||||
descriptionLog: job.descriptionLog || "",
|
descriptionLog: job.descriptionLog,
|
||||||
});
|
});
|
||||||
} else if (job.type === "deploy") {
|
} else if (job.type === "deploy") {
|
||||||
await deployRemoteApplication({
|
await deployRemoteApplication({
|
||||||
applicationId: job.applicationId,
|
applicationId: job.applicationId,
|
||||||
titleLog: job.titleLog || "Manual deployment",
|
titleLog: job.titleLog,
|
||||||
descriptionLog: job.descriptionLog || "",
|
descriptionLog: job.descriptionLog,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,14 +38,14 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
if (job.type === "redeploy") {
|
if (job.type === "redeploy") {
|
||||||
await rebuildRemoteCompose({
|
await rebuildRemoteCompose({
|
||||||
composeId: job.composeId,
|
composeId: job.composeId,
|
||||||
titleLog: job.titleLog || "Rebuild deployment",
|
titleLog: job.titleLog,
|
||||||
descriptionLog: job.descriptionLog || "",
|
descriptionLog: job.descriptionLog,
|
||||||
});
|
});
|
||||||
} else if (job.type === "deploy") {
|
} else if (job.type === "deploy") {
|
||||||
await deployRemoteCompose({
|
await deployRemoteCompose({
|
||||||
composeId: job.composeId,
|
composeId: job.composeId,
|
||||||
titleLog: job.titleLog || "Manual deployment",
|
titleLog: job.titleLog,
|
||||||
descriptionLog: job.descriptionLog || "",
|
descriptionLog: job.descriptionLog,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,8 +57,8 @@ export const deploy = async (job: DeployJob) => {
|
|||||||
if (job.type === "deploy") {
|
if (job.type === "deploy") {
|
||||||
await deployRemotePreviewApplication({
|
await deployRemotePreviewApplication({
|
||||||
applicationId: job.applicationId,
|
applicationId: job.applicationId,
|
||||||
titleLog: job.titleLog || "Preview Deployment",
|
titleLog: job.titleLog,
|
||||||
descriptionLog: job.descriptionLog || "",
|
descriptionLog: job.descriptionLog,
|
||||||
previewDeploymentId: job.previewDeploymentId,
|
previewDeploymentId: job.previewDeploymentId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,21 +56,13 @@ const baseApp: ApplicationNested = {
|
|||||||
previewPort: 3000,
|
previewPort: 3000,
|
||||||
previewLimit: 0,
|
previewLimit: 0,
|
||||||
previewWildcard: "",
|
previewWildcard: "",
|
||||||
environment: {
|
project: {
|
||||||
env: "",
|
env: "",
|
||||||
environmentId: "",
|
organizationId: "",
|
||||||
name: "",
|
name: "",
|
||||||
createdAt: "",
|
|
||||||
description: "",
|
description: "",
|
||||||
|
createdAt: "",
|
||||||
projectId: "",
|
projectId: "",
|
||||||
project: {
|
|
||||||
env: "",
|
|
||||||
organizationId: "",
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
createdAt: "",
|
|
||||||
projectId: "",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
buildArgs: null,
|
buildArgs: null,
|
||||||
buildPath: "/",
|
buildPath: "/",
|
||||||
@@ -100,7 +92,6 @@ const baseApp: ApplicationNested = {
|
|||||||
dockerfile: null,
|
dockerfile: null,
|
||||||
dockerImage: null,
|
dockerImage: null,
|
||||||
dropBuildPath: null,
|
dropBuildPath: null,
|
||||||
environmentId: "",
|
|
||||||
enabled: null,
|
enabled: null,
|
||||||
env: null,
|
env: null,
|
||||||
healthCheckSwarm: null,
|
healthCheckSwarm: null,
|
||||||
@@ -115,6 +106,7 @@ const baseApp: ApplicationNested = {
|
|||||||
password: null,
|
password: null,
|
||||||
placementSwarm: null,
|
placementSwarm: null,
|
||||||
ports: [],
|
ports: [],
|
||||||
|
projectId: "",
|
||||||
publishDirectory: null,
|
publishDirectory: null,
|
||||||
isStaticSpa: null,
|
isStaticSpa: null,
|
||||||
redirects: [],
|
redirects: [],
|
||||||
|
|||||||
335
apps/dokploy/__test__/env/environment.test.ts
vendored
335
apps/dokploy/__test__/env/environment.test.ts
vendored
@@ -1,335 +0,0 @@
|
|||||||
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",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -36,22 +36,13 @@ const baseApp: ApplicationNested = {
|
|||||||
previewLimit: 0,
|
previewLimit: 0,
|
||||||
previewCustomCertResolver: null,
|
previewCustomCertResolver: null,
|
||||||
previewWildcard: "",
|
previewWildcard: "",
|
||||||
environmentId: "",
|
project: {
|
||||||
environment: {
|
|
||||||
env: "",
|
env: "",
|
||||||
environmentId: "",
|
organizationId: "",
|
||||||
name: "",
|
name: "",
|
||||||
createdAt: "",
|
|
||||||
description: "",
|
description: "",
|
||||||
|
createdAt: "",
|
||||||
projectId: "",
|
projectId: "",
|
||||||
project: {
|
|
||||||
env: "",
|
|
||||||
organizationId: "",
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
createdAt: "",
|
|
||||||
projectId: "",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
buildPath: "/",
|
buildPath: "/",
|
||||||
gitlabPathNamespace: "",
|
gitlabPathNamespace: "",
|
||||||
@@ -94,6 +85,7 @@ const baseApp: ApplicationNested = {
|
|||||||
password: null,
|
password: null,
|
||||||
placementSwarm: null,
|
placementSwarm: null,
|
||||||
ports: [],
|
ports: [],
|
||||||
|
projectId: "",
|
||||||
publishDirectory: null,
|
publishDirectory: null,
|
||||||
isStaticSpa: null,
|
isStaticSpa: null,
|
||||||
redirects: [],
|
redirects: [],
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
|
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
@@ -62,48 +61,12 @@ export const ShowDeployments = ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
|
||||||
|
|
||||||
const { mutateAsync: rollback, isLoading: isRollingBack } =
|
const { mutateAsync: rollback, isLoading: isRollingBack } =
|
||||||
api.rollback.rollback.useMutation();
|
api.rollback.rollback.useMutation();
|
||||||
const { mutateAsync: killProcess, isLoading: isKillingProcess } =
|
const { mutateAsync: killProcess, isLoading: isKillingProcess } =
|
||||||
api.deployment.killProcess.useMutation();
|
api.deployment.killProcess.useMutation();
|
||||||
|
|
||||||
// Cancel deployment mutations
|
|
||||||
const {
|
|
||||||
mutateAsync: cancelApplicationDeployment,
|
|
||||||
isLoading: isCancellingApp,
|
|
||||||
} = api.application.cancelDeployment.useMutation();
|
|
||||||
const {
|
|
||||||
mutateAsync: cancelComposeDeployment,
|
|
||||||
isLoading: isCancellingCompose,
|
|
||||||
} = api.compose.cancelDeployment.useMutation();
|
|
||||||
|
|
||||||
const [url, setUrl] = React.useState("");
|
const [url, setUrl] = React.useState("");
|
||||||
|
|
||||||
// Check for stuck deployment (more than 9 minutes) - only for the most recent deployment
|
|
||||||
const stuckDeployment = useMemo(() => {
|
|
||||||
if (!isCloud || !deployments || deployments.length === 0) return null;
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
const NINE_MINUTES = 10 * 60 * 1000; // 9 minutes in milliseconds
|
|
||||||
|
|
||||||
// Get the most recent deployment (first in the list since they're sorted by date)
|
|
||||||
const mostRecentDeployment = deployments[0];
|
|
||||||
|
|
||||||
if (
|
|
||||||
!mostRecentDeployment ||
|
|
||||||
mostRecentDeployment.status !== "running" ||
|
|
||||||
!mostRecentDeployment.startedAt
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = new Date(mostRecentDeployment.startedAt).getTime();
|
|
||||||
const elapsed = now - startTime;
|
|
||||||
|
|
||||||
return elapsed > NINE_MINUTES ? mostRecentDeployment : null;
|
|
||||||
}, [isCloud, deployments]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUrl(document.location.origin);
|
setUrl(document.location.origin);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -114,7 +77,7 @@ export const ShowDeployments = ({
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<CardTitle className="text-xl">Deployments</CardTitle>
|
<CardTitle className="text-xl">Deployments</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
See the last 10 deployments for this {type}
|
See all the 10 last deployments for this {type}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
@@ -131,54 +94,6 @@ export const ShowDeployments = ({
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-4">
|
<CardContent className="flex flex-col gap-4">
|
||||||
{stuckDeployment && (type === "application" || type === "compose") && (
|
|
||||||
<AlertBlock
|
|
||||||
type="warning"
|
|
||||||
className="flex-col items-start w-full p-4"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<div>
|
|
||||||
<div className="font-medium text-sm mb-1">
|
|
||||||
Build appears to be stuck
|
|
||||||
</div>
|
|
||||||
<p className="text-sm">
|
|
||||||
Hey! Looks like the build has been running for more than 10
|
|
||||||
minutes. Would you like to cancel this deployment?
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
size="sm"
|
|
||||||
className="w-fit"
|
|
||||||
isLoading={
|
|
||||||
type === "application" ? isCancellingApp : isCancellingCompose
|
|
||||||
}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
if (type === "application") {
|
|
||||||
await cancelApplicationDeployment({
|
|
||||||
applicationId: id,
|
|
||||||
});
|
|
||||||
} else if (type === "compose") {
|
|
||||||
await cancelComposeDeployment({
|
|
||||||
composeId: id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
toast.success("Deployment cancellation requested");
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: "Failed to cancel deployment",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel Deployment
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</AlertBlock>
|
|
||||||
)}
|
|
||||||
{refreshToken && (
|
{refreshToken && (
|
||||||
<div className="flex flex-col gap-2 text-sm">
|
<div className="flex flex-col gap-2 text-sm">
|
||||||
<span>
|
<span>
|
||||||
@@ -189,9 +104,7 @@ export const ShowDeployments = ({
|
|||||||
<span>Webhook URL: </span>
|
<span>Webhook URL: </span>
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
<span className="break-all text-muted-foreground">
|
<span className="break-all text-muted-foreground">
|
||||||
{`${url}/api/deploy${
|
{`${url}/api/deploy${type === "compose" ? "/compose" : ""}/${refreshToken}`}
|
||||||
type === "compose" ? "/compose" : ""
|
|
||||||
}/${refreshToken}`}
|
|
||||||
</span>
|
</span>
|
||||||
{(type === "application" || type === "compose") && (
|
{(type === "application" || type === "compose") && (
|
||||||
<RefreshToken id={id} type={type} />
|
<RefreshToken id={id} type={type} />
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
|||||||
toast.success("Application deployed successfully");
|
toast.success("Application deployed successfully");
|
||||||
refetch();
|
refetch();
|
||||||
router.push(
|
router.push(
|
||||||
`/dashboard/project/${data?.environment.projectId}/environment/${data?.environmentId}/services/application/${applicationId}?tab=deployments`,
|
`/dashboard/project/${data?.projectId}/services/application/${applicationId}?tab=deployments`,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Card className="border px-6 shadow-none bg-transparent h-full min-h-[50vh]">
|
<Card className="border px-6 shadow-none bg-transparent h-full min-h-[50vh]">
|
||||||
<CardHeader className="px-0">
|
<CardHeader className="px-0">
|
||||||
<div className="flex justify-between items-center gap-y-2 flex-wrap">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
<CardTitle className="text-xl font-bold flex items-center gap-2">
|
||||||
Scheduled Tasks
|
Scheduled Tasks
|
||||||
@@ -91,15 +91,15 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={schedule.scheduleId}
|
key={schedule.scheduleId}
|
||||||
className="flex items-center flex-wrap sm:flex-nowrap gap-y-2 justify-between rounded-lg border p-3 transition-colors bg-muted/50"
|
className="flex items-center justify-between rounded-lg border p-3 transition-colors bg-muted/50"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="flex flex-shrink-0 h-9 w-9 items-center justify-center rounded-full bg-primary/5">
|
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">
|
||||||
<Clock className="size-4 text-primary/70" />
|
<Clock className="size-4 text-primary/70" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="text-sm font-medium leading-none [overflow-wrap:anywhere] line-clamp-3">
|
<h3 className="text-sm font-medium leading-none">
|
||||||
{schedule.name}
|
{schedule.name}
|
||||||
</h3>
|
</h3>
|
||||||
<Badge
|
<Badge
|
||||||
@@ -109,7 +109,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
{schedule.enabled ? "Enabled" : "Disabled"}
|
{schedule.enabled ? "Enabled" : "Disabled"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground flex-wrap">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="font-mono text-[10px] bg-transparent"
|
className="font-mono text-[10px] bg-transparent"
|
||||||
@@ -142,7 +142,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-0.5 md:gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<ShowDeploymentsModal
|
<ShowDeploymentsModal
|
||||||
id={schedule.scheduleId}
|
id={schedule.scheduleId}
|
||||||
type="schedule"
|
type="schedule"
|
||||||
@@ -226,7 +226,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-2 items-center justify-center py-12 rounded-lg">
|
<div className="flex flex-col gap-2 items-center justify-center py-12 rounded-lg">
|
||||||
<Clock className="size-8 mb-4 text-muted-foreground" />
|
<Clock className="size-8 mb-4 text-muted-foreground" />
|
||||||
<p className="text-lg font-medium text-muted-foreground">
|
<p className="text-lg font-medium text-muted-foreground">
|
||||||
No scheduled tasks
|
No scheduled tasks
|
||||||
|
|||||||
@@ -101,9 +101,7 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
deleteVolumes,
|
deleteVolumes,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
push(
|
push(`/dashboard/project/${result?.projectId}`);
|
||||||
`/dashboard/project/${result?.environment?.projectId}/environment/${result?.environment?.environmentId}`,
|
|
||||||
);
|
|
||||||
toast.success("deleted successfully");
|
toast.success("deleted successfully");
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const ComposeActions = ({ composeId }: Props) => {
|
|||||||
toast.success("Compose deployed successfully");
|
toast.success("Compose deployed successfully");
|
||||||
refetch();
|
refetch();
|
||||||
router.push(
|
router.push(
|
||||||
`/dashboard/project/${data?.environment.projectId}/environment/${data?.environmentId}/services/compose/${composeId}?tab=deployments`,
|
`/dashboard/project/${data?.project.projectId}/services/compose/${composeId}?tab=deployments`,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">External Credentials</CardTitle>
|
<CardTitle className="text-xl">External Credentials</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
In order to make the database reachable through the internet, you
|
In order to make the database reachable trought internet is
|
||||||
must set a port and ensure that the port is not being used by
|
required to set a port, make sure the port is not used by another
|
||||||
another application or database
|
application or database
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">External Credentials</CardTitle>
|
<CardTitle className="text-xl">External Credentials</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
In order to make the database reachable through the internet, you
|
In order to make the database reachable trought internet is
|
||||||
must set a port and ensure that the port is not being used by
|
required to set a port, make sure the port is not used by another
|
||||||
another application or database
|
application or database
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">External Credentials</CardTitle>
|
<CardTitle className="text-xl">External Credentials</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
In order to make the database reachable through the internet, you
|
In order to make the database reachable trought internet is
|
||||||
must set a port and ensure that the port is not being used by
|
required to set a port, make sure the port is not used by another
|
||||||
another application or database
|
application or database
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
|||||||
@@ -104,9 +104,9 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">External Credentials</CardTitle>
|
<CardTitle className="text-xl">External Credentials</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
In order to make the database reachable through the internet, you
|
In order to make the database reachable trought internet is
|
||||||
must set a port and ensure that the port is not being used by
|
required to set a port, make sure the port is not used by another
|
||||||
another application or database
|
application or database
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { TemplateGenerator } from "@/components/dashboard/project/ai/template-generator";
|
import { TemplateGenerator } from "@/components/dashboard/project/ai/template-generator";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environmentId: string;
|
projectId: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddAiAssistant = ({ environmentId }: Props) => {
|
export const AddAiAssistant = ({ projectId }: Props) => {
|
||||||
return <TemplateGenerator environmentId={environmentId} />;
|
return <TemplateGenerator projectId={projectId} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ const AddTemplateSchema = z.object({
|
|||||||
type AddTemplate = z.infer<typeof AddTemplateSchema>;
|
type AddTemplate = z.infer<typeof AddTemplateSchema>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environmentId: string;
|
projectId: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddApplication = ({ environmentId, projectName }: Props) => {
|
export const AddApplication = ({ projectId, projectName }: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
@@ -76,10 +76,6 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
|
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
// Show dropdown logic based on cloud environment
|
|
||||||
// Cloud: show only if there are remote servers (no Dokploy option)
|
|
||||||
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
|
||||||
const shouldShowServerDropdown = hasServers;
|
|
||||||
|
|
||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
api.application.create.useMutation();
|
api.application.create.useMutation();
|
||||||
@@ -98,15 +94,15 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
appName: data.appName,
|
appName: data.appName,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
projectId,
|
||||||
environmentId,
|
serverId: data.serverId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Service Created");
|
toast.success("Service Created");
|
||||||
form.reset();
|
form.reset();
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
await utils.environment.one.invalidate({
|
await utils.project.one.invalidate({
|
||||||
environmentId,
|
projectId,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -161,7 +157,7 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{shouldShowServerDropdown && (
|
{hasServers && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serverId"
|
name="serverId"
|
||||||
@@ -190,27 +186,13 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={
|
defaultValue={field.value}
|
||||||
field.value || (!isCloud ? "dokploy" : undefined)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue
|
<SelectValue placeholder="Select a Server" />
|
||||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
|
||||||
<SelectItem value="dokploy">
|
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
|
||||||
<span>Dokploy</span>
|
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
|
||||||
Default
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -224,9 +206,7 @@ export const AddApplication = ({ environmentId, projectName }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
|
||||||
</SelectLabel>
|
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ const AddComposeSchema = z.object({
|
|||||||
type AddCompose = z.infer<typeof AddComposeSchema>;
|
type AddCompose = z.infer<typeof AddComposeSchema>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environmentId: string;
|
projectId: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddCompose = ({ environmentId, projectName }: Props) => {
|
export const AddCompose = ({ projectId, projectName }: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
@@ -78,14 +78,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
api.compose.create.useMutation();
|
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 hasServers = servers && servers.length > 0;
|
||||||
// Show dropdown logic based on cloud environment
|
|
||||||
// Cloud: show only if there are remote servers (no Dokploy option)
|
|
||||||
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
|
||||||
const shouldShowServerDropdown = hasServers;
|
|
||||||
|
|
||||||
const form = useForm<AddCompose>({
|
const form = useForm<AddCompose>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -105,17 +98,16 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
environmentId,
|
projectId,
|
||||||
composeType: data.composeType,
|
composeType: data.composeType,
|
||||||
appName: data.appName,
|
appName: data.appName,
|
||||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
serverId: data.serverId,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Compose Created");
|
toast.success("Compose Created");
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
// Invalidate the project query to refresh the environment data
|
await utils.project.one.invalidate({
|
||||||
await utils.environment.one.invalidate({
|
projectId,
|
||||||
environmentId,
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -173,7 +165,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{shouldShowServerDropdown && (
|
{hasServers && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serverId"
|
name="serverId"
|
||||||
@@ -202,27 +194,13 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={
|
defaultValue={field.value}
|
||||||
field.value || (!isCloud ? "dokploy" : undefined)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue
|
<SelectValue placeholder="Select a Server" />
|
||||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
|
||||||
<SelectItem value="dokploy">
|
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
|
||||||
<span>Dokploy</span>
|
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
|
||||||
Default
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -236,9 +214,7 @@ export const AddCompose = ({ environmentId, projectName }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
|
||||||
</SelectLabel>
|
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -83,12 +83,7 @@ const baseDatabaseSchema = z.object({
|
|||||||
message:
|
message:
|
||||||
"App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
|
"App name supports lowercase letters, numbers, '-' and can only start and end letters, and does not support continuous '-'",
|
||||||
}),
|
}),
|
||||||
databasePassword: z
|
databasePassword: z.string(),
|
||||||
.string()
|
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
}),
|
|
||||||
dockerImage: z.string(),
|
dockerImage: z.string(),
|
||||||
description: z.string().nullable(),
|
description: z.string().nullable(),
|
||||||
serverId: z.string().nullable(),
|
serverId: z.string().nullable(),
|
||||||
@@ -117,13 +112,7 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("mysql"),
|
type: z.literal("mysql"),
|
||||||
databaseRootPassword: z
|
databaseRootPassword: z.string().default(""),
|
||||||
.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"),
|
databaseUser: z.string().default("mysql"),
|
||||||
databaseName: z.string().default("mysql"),
|
databaseName: z.string().default("mysql"),
|
||||||
})
|
})
|
||||||
@@ -132,13 +121,7 @@ const mySchema = z.discriminatedUnion("type", [
|
|||||||
.object({
|
.object({
|
||||||
type: z.literal("mariadb"),
|
type: z.literal("mariadb"),
|
||||||
dockerImage: z.string().default("mariadb:4"),
|
dockerImage: z.string().default("mariadb:4"),
|
||||||
databaseRootPassword: z
|
databaseRootPassword: z.string().default(""),
|
||||||
.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"),
|
databaseUser: z.string().default("mariadb"),
|
||||||
databaseName: z.string().default("mariadb"),
|
databaseName: z.string().default("mariadb"),
|
||||||
})
|
})
|
||||||
@@ -171,15 +154,14 @@ const databasesMap = {
|
|||||||
type AddDatabase = z.infer<typeof mySchema>;
|
type AddDatabase = z.infer<typeof mySchema>;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environmentId: string;
|
projectId: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
export const AddDatabase = ({ projectId, projectName }: Props) => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const postgresMutation = api.postgres.create.useMutation();
|
const postgresMutation = api.postgres.create.useMutation();
|
||||||
const mongoMutation = api.mongo.create.useMutation();
|
const mongoMutation = api.mongo.create.useMutation();
|
||||||
@@ -187,14 +169,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
const mariadbMutation = api.mariadb.create.useMutation();
|
const mariadbMutation = api.mariadb.create.useMutation();
|
||||||
const mysqlMutation = api.mysql.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 hasServers = servers && servers.length > 0;
|
||||||
// Show dropdown logic based on cloud environment
|
|
||||||
// Cloud: show only if there are remote servers (no Dokploy option)
|
|
||||||
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
|
||||||
const shouldShowServerDropdown = hasServers;
|
|
||||||
|
|
||||||
const form = useForm<AddDatabase>({
|
const form = useForm<AddDatabase>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -228,8 +203,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
appName: data.appName,
|
appName: data.appName,
|
||||||
dockerImage: defaultDockerImage,
|
dockerImage: defaultDockerImage,
|
||||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
projectId,
|
||||||
environmentId,
|
serverId: data.serverId,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,7 +216,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
|
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
serverId: data.serverId,
|
||||||
});
|
});
|
||||||
} else if (data.type === "mongo") {
|
} else if (data.type === "mongo") {
|
||||||
promise = mongoMutation.mutateAsync({
|
promise = mongoMutation.mutateAsync({
|
||||||
@@ -249,24 +224,25 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
databasePassword: data.databasePassword,
|
databasePassword: data.databasePassword,
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
serverId: data.serverId,
|
||||||
replicaSets: data.replicaSets,
|
replicaSets: data.replicaSets,
|
||||||
});
|
});
|
||||||
} else if (data.type === "redis") {
|
} else if (data.type === "redis") {
|
||||||
promise = redisMutation.mutateAsync({
|
promise = redisMutation.mutateAsync({
|
||||||
...commonParams,
|
...commonParams,
|
||||||
databasePassword: data.databasePassword,
|
databasePassword: data.databasePassword,
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
serverId: data.serverId,
|
||||||
|
projectId,
|
||||||
});
|
});
|
||||||
} else if (data.type === "mariadb") {
|
} else if (data.type === "mariadb") {
|
||||||
promise = mariadbMutation.mutateAsync({
|
promise = mariadbMutation.mutateAsync({
|
||||||
...commonParams,
|
...commonParams,
|
||||||
databasePassword: data.databasePassword,
|
databasePassword: data.databasePassword,
|
||||||
databaseRootPassword: data.databaseRootPassword || "",
|
databaseRootPassword: data.databaseRootPassword,
|
||||||
databaseName: data.databaseName || "mariadb",
|
databaseName: data.databaseName || "mariadb",
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
serverId: data.serverId,
|
||||||
});
|
});
|
||||||
} else if (data.type === "mysql") {
|
} else if (data.type === "mysql") {
|
||||||
promise = mysqlMutation.mutateAsync({
|
promise = mysqlMutation.mutateAsync({
|
||||||
@@ -275,8 +251,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
databaseName: data.databaseName || "mysql",
|
databaseName: data.databaseName || "mysql",
|
||||||
databaseUser:
|
databaseUser:
|
||||||
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
data.databaseUser || databasesUserDefaultPlaceholder[data.type],
|
||||||
serverId: data.serverId === "dokploy" ? null : data.serverId,
|
databaseRootPassword: data.databaseRootPassword,
|
||||||
databaseRootPassword: data.databaseRootPassword || "",
|
serverId: data.serverId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,9 +271,8 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
databaseUser: "",
|
databaseUser: "",
|
||||||
});
|
});
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
// Invalidate the project query to refresh the environment data
|
await utils.project.one.invalidate({
|
||||||
await utils.environment.one.invalidate({
|
projectId,
|
||||||
environmentId,
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -407,7 +382,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{shouldShowServerDropdown && (
|
{hasServers && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serverId"
|
name="serverId"
|
||||||
@@ -416,29 +391,13 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
<FormLabel>Select a Server</FormLabel>
|
<FormLabel>Select a Server</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={
|
defaultValue={field.value || ""}
|
||||||
field.value || (!isCloud ? "dokploy" : undefined)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue
|
<SelectValue placeholder="Select a Server" />
|
||||||
placeholder={
|
|
||||||
!isCloud ? "Dokploy" : "Select a Server"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
|
||||||
<SelectItem value="dokploy">
|
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
|
||||||
<span>Dokploy</span>
|
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
|
||||||
Default
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -448,7 +407,7 @@ export const AddDatabase = ({ environmentId, projectName }: Props) => {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
Servers ({servers?.length})
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ import { api } from "@/utils/api";
|
|||||||
const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url";
|
const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environmentId: string;
|
projectId: string;
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [viewMode, setViewMode] = useState<"detailed" | "icon">("detailed");
|
const [viewMode, setViewMode] = useState<"detailed" | "icon">("detailed");
|
||||||
@@ -91,9 +91,6 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get environment data to extract projectId
|
|
||||||
const { data: environment } = api.environment.one.useQuery({ environmentId });
|
|
||||||
|
|
||||||
// Save to localStorage when customBaseUrl changes
|
// Save to localStorage when customBaseUrl changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (customBaseUrl) {
|
if (customBaseUrl) {
|
||||||
@@ -141,10 +138,6 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
// Show dropdown logic based on cloud environment
|
|
||||||
// Cloud: show only if there are remote servers (no Dokploy option)
|
|
||||||
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
|
||||||
const shouldShowServerDropdown = hasServers;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
@@ -434,7 +427,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
project.
|
project.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
|
|
||||||
{shouldShowServerDropdown && (
|
{hasServers && (
|
||||||
<div>
|
<div>
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -463,29 +456,12 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setServerId(e);
|
setServerId(e);
|
||||||
}}
|
}}
|
||||||
defaultValue={
|
|
||||||
!isCloud ? "dokploy" : undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue
|
<SelectValue placeholder="Select a Server" />
|
||||||
placeholder={
|
|
||||||
!isCloud ? "Dokploy" : "Select a Server"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
|
||||||
<SelectItem value="dokploy">
|
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
|
||||||
<span>Dokploy</span>
|
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
|
||||||
Default
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
@@ -500,8 +476,7 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>
|
||||||
Servers (
|
Servers ({servers?.length})
|
||||||
{servers?.length + (!isCloud ? 1 : 0)})
|
|
||||||
</SelectLabel>
|
</SelectLabel>
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -515,20 +490,16 @@ export const AddTemplate = ({ environmentId, baseUrl }: Props) => {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const promise = mutateAsync({
|
const promise = mutateAsync({
|
||||||
serverId:
|
projectId,
|
||||||
serverId === "dokploy"
|
serverId: serverId || undefined,
|
||||||
? undefined
|
|
||||||
: serverId,
|
|
||||||
environmentId,
|
|
||||||
id: template.id,
|
id: template.id,
|
||||||
baseUrl: customBaseUrl,
|
baseUrl: customBaseUrl,
|
||||||
});
|
});
|
||||||
toast.promise(promise, {
|
toast.promise(promise, {
|
||||||
loading: "Setting up...",
|
loading: "Setting up...",
|
||||||
success: () => {
|
success: () => {
|
||||||
// Invalidate the project query to refresh the environment data
|
utils.project.one.invalidate({
|
||||||
utils.environment.one.invalidate({
|
projectId,
|
||||||
environmentId,
|
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
return `${template.name} template created successfully`;
|
return `${template.name} template created successfully`;
|
||||||
|
|||||||
@@ -1,446 +0,0 @@
|
|||||||
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<typeof findEnvironmentsByProjectId>
|
|
||||||
>[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<Environment | null>(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 (
|
|
||||||
<>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" className="h-auto p-2 font-normal">
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
<span className="text-muted-foreground">/</span>
|
|
||||||
<span>{currentEnv?.name || "Select Environment"}</span>
|
|
||||||
<ChevronDownIcon className="h-4 w-4 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent className="w-[300px]" align="start">
|
|
||||||
<DropdownMenuLabel>Environments</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
|
|
||||||
{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 (
|
|
||||||
<div
|
|
||||||
key={environment.environmentId}
|
|
||||||
className="flex items-center"
|
|
||||||
>
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="flex-1 cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
router.push(
|
|
||||||
`/dashboard/project/${projectId}/environment/${environment.environmentId}`,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between w-full">
|
|
||||||
<span>
|
|
||||||
{environment.name} ({servicesCount})
|
|
||||||
</span>
|
|
||||||
{environment.environmentId === currentEnvironmentId && (
|
|
||||||
<div className="w-2 h-2 bg-blue-500 rounded-full" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
|
|
||||||
{/* Action buttons for non-production environments */}
|
|
||||||
<EnvironmentVariables environmentId={environment.environmentId}>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-6 w-6 p-0"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Terminal className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</EnvironmentVariables>
|
|
||||||
{environment.name !== "production" && (
|
|
||||||
<div className="flex items-center gap-1 px-2">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-6 w-6 p-0"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
openEditDialog(environment);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PencilIcon className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="h-6 w-6 p-0 text-red-600 hover:text-red-700"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
openDeleteDialog(environment);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TrashIcon className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => setIsCreateDialogOpen(true)}
|
|
||||||
>
|
|
||||||
<PlusIcon className="h-4 w-4 mr-2" />
|
|
||||||
Create Environment
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Create Environment</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Create a new environment for your project.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="name">Name</Label>
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
placeholder="Environment name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="description">Description (optional)</Label>
|
|
||||||
<Textarea
|
|
||||||
id="description"
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
placeholder="Environment description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
setIsCreateDialogOpen(false);
|
|
||||||
setName("");
|
|
||||||
setDescription("");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleCreateEnvironment}
|
|
||||||
disabled={!name.trim() || createEnvironment.isLoading}
|
|
||||||
>
|
|
||||||
{createEnvironment.isLoading ? "Creating..." : "Create"}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* Edit Environment Dialog */}
|
|
||||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Edit Environment</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Update the environment details.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="edit-name">Name</Label>
|
|
||||||
<Input
|
|
||||||
id="edit-name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
placeholder="Environment name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label htmlFor="edit-description">Description (optional)</Label>
|
|
||||||
<Textarea
|
|
||||||
id="edit-description"
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
placeholder="Environment description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
setIsEditDialogOpen(false);
|
|
||||||
setSelectedEnvironment(null);
|
|
||||||
setName("");
|
|
||||||
setDescription("");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={handleUpdateEnvironment}
|
|
||||||
disabled={!name.trim() || updateEnvironment.isLoading}
|
|
||||||
>
|
|
||||||
{updateEnvironment.isLoading ? "Updating..." : "Update"}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* Delete Environment Dialog */}
|
|
||||||
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Delete Environment</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete the environment "
|
|
||||||
{selectedEnvironment?.name}"? This action cannot be undone and
|
|
||||||
will also delete all services in this environment.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
{haveServices && (
|
|
||||||
<AlertBlock type="warning">
|
|
||||||
This environment have active services, please delete them first.
|
|
||||||
</AlertBlock>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
setIsDeleteDialogOpen(false);
|
|
||||||
setSelectedEnvironment(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={handleDeleteEnvironment}
|
|
||||||
disabled={
|
|
||||||
deleteEnvironment.isLoading ||
|
|
||||||
haveServices ||
|
|
||||||
!selectedEnvironment
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{deleteEnvironment.isLoading ? "Deleting..." : "Delete"}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -25,12 +25,7 @@ const examples = [
|
|||||||
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
||||||
// Get servers from the API
|
// Get servers from the API
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
|
||||||
const hasServers = servers && servers.length > 0;
|
const hasServers = servers && servers.length > 0;
|
||||||
// Show dropdown logic based on cloud environment
|
|
||||||
// Cloud: show only if there are remote servers (no Dokploy option)
|
|
||||||
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
|
||||||
const shouldShowServerDropdown = hasServers;
|
|
||||||
|
|
||||||
const handleExampleClick = (example: string) => {
|
const handleExampleClick = (example: string) => {
|
||||||
setTemplateInfo({ ...templateInfo, userInput: example });
|
setTemplateInfo({ ...templateInfo, userInput: example });
|
||||||
@@ -53,58 +48,34 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{shouldShowServerDropdown && (
|
{hasServers && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="server-deploy">
|
<Label htmlFor="server-deploy">
|
||||||
Select the server where you want to deploy (optional)
|
Select the server where you want to deploy (optional)
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={
|
value={templateInfo.server?.serverId}
|
||||||
templateInfo.server?.serverId ||
|
|
||||||
(!isCloud ? "dokploy" : undefined)
|
|
||||||
}
|
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
if (value === "dokploy") {
|
const server = servers?.find((s) => s.serverId === value);
|
||||||
|
if (server) {
|
||||||
setTemplateInfo({
|
setTemplateInfo({
|
||||||
...templateInfo,
|
...templateInfo,
|
||||||
server: undefined,
|
server: server,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
const server = servers?.find((s) => s.serverId === value);
|
|
||||||
if (server) {
|
|
||||||
setTemplateInfo({
|
|
||||||
...templateInfo,
|
|
||||||
server: server,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full">
|
<SelectTrigger className="w-full">
|
||||||
<SelectValue
|
<SelectValue placeholder="Select a server" />
|
||||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{!isCloud && (
|
|
||||||
<SelectItem value="dokploy">
|
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
|
||||||
<span>Dokploy</span>
|
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
|
||||||
Default
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem key={server.serverId} value={server.serverId}>
|
<SelectItem key={server.serverId} value={server.serverId}>
|
||||||
{server.name}
|
{server.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>
|
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
|
||||||
</SelectLabel>
|
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -90,11 +90,11 @@ export const { useStepper, steps, Scoped } = defineStepper(
|
|||||||
);
|
);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environmentId: string;
|
projectId: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TemplateGenerator = ({ environmentId }: Props) => {
|
export const TemplateGenerator = ({ projectId }: Props) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const stepper = useStepper();
|
const stepper = useStepper();
|
||||||
const { data: aiSettings } = api.ai.getAll.useQuery();
|
const { data: aiSettings } = api.ai.getAll.useQuery();
|
||||||
@@ -121,7 +121,7 @@ export const TemplateGenerator = ({ environmentId }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
environmentId: environmentId,
|
projectId,
|
||||||
id: templateInfo.details?.id || "",
|
id: templateInfo.details?.id || "",
|
||||||
name: templateInfo?.details?.name || "",
|
name: templateInfo?.details?.name || "",
|
||||||
description: templateInfo?.details?.shortDescription || "",
|
description: templateInfo?.details?.shortDescription || "",
|
||||||
@@ -138,9 +138,8 @@ export const TemplateGenerator = ({ environmentId }: Props) => {
|
|||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Compose Created");
|
toast.success("Compose Created");
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
// Invalidate the project query to refresh the environment data
|
await utils.project.one.invalidate({
|
||||||
await utils.environment.one.invalidate({
|
projectId,
|
||||||
environmentId,
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|||||||
@@ -15,13 +15,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
export type Services = {
|
export type Services = {
|
||||||
@@ -43,35 +36,23 @@ export type Services = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface DuplicateProjectProps {
|
interface DuplicateProjectProps {
|
||||||
environmentId: string;
|
projectId: string;
|
||||||
services: Services[];
|
services: Services[];
|
||||||
selectedServiceIds: string[];
|
selectedServiceIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DuplicateProject = ({
|
export const DuplicateProject = ({
|
||||||
environmentId,
|
projectId,
|
||||||
services,
|
services,
|
||||||
selectedServiceIds,
|
selectedServiceIds,
|
||||||
}: DuplicateProjectProps) => {
|
}: DuplicateProjectProps) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [duplicateType, setDuplicateType] = useState("new-project"); // "new-project" or "existing-environment"
|
const [duplicateType, setDuplicateType] = useState("new-project"); // "new-project" or "same-project"
|
||||||
const [selectedTargetProject, setSelectedTargetProject] =
|
|
||||||
useState<string>("");
|
|
||||||
const [selectedTargetEnvironment, setSelectedTargetEnvironment] =
|
|
||||||
useState<string>("");
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Queries for project and environment selection
|
|
||||||
const { data: allProjects } = api.project.all.useQuery();
|
|
||||||
const { data: selectedProjectEnvironments } =
|
|
||||||
api.environment.byProjectId.useQuery(
|
|
||||||
{ projectId: selectedTargetProject },
|
|
||||||
{ enabled: !!selectedTargetProject },
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedServices = services.filter((service) =>
|
const selectedServices = services.filter((service) =>
|
||||||
selectedServiceIds.includes(service.id),
|
selectedServiceIds.includes(service.id),
|
||||||
);
|
);
|
||||||
@@ -87,9 +68,7 @@ export const DuplicateProject = ({
|
|||||||
);
|
);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
if (duplicateType === "new-project") {
|
if (duplicateType === "new-project") {
|
||||||
router.push(
|
router.push(`/dashboard/project/${newProject.projectId}`);
|
||||||
`/dashboard/project/${newProject?.projectId}/environment/${newProject?.environmentId}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -103,20 +82,8 @@ export const DuplicateProject = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duplicateType === "existing-environment") {
|
|
||||||
if (!selectedTargetProject) {
|
|
||||||
toast.error("Please select a target project");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!selectedTargetEnvironment) {
|
|
||||||
toast.error("Please select a target environment");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Update duplicate API to support targetProjectId and targetEnvironmentId
|
|
||||||
await duplicateProject({
|
await duplicateProject({
|
||||||
sourceEnvironmentId: selectedTargetEnvironment,
|
sourceProjectId: projectId,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
includeServices: true,
|
includeServices: true,
|
||||||
@@ -124,7 +91,7 @@ export const DuplicateProject = ({
|
|||||||
id: service.id,
|
id: service.id,
|
||||||
type: service.type,
|
type: service.type,
|
||||||
})),
|
})),
|
||||||
duplicateInSameProject: duplicateType === "existing-environment",
|
duplicateInSameProject: duplicateType === "same-project",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -138,8 +105,6 @@ export const DuplicateProject = ({
|
|||||||
setName("");
|
setName("");
|
||||||
setDescription("");
|
setDescription("");
|
||||||
setDuplicateType("new-project");
|
setDuplicateType("new-project");
|
||||||
setSelectedTargetProject("");
|
|
||||||
setSelectedTargetEnvironment("");
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -162,14 +127,7 @@ export const DuplicateProject = ({
|
|||||||
<Label>Duplicate to</Label>
|
<Label>Duplicate to</Label>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={duplicateType}
|
value={duplicateType}
|
||||||
onValueChange={(value) => {
|
onValueChange={setDuplicateType}
|
||||||
setDuplicateType(value);
|
|
||||||
// Reset selections when changing type
|
|
||||||
if (value !== "existing-environment") {
|
|
||||||
setSelectedTargetProject("");
|
|
||||||
setSelectedTargetEnvironment("");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="grid gap-2"
|
className="grid gap-2"
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
@@ -177,13 +135,8 @@ export const DuplicateProject = ({
|
|||||||
<Label htmlFor="new-project">New project</Label>
|
<Label htmlFor="new-project">New project</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<RadioGroupItem
|
<RadioGroupItem value="same-project" id="same-project" />
|
||||||
value="existing-environment"
|
<Label htmlFor="same-project">Same project</Label>
|
||||||
id="existing-environment"
|
|
||||||
/>
|
|
||||||
<Label htmlFor="existing-environment">
|
|
||||||
Existing environment
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,74 +165,6 @@ export const DuplicateProject = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{duplicateType === "existing-environment" && (
|
|
||||||
<>
|
|
||||||
{allProjects?.filter((p) => p.projectId !== environmentId)
|
|
||||||
.length === 0 ? (
|
|
||||||
<div className="flex flex-col items-center justify-center gap-2 py-4 text-center">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
No other projects available. Create a new project first.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{/* Step 1: Select Project */}
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label>Target Project</Label>
|
|
||||||
<Select
|
|
||||||
value={selectedTargetProject}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
setSelectedTargetProject(value);
|
|
||||||
setSelectedTargetEnvironment(""); // Reset environment when project changes
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select target project" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{allProjects
|
|
||||||
?.filter((p) => p.projectId !== environmentId)
|
|
||||||
.map((project) => (
|
|
||||||
<SelectItem
|
|
||||||
key={project.projectId}
|
|
||||||
value={project.projectId}
|
|
||||||
>
|
|
||||||
{project.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Step 2: Select Environment (only show if project is selected) */}
|
|
||||||
{selectedTargetProject && (
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label>Target Environment</Label>
|
|
||||||
<Select
|
|
||||||
value={selectedTargetEnvironment}
|
|
||||||
onValueChange={setSelectedTargetEnvironment}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select target environment" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{selectedProjectEnvironments?.map((env) => (
|
|
||||||
<SelectItem
|
|
||||||
key={env.environmentId}
|
|
||||||
value={env.environmentId}
|
|
||||||
>
|
|
||||||
{env.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Selected services to duplicate</Label>
|
<Label>Selected services to duplicate</Label>
|
||||||
<div className="space-y-2 max-h-[200px] overflow-y-auto border rounded-md p-4">
|
<div className="space-y-2 max-h-[200px] overflow-y-auto border rounded-md p-4">
|
||||||
@@ -302,26 +187,18 @@ export const DuplicateProject = ({
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={handleDuplicate} disabled={isLoading}>
|
||||||
onClick={handleDuplicate}
|
|
||||||
disabled={
|
|
||||||
isLoading ||
|
|
||||||
(duplicateType === "new-project" && !name) ||
|
|
||||||
(duplicateType === "existing-environment" &&
|
|
||||||
(!selectedTargetProject || !selectedTargetEnvironment))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
{duplicateType === "new-project"
|
{duplicateType === "new-project"
|
||||||
? "Duplicating to new project..."
|
? "Duplicating project..."
|
||||||
: "Duplicating to environment..."}
|
: "Duplicating services..."}
|
||||||
</>
|
</>
|
||||||
) : duplicateType === "new-project" ? (
|
) : duplicateType === "new-project" ? (
|
||||||
"Duplicate to new project"
|
"Duplicate project"
|
||||||
) : (
|
) : (
|
||||||
"Duplicate to environment"
|
"Duplicate services"
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Terminal } 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 {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
|
|
||||||
const updateEnvironmentSchema = z.object({
|
|
||||||
env: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
type UpdateEnvironment = z.infer<typeof updateEnvironmentSchema>;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
environmentId: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EnvironmentVariables = ({ environmentId, children }: Props) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const { mutateAsync, error, isError, isLoading } =
|
|
||||||
api.environment.update.useMutation();
|
|
||||||
const { data } = api.environment.one.useQuery(
|
|
||||||
{
|
|
||||||
environmentId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!environmentId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const form = useForm<UpdateEnvironment>({
|
|
||||||
defaultValues: {
|
|
||||||
env: data?.env ?? "",
|
|
||||||
},
|
|
||||||
resolver: zodResolver(updateEnvironmentSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
form.reset({
|
|
||||||
env: data.env ?? "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data, form, form.reset]);
|
|
||||||
|
|
||||||
const onSubmit = async (formData: UpdateEnvironment) => {
|
|
||||||
await mutateAsync({
|
|
||||||
env: formData.env || "",
|
|
||||||
environmentId: environmentId,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
toast.success("Environment variables updated successfully");
|
|
||||||
utils.environment.one.invalidate({ environmentId });
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error updating the environment variables");
|
|
||||||
})
|
|
||||||
.finally(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
{children ?? (
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="w-full cursor-pointer space-x-3"
|
|
||||||
onSelect={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<Terminal className="size-4" />
|
|
||||||
<span>Environment Variables</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-6xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Environment Variables</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Update the environment variables that are accessible to all services
|
|
||||||
in this environment.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
|
||||||
<AlertBlock type="info">
|
|
||||||
Use this syntax to reference environment-level variables in your
|
|
||||||
service environments:{" "}
|
|
||||||
<code>API_URL=${"{{environment.API_URL}}"}</code>
|
|
||||||
</AlertBlock>
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<div className="grid items-center gap-4">
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="grid w-full gap-4 "
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="env"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Environment variables</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<CodeEditor
|
|
||||||
lineWrapping
|
|
||||||
language="properties"
|
|
||||||
wrapperClassName="h-[35rem] font-mono"
|
|
||||||
placeholder={`NODE_ENV=development
|
|
||||||
DATABASE_URL=postgresql://localhost:5432/mydb
|
|
||||||
API_KEY=your-api-key-here
|
|
||||||
|
|
||||||
`}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
<FormMessage />
|
|
||||||
</pre>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button isLoading={isLoading} type="submit">
|
|
||||||
Update
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -101,18 +101,7 @@ export const HandleProject = ({ projectId }: Props) => {
|
|||||||
toast.success(projectId ? "Project Updated" : "Project Created");
|
toast.success(projectId ? "Project Updated" : "Project Created");
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
const projectIdToUse =
|
router.push(`/dashboard/project/${data?.projectId}`);
|
||||||
data && "project" in data ? data.project.projectId : undefined;
|
|
||||||
const environmentIdToUse =
|
|
||||||
data && "environment" in data
|
|
||||||
? data.environment.environmentId
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (environmentIdToUse && projectIdToUse) {
|
|
||||||
router.push(
|
|
||||||
`/dashboard/project/${projectIdToUse}/environment/${environmentIdToUse}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -96,8 +96,22 @@ export const ShowProjects = () => {
|
|||||||
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||||
break;
|
break;
|
||||||
case "services": {
|
case "services": {
|
||||||
const aTotalServices = a.environments.length;
|
const aTotalServices =
|
||||||
const bTotalServices = b.environments.length;
|
a.mariadb.length +
|
||||||
|
a.mongo.length +
|
||||||
|
a.mysql.length +
|
||||||
|
a.postgres.length +
|
||||||
|
a.redis.length +
|
||||||
|
a.applications.length +
|
||||||
|
a.compose.length;
|
||||||
|
const bTotalServices =
|
||||||
|
b.mariadb.length +
|
||||||
|
b.mongo.length +
|
||||||
|
b.mysql.length +
|
||||||
|
b.postgres.length +
|
||||||
|
b.redis.length +
|
||||||
|
b.applications.length +
|
||||||
|
b.compose.length;
|
||||||
comparison = aTotalServices - bTotalServices;
|
comparison = aTotalServices - bTotalServices;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -144,13 +158,12 @@ export const ShowProjects = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="flex max-sm:flex-col gap-4 items-center w-full">
|
<div className="flex max-sm:flex-col gap-4 items-center w-full">
|
||||||
<div className="flex-1 relative max-sm:w-full">
|
<div className="flex-1 relative max-sm:w-full">
|
||||||
<FocusShortcutInput
|
<Input
|
||||||
placeholder="Filter projects..."
|
placeholder="Filter projects..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
className="pr-10"
|
className="pr-10"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
<Search className="absolute right-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 min-w-48 max-sm:w-full">
|
<div className="flex items-center gap-2 min-w-48 max-sm:w-full">
|
||||||
@@ -188,40 +201,23 @@ export const ShowProjects = () => {
|
|||||||
)}
|
)}
|
||||||
<div className="w-full grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-5 flex-wrap gap-5">
|
<div className="w-full grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-5 flex-wrap gap-5">
|
||||||
{filteredProjects?.map((project) => {
|
{filteredProjects?.map((project) => {
|
||||||
const emptyServices = project?.environments
|
const emptyServices =
|
||||||
.map(
|
project?.mariadb.length === 0 &&
|
||||||
(env) =>
|
project?.mongo.length === 0 &&
|
||||||
env.applications.length === 0 &&
|
project?.mysql.length === 0 &&
|
||||||
env.mariadb.length === 0 &&
|
project?.postgres.length === 0 &&
|
||||||
env.mongo.length === 0 &&
|
project?.redis.length === 0 &&
|
||||||
env.mysql.length === 0 &&
|
project?.applications.length === 0 &&
|
||||||
env.postgres.length === 0 &&
|
project?.compose.length === 0;
|
||||||
env.redis.length === 0 &&
|
|
||||||
env.applications.length === 0 &&
|
|
||||||
env.compose.length === 0,
|
|
||||||
)
|
|
||||||
.every(Boolean);
|
|
||||||
|
|
||||||
const totalServices = project?.environments
|
const totalServices =
|
||||||
.map(
|
project?.mariadb.length +
|
||||||
(env) =>
|
project?.mongo.length +
|
||||||
env.mariadb.length +
|
project?.mysql.length +
|
||||||
env.mongo.length +
|
project?.postgres.length +
|
||||||
env.mysql.length +
|
project?.redis.length +
|
||||||
env.postgres.length +
|
project?.applications.length +
|
||||||
env.redis.length +
|
project?.compose.length;
|
||||||
env.applications.length +
|
|
||||||
env.compose.length,
|
|
||||||
)
|
|
||||||
.reduce((acc, curr) => acc + curr, 0);
|
|
||||||
|
|
||||||
const haveServicesWithDomains = project?.environments
|
|
||||||
.map(
|
|
||||||
(env) =>
|
|
||||||
env.applications.length > 0 ||
|
|
||||||
env.compose.length > 0,
|
|
||||||
)
|
|
||||||
.some(Boolean);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -229,10 +225,11 @@ export const ShowProjects = () => {
|
|||||||
className="w-full lg:max-w-md"
|
className="w-full lg:max-w-md"
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href={`/dashboard/project/${project.projectId}/environment/${project?.environments?.[0]?.environmentId}`}
|
href={`/dashboard/project/${project.projectId}`}
|
||||||
>
|
>
|
||||||
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
|
<Card className="group relative w-full h-full bg-transparent transition-colors hover:bg-border">
|
||||||
{haveServicesWithDomains ? (
|
{project.applications.length > 0 ||
|
||||||
|
project.compose.length > 0 ? (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
@@ -247,51 +244,44 @@ export const ShowProjects = () => {
|
|||||||
className="w-[200px] space-y-2 overflow-y-auto max-h-[400px]"
|
className="w-[200px] space-y-2 overflow-y-auto max-h-[400px]"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{project.environments.some(
|
{project.applications.length > 0 && (
|
||||||
(env) => env.applications.length > 0,
|
|
||||||
) && (
|
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuLabel>
|
<DropdownMenuLabel>
|
||||||
Applications
|
Applications
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
{project.environments.map((env) =>
|
{project.applications.map((app) => (
|
||||||
env.applications.map((app) => (
|
<div key={app.applicationId}>
|
||||||
<div key={app.applicationId}>
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
||||||
|
{app.name}
|
||||||
|
<StatusTooltip
|
||||||
|
status={app.applicationStatus}
|
||||||
|
/>
|
||||||
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup>
|
{app.domains.map((domain) => (
|
||||||
<DropdownMenuLabel className="font-normal capitalize text-xs flex items-center justify-between">
|
<DropdownMenuItem
|
||||||
{app.name}
|
key={domain.domainId}
|
||||||
<StatusTooltip
|
asChild
|
||||||
status={
|
>
|
||||||
app.applicationStatus
|
<Link
|
||||||
}
|
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||||
/>
|
target="_blank"
|
||||||
</DropdownMenuLabel>
|
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||||
<DropdownMenuSeparator />
|
|
||||||
{app.domains.map((domain) => (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={domain.domainId}
|
|
||||||
asChild
|
|
||||||
>
|
>
|
||||||
<Link
|
<span className="truncate">
|
||||||
className="space-x-4 text-xs cursor-pointer justify-between"
|
{domain.host}
|
||||||
target="_blank"
|
</span>
|
||||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
>
|
</Link>
|
||||||
<span className="truncate">
|
</DropdownMenuItem>
|
||||||
{domain.host}
|
))}
|
||||||
</span>
|
</DropdownMenuGroup>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
</div>
|
||||||
</Link>
|
))}
|
||||||
</DropdownMenuItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</div>
|
|
||||||
)),
|
|
||||||
)}
|
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
)}
|
)}
|
||||||
{/*
|
|
||||||
{project.compose.length > 0 && (
|
{project.compose.length > 0 && (
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuLabel>
|
<DropdownMenuLabel>
|
||||||
@@ -329,7 +319,7 @@ export const ShowProjects = () => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
)} */}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -96,9 +96,9 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl">External Credentials</CardTitle>
|
<CardTitle className="text-xl">External Credentials</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
In order to make the database reachable through the internet, you
|
In order to make the database reachable trought internet is
|
||||||
must set a port and ensure that the port is not being used by
|
required to set a port, make sure the port is not used by another
|
||||||
another application or database
|
application or database
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react";
|
import { BookIcon, CircuitBoard, GlobeIcon } from "lucide-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
|
||||||
extractServices,
|
|
||||||
type Services,
|
|
||||||
} from "@/components/dashboard/settings/users/add-permissions";
|
|
||||||
import {
|
import {
|
||||||
MariadbIcon,
|
MariadbIcon,
|
||||||
MongodbIcon,
|
MongodbIcon,
|
||||||
@@ -24,34 +20,13 @@ import {
|
|||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
} from "@/components/ui/command";
|
} from "@/components/ui/command";
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
import {
|
||||||
|
extractServices,
|
||||||
|
type Services,
|
||||||
|
} from "@/pages/dashboard/project/[projectId]";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { StatusTooltip } from "../shared/status-tooltip";
|
import { StatusTooltip } from "../shared/status-tooltip";
|
||||||
|
|
||||||
// Extended Services type to include environmentId and environmentName for search navigation
|
|
||||||
type SearchServices = Services & {
|
|
||||||
environmentId: string;
|
|
||||||
environmentName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const extractAllServicesFromProject = (project: any): SearchServices[] => {
|
|
||||||
const allServices: SearchServices[] = [];
|
|
||||||
|
|
||||||
// Iterate through all environments in the project
|
|
||||||
project.environments?.forEach((environment: any) => {
|
|
||||||
const environmentServices = extractServices(environment);
|
|
||||||
const servicesWithEnvironmentId: SearchServices[] = environmentServices.map(
|
|
||||||
(service) => ({
|
|
||||||
...service,
|
|
||||||
environmentId: environment.environmentId,
|
|
||||||
environmentName: environment.name,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
allServices.push(...servicesWithEnvironmentId);
|
|
||||||
});
|
|
||||||
|
|
||||||
return allServices;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SearchCommand = () => {
|
export const SearchCommand = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
@@ -88,42 +63,31 @@ export const SearchCommand = () => {
|
|||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
<CommandGroup heading={"Projects"}>
|
<CommandGroup heading={"Projects"}>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
{data?.map((project) => {
|
{data?.map((project) => (
|
||||||
const productionEnvironment = project.environments.find(
|
<CommandItem
|
||||||
(environment) => environment.name === "production",
|
key={project.projectId}
|
||||||
);
|
onSelect={() => {
|
||||||
|
router.push(`/dashboard/project/${project.projectId}`);
|
||||||
if (!productionEnvironment) return null;
|
setOpen(false);
|
||||||
|
}}
|
||||||
return (
|
>
|
||||||
<CommandItem
|
<BookIcon className="size-4 text-muted-foreground mr-2" />
|
||||||
key={project.projectId}
|
{project.name}
|
||||||
onSelect={() => {
|
</CommandItem>
|
||||||
router.push(
|
))}
|
||||||
`/dashboard/project/${project.projectId}/environment/${productionEnvironment!.environmentId}`,
|
|
||||||
);
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BookIcon className="size-4 text-muted-foreground mr-2" />
|
|
||||||
{project.name} / {productionEnvironment!.name}
|
|
||||||
</CommandItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
<CommandGroup heading={"Services"}>
|
<CommandGroup heading={"Services"}>
|
||||||
<CommandList>
|
<CommandList>
|
||||||
{data?.map((project) => {
|
{data?.map((project) => {
|
||||||
const applications: SearchServices[] =
|
const applications: Services[] = extractServices(project);
|
||||||
extractAllServicesFromProject(project);
|
|
||||||
return applications.map((application) => (
|
return applications.map((application) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={application.id}
|
key={application.id}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
router.push(
|
router.push(
|
||||||
`/dashboard/project/${project.projectId}/environment/${application.environmentId}/services/${application.type}/${application.id}`,
|
`/dashboard/project/${project.projectId}/services/${application.type}/${application.id}`,
|
||||||
);
|
);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
@@ -150,8 +114,7 @@ export const SearchCommand = () => {
|
|||||||
<CircuitBoard className="h-6 w-6 mr-2" />
|
<CircuitBoard className="h-6 w-6 mr-2" />
|
||||||
)}
|
)}
|
||||||
<span className="flex-grow">
|
<span className="flex-grow">
|
||||||
{project.name} / {application.environmentName} /{" "}
|
{project.name} / {application.name}{" "}
|
||||||
{application.name}{" "}
|
|
||||||
<div style={{ display: "none" }}>{application.id}</div>
|
<div style={{ display: "none" }}>{application.id}</div>
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -65,11 +65,6 @@ export const AddCertificate = () => {
|
|||||||
const { mutateAsync, isError, error, isLoading } =
|
const { mutateAsync, isError, error, isLoading } =
|
||||||
api.certificates.create.useMutation();
|
api.certificates.create.useMutation();
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
const hasServers = servers && servers.length > 0;
|
|
||||||
// Show dropdown logic based on cloud environment
|
|
||||||
// Cloud: show only if there are remote servers (no Dokploy option)
|
|
||||||
// Self-hosted: show only if there are remote servers (Dokploy is default, hide if no remote servers)
|
|
||||||
const shouldShowServerDropdown = hasServers;
|
|
||||||
|
|
||||||
const form = useForm<AddCertificate>({
|
const form = useForm<AddCertificate>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -90,7 +85,7 @@ export const AddCertificate = () => {
|
|||||||
certificateData: data.certificateData,
|
certificateData: data.certificateData,
|
||||||
privateKey: data.privateKey,
|
privateKey: data.privateKey,
|
||||||
autoRenew: data.autoRenew,
|
autoRenew: data.autoRenew,
|
||||||
serverId: data.serverId === "dokploy" ? undefined : data.serverId,
|
serverId: data.serverId,
|
||||||
organizationId: "",
|
organizationId: "",
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -179,70 +174,52 @@ export const AddCertificate = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{shouldShowServerDropdown && (
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="serverId"
|
||||||
name="serverId"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<TooltipProvider delayDuration={0}>
|
||||||
<TooltipProvider delayDuration={0}>
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
Select a Server {!isCloud && "(Optional)"}
|
||||||
Select a Server {!isCloud && "(Optional)"}
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
<HelpCircle className="size-4 text-muted-foreground" />
|
</FormLabel>
|
||||||
</FormLabel>
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
</Tooltip>
|
||||||
</Tooltip>
|
</TooltipProvider>
|
||||||
</TooltipProvider>
|
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={
|
defaultValue={field.value}
|
||||||
field.value || (!isCloud ? "dokploy" : undefined)
|
>
|
||||||
}
|
<SelectTrigger>
|
||||||
>
|
<SelectValue placeholder="Select a Server" />
|
||||||
<SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectValue
|
<SelectContent>
|
||||||
placeholder={!isCloud ? "Dokploy" : "Select a Server"}
|
<SelectGroup>
|
||||||
/>
|
{servers?.map((server) => (
|
||||||
</SelectTrigger>
|
<SelectItem
|
||||||
<SelectContent>
|
key={server.serverId}
|
||||||
<SelectGroup>
|
value={server.serverId}
|
||||||
{!isCloud && (
|
>
|
||||||
<SelectItem value="dokploy">
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<span>{server.name}</span>
|
||||||
<span>Dokploy</span>
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
{server.ipAddress}
|
||||||
Default
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</SelectItem>
|
</span>
|
||||||
)}
|
</SelectItem>
|
||||||
{servers?.map((server) => (
|
))}
|
||||||
<SelectItem
|
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||||
key={server.serverId}
|
</SelectGroup>
|
||||||
value={server.serverId}
|
</SelectContent>
|
||||||
>
|
</Select>
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<FormMessage />
|
||||||
<span>{server.name}</span>
|
</FormItem>
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
)}
|
||||||
{server.ipAddress}
|
/>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
<SelectLabel>
|
|
||||||
Servers ({servers?.length + (!isCloud ? 1 : 0)})
|
|
||||||
</SelectLabel>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<DialogFooter className="flex w-full flex-row !justify-end">
|
<DialogFooter className="flex w-full flex-row !justify-end">
|
||||||
|
|||||||
@@ -101,15 +101,6 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
|||||||
decoration: z.boolean().default(true),
|
decoration: z.boolean().default(true),
|
||||||
})
|
})
|
||||||
.merge(notificationBaseSchema),
|
.merge(notificationBaseSchema),
|
||||||
z
|
|
||||||
.object({
|
|
||||||
type: z.literal("ntfy"),
|
|
||||||
serverUrl: z.string().min(1, { message: "Server URL is required" }),
|
|
||||||
topic: z.string().min(1, { message: "Topic is required" }),
|
|
||||||
accessToken: z.string().min(1, { message: "Access Token is required" }),
|
|
||||||
priority: z.number().min(1).max(5).default(3),
|
|
||||||
})
|
|
||||||
.merge(notificationBaseSchema),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const notificationsMap = {
|
export const notificationsMap = {
|
||||||
@@ -133,10 +124,6 @@ export const notificationsMap = {
|
|||||||
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
||||||
label: "Gotify",
|
label: "Gotify",
|
||||||
},
|
},
|
||||||
ntfy: {
|
|
||||||
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
|
||||||
label: "ntfy",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
||||||
@@ -168,8 +155,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
api.notification.testEmailConnection.useMutation();
|
api.notification.testEmailConnection.useMutation();
|
||||||
const { mutateAsync: testGotifyConnection, isLoading: isLoadingGotify } =
|
const { mutateAsync: testGotifyConnection, isLoading: isLoadingGotify } =
|
||||||
api.notification.testGotifyConnection.useMutation();
|
api.notification.testGotifyConnection.useMutation();
|
||||||
const { mutateAsync: testNtfyConnection, isLoading: isLoadingNtfy } =
|
|
||||||
api.notification.testNtfyConnection.useMutation();
|
|
||||||
const slackMutation = notificationId
|
const slackMutation = notificationId
|
||||||
? api.notification.updateSlack.useMutation()
|
? api.notification.updateSlack.useMutation()
|
||||||
: api.notification.createSlack.useMutation();
|
: api.notification.createSlack.useMutation();
|
||||||
@@ -185,9 +170,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
const gotifyMutation = notificationId
|
const gotifyMutation = notificationId
|
||||||
? api.notification.updateGotify.useMutation()
|
? api.notification.updateGotify.useMutation()
|
||||||
: api.notification.createGotify.useMutation();
|
: api.notification.createGotify.useMutation();
|
||||||
const ntfyMutation = notificationId
|
|
||||||
? api.notification.updateNtfy.useMutation()
|
|
||||||
: api.notification.createNtfy.useMutation();
|
|
||||||
|
|
||||||
const form = useForm<NotificationSchema>({
|
const form = useForm<NotificationSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -284,20 +266,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
name: notification.name,
|
name: notification.name,
|
||||||
dockerCleanup: notification.dockerCleanup,
|
dockerCleanup: notification.dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (notification.notificationType === "ntfy") {
|
|
||||||
form.reset({
|
|
||||||
appBuildError: notification.appBuildError,
|
|
||||||
appDeploy: notification.appDeploy,
|
|
||||||
dokployRestart: notification.dokployRestart,
|
|
||||||
databaseBackup: notification.databaseBackup,
|
|
||||||
type: notification.notificationType,
|
|
||||||
accessToken: notification.ntfy?.accessToken,
|
|
||||||
topic: notification.ntfy?.topic,
|
|
||||||
priority: notification.ntfy?.priority,
|
|
||||||
serverUrl: notification.ntfy?.serverUrl,
|
|
||||||
name: notification.name,
|
|
||||||
dockerCleanup: notification.dockerCleanup,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
form.reset();
|
form.reset();
|
||||||
@@ -310,7 +278,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
discord: discordMutation,
|
discord: discordMutation,
|
||||||
email: emailMutation,
|
email: emailMutation,
|
||||||
gotify: gotifyMutation,
|
gotify: gotifyMutation,
|
||||||
ntfy: ntfyMutation,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: NotificationSchema) => {
|
const onSubmit = async (data: NotificationSchema) => {
|
||||||
@@ -399,21 +366,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
notificationId: notificationId || "",
|
notificationId: notificationId || "",
|
||||||
gotifyId: notification?.gotifyId || "",
|
gotifyId: notification?.gotifyId || "",
|
||||||
});
|
});
|
||||||
} else if (data.type === "ntfy") {
|
|
||||||
promise = ntfyMutation.mutateAsync({
|
|
||||||
appBuildError: appBuildError,
|
|
||||||
appDeploy: appDeploy,
|
|
||||||
dokployRestart: dokployRestart,
|
|
||||||
databaseBackup: databaseBackup,
|
|
||||||
serverUrl: data.serverUrl,
|
|
||||||
accessToken: data.accessToken,
|
|
||||||
topic: data.topic,
|
|
||||||
priority: data.priority,
|
|
||||||
name: data.name,
|
|
||||||
dockerCleanup: dockerCleanup,
|
|
||||||
notificationId: notificationId || "",
|
|
||||||
ntfyId: notification?.ntfyId || "",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise) {
|
if (promise) {
|
||||||
@@ -923,83 +875,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{type === "ntfy" && (
|
|
||||||
<>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="serverUrl"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Server URL</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="https://ntfy.sh" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="topic"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Topic</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="deployments" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="accessToken"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Access Token</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder="AzxcvbnmKjhgfdsa..."
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="priority"
|
|
||||||
defaultValue={3}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="w-full">
|
|
||||||
<FormLabel>Priority</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder="3"
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
if (value) {
|
|
||||||
const port = Number.parseInt(value);
|
|
||||||
if (port > 0 && port <= 5) {
|
|
||||||
field.onChange(port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
type="number"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>
|
|
||||||
Message priority (1-5, default: 3)
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
@@ -1149,8 +1024,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
isLoadingTelegram ||
|
isLoadingTelegram ||
|
||||||
isLoadingDiscord ||
|
isLoadingDiscord ||
|
||||||
isLoadingEmail ||
|
isLoadingEmail ||
|
||||||
isLoadingGotify ||
|
isLoadingGotify
|
||||||
isLoadingNtfy
|
|
||||||
}
|
}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -1187,13 +1061,6 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
priority: form.getValues("priority"),
|
priority: form.getValues("priority"),
|
||||||
decoration: form.getValues("decoration"),
|
decoration: form.getValues("decoration"),
|
||||||
});
|
});
|
||||||
} else if (type === "ntfy") {
|
|
||||||
await testNtfyConnection({
|
|
||||||
serverUrl: form.getValues("serverUrl"),
|
|
||||||
topic: form.getValues("topic"),
|
|
||||||
accessToken: form.getValues("accessToken"),
|
|
||||||
priority: form.getValues("priority"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
toast.success("Connection Success");
|
toast.success("Connection Success");
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -88,11 +88,6 @@ export const ShowNotifications = () => {
|
|||||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
<MessageCircleMore className="size-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "ntfy" && (
|
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
|
||||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{notification.name}
|
{notification.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -97,7 +97,11 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
|||||||
);
|
);
|
||||||
refetchDashboard();
|
refetchDashboard();
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {
|
||||||
|
toast.error(
|
||||||
|
`${haveTraefikDashboardPortEnabled ? "Disabled" : "Enabled"} Dashboard`,
|
||||||
|
);
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
className="w-full cursor-pointer space-x-3"
|
className="w-full cursor-pointer space-x-3"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
|
import { extractServices } from "@/pages/dashboard/project/[projectId]";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { useUrl } from "@/utils/hooks/use-url";
|
import { useUrl } from "@/utils/hooks/use-url";
|
||||||
import { extractServices } from "../users/add-permissions";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
@@ -95,13 +95,11 @@ export const SetupMonitoring = ({ serverId }: Props) => {
|
|||||||
|
|
||||||
const { data: projects } = api.project.all.useQuery();
|
const { data: projects } = api.project.all.useQuery();
|
||||||
|
|
||||||
const extractServicesFromProjects = () => {
|
const extractServicesFromProjects = (projects: any[] | undefined) => {
|
||||||
if (!projects) return [];
|
if (!projects) return [];
|
||||||
|
|
||||||
const allServices = projects.flatMap((project) => {
|
const allServices = projects.flatMap((project) => {
|
||||||
const services = project.environments.flatMap((env) =>
|
const services = extractServices(project);
|
||||||
extractServices(env),
|
|
||||||
);
|
|
||||||
return serverId
|
return serverId
|
||||||
? services
|
? services
|
||||||
.filter((service) => service.serverId === serverId)
|
.filter((service) => service.serverId === serverId)
|
||||||
@@ -112,7 +110,7 @@ export const SetupMonitoring = ({ serverId }: Props) => {
|
|||||||
return [...new Set(allServices)];
|
return [...new Set(allServices)];
|
||||||
};
|
};
|
||||||
|
|
||||||
const services = extractServicesFromProjects();
|
const services = extractServicesFromProjects(projects);
|
||||||
|
|
||||||
const form = useForm<Schema>({
|
const form = useForm<Schema>({
|
||||||
resolver: zodResolver(Schema),
|
resolver: zodResolver(Schema),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { findEnvironmentById } from "@dokploy/server/index";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -27,135 +26,11 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { extractServices } from "@/pages/dashboard/project/[projectId]";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
|
||||||
type Environment = Omit<
|
|
||||||
Awaited<ReturnType<typeof findEnvironmentById>>,
|
|
||||||
"project"
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type Services = {
|
|
||||||
appName: string;
|
|
||||||
serverId?: string | null;
|
|
||||||
name: string;
|
|
||||||
type:
|
|
||||||
| "mariadb"
|
|
||||||
| "application"
|
|
||||||
| "postgres"
|
|
||||||
| "mysql"
|
|
||||||
| "mongo"
|
|
||||||
| "redis"
|
|
||||||
| "compose";
|
|
||||||
description?: string | null;
|
|
||||||
id: string;
|
|
||||||
createdAt: string;
|
|
||||||
status?: "idle" | "running" | "done" | "error";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const extractServices = (data: Environment | undefined) => {
|
|
||||||
const applications: Services[] =
|
|
||||||
data?.applications.map((item) => ({
|
|
||||||
appName: item.appName,
|
|
||||||
name: item.name,
|
|
||||||
type: "application",
|
|
||||||
id: item.applicationId,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
status: item.applicationStatus,
|
|
||||||
description: item.description,
|
|
||||||
serverId: item.serverId,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const mariadb: Services[] =
|
|
||||||
data?.mariadb.map((item) => ({
|
|
||||||
appName: item.appName,
|
|
||||||
name: item.name,
|
|
||||||
type: "mariadb",
|
|
||||||
id: item.mariadbId,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
status: item.applicationStatus,
|
|
||||||
description: item.description,
|
|
||||||
serverId: item.serverId,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const postgres: Services[] =
|
|
||||||
data?.postgres.map((item) => ({
|
|
||||||
appName: item.appName,
|
|
||||||
name: item.name,
|
|
||||||
type: "postgres",
|
|
||||||
id: item.postgresId,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
status: item.applicationStatus,
|
|
||||||
description: item.description,
|
|
||||||
serverId: item.serverId,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const mongo: Services[] =
|
|
||||||
data?.mongo.map((item) => ({
|
|
||||||
appName: item.appName,
|
|
||||||
name: item.name,
|
|
||||||
type: "mongo",
|
|
||||||
id: item.mongoId,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
status: item.applicationStatus,
|
|
||||||
description: item.description,
|
|
||||||
serverId: item.serverId,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const redis: Services[] =
|
|
||||||
data?.redis.map((item) => ({
|
|
||||||
appName: item.appName,
|
|
||||||
name: item.name,
|
|
||||||
type: "redis",
|
|
||||||
id: item.redisId,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
status: item.applicationStatus,
|
|
||||||
description: item.description,
|
|
||||||
serverId: item.serverId,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const mysql: Services[] =
|
|
||||||
data?.mysql.map((item) => ({
|
|
||||||
appName: item.appName,
|
|
||||||
name: item.name,
|
|
||||||
type: "mysql",
|
|
||||||
id: item.mysqlId,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
status: item.applicationStatus,
|
|
||||||
description: item.description,
|
|
||||||
serverId: item.serverId,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const compose: Services[] =
|
|
||||||
data?.compose.map((item) => ({
|
|
||||||
appName: item.appName,
|
|
||||||
name: item.name,
|
|
||||||
type: "compose",
|
|
||||||
id: item.composeId,
|
|
||||||
createdAt: item.createdAt,
|
|
||||||
status: item.composeStatus,
|
|
||||||
description: item.description,
|
|
||||||
serverId: item.serverId,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
applications.push(
|
|
||||||
...mysql,
|
|
||||||
...redis,
|
|
||||||
...mongo,
|
|
||||||
...postgres,
|
|
||||||
...mariadb,
|
|
||||||
...compose,
|
|
||||||
);
|
|
||||||
|
|
||||||
applications.sort((a, b) => {
|
|
||||||
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
||||||
});
|
|
||||||
|
|
||||||
return applications;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addPermissions = z.object({
|
const addPermissions = z.object({
|
||||||
accessedProjects: z.array(z.string()).optional(),
|
accessedProjects: z.array(z.string()).optional(),
|
||||||
accessedEnvironments: z.array(z.string()).optional(),
|
|
||||||
accessedServices: z.array(z.string()).optional(),
|
accessedServices: z.array(z.string()).optional(),
|
||||||
canCreateProjects: z.boolean().optional().default(false),
|
canCreateProjects: z.boolean().optional().default(false),
|
||||||
canCreateServices: z.boolean().optional().default(false),
|
canCreateServices: z.boolean().optional().default(false),
|
||||||
@@ -201,7 +76,6 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
accessedProjects: data.accessedProjects || [],
|
accessedProjects: data.accessedProjects || [],
|
||||||
accessedEnvironments: data.accessedEnvironments || [],
|
|
||||||
accessedServices: data.accessedServices || [],
|
accessedServices: data.accessedServices || [],
|
||||||
canCreateProjects: data.canCreateProjects,
|
canCreateProjects: data.canCreateProjects,
|
||||||
canCreateServices: data.canCreateServices,
|
canCreateServices: data.canCreateServices,
|
||||||
@@ -225,7 +99,6 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
canDeleteProjects: data.canDeleteProjects,
|
canDeleteProjects: data.canDeleteProjects,
|
||||||
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
|
||||||
accessedProjects: data.accessedProjects || [],
|
accessedProjects: data.accessedProjects || [],
|
||||||
accessedEnvironments: data.accessedEnvironments || [],
|
|
||||||
accessedServices: data.accessedServices || [],
|
accessedServices: data.accessedServices || [],
|
||||||
canAccessToDocker: data.canAccessToDocker,
|
canAccessToDocker: data.canAccessToDocker,
|
||||||
canAccessToAPI: data.canAccessToAPI,
|
canAccessToAPI: data.canAccessToAPI,
|
||||||
@@ -459,317 +332,89 @@ export const AddUserPermissions = ({ userId }: Props) => {
|
|||||||
No projects found
|
No projects found
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="grid md:grid-cols-1 gap-4">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
{projects?.map((project, projectIndex) => {
|
{projects?.map((item, index) => {
|
||||||
|
const applications = extractServices(item);
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
key={`project-${projectIndex}`}
|
key={`project-${index}`}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="accessedProjects"
|
name="accessedProjects"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<FormItem
|
<FormItem
|
||||||
key={project.projectId}
|
key={item.projectId}
|
||||||
className="flex flex-col items-start rounded-lg p-4 border"
|
className="flex flex-col items-start space-x-4 rounded-lg p-4 border"
|
||||||
>
|
>
|
||||||
{/* Project Header */}
|
<div className="flex flex-row gap-4">
|
||||||
<div className="flex flex-row gap-4 items-center w-full">
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={field.value?.includes(
|
checked={field.value?.includes(
|
||||||
project.projectId,
|
item.projectId,
|
||||||
)}
|
)}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => {
|
||||||
if (checked) {
|
return checked
|
||||||
// Add the project
|
? field.onChange([
|
||||||
field.onChange([
|
...(field.value || []),
|
||||||
...(field.value || []),
|
item.projectId,
|
||||||
project.projectId,
|
])
|
||||||
]);
|
: field.onChange(
|
||||||
} else {
|
field.value?.filter(
|
||||||
// Remove the project
|
(value) =>
|
||||||
field.onChange(
|
value !== item.projectId,
|
||||||
field.value?.filter(
|
),
|
||||||
(value) =>
|
|
||||||
value !== project.projectId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Also remove all environments and services from this project
|
|
||||||
const currentEnvs =
|
|
||||||
form.getValues(
|
|
||||||
"accessedEnvironments",
|
|
||||||
) || [];
|
|
||||||
const currentServices =
|
|
||||||
form.getValues(
|
|
||||||
"accessedServices",
|
|
||||||
) || [];
|
|
||||||
|
|
||||||
// Get all environment IDs from this project
|
|
||||||
const projectEnvIds =
|
|
||||||
project.environments.map(
|
|
||||||
(env) => env.environmentId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get all service IDs from this project
|
|
||||||
const projectServiceIds =
|
|
||||||
project.environments.flatMap(
|
|
||||||
(env) =>
|
|
||||||
extractServices(env).map(
|
|
||||||
(service) => service.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove environments and services from this project
|
|
||||||
form.setValue(
|
|
||||||
"accessedEnvironments",
|
|
||||||
currentEnvs.filter(
|
|
||||||
(envId) =>
|
|
||||||
!projectEnvIds.includes(envId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
form.setValue(
|
|
||||||
"accessedServices",
|
|
||||||
currentServices.filter(
|
|
||||||
(serviceId) =>
|
|
||||||
!projectServiceIds.includes(
|
|
||||||
serviceId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="text-base font-semibold text-primary">
|
<FormLabel className="text-sm font-medium text-primary">
|
||||||
{project.name}
|
{item.name}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</div>
|
</div>
|
||||||
|
{applications.length === 0 && (
|
||||||
{/* Environments */}
|
<p className="text-sm text-muted-foreground">
|
||||||
<div className="ml-6 w-full space-y-3">
|
No services found
|
||||||
{project.environments.length === 0 && (
|
</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
)}
|
||||||
No environments found
|
{applications?.map((item, index) => (
|
||||||
</p>
|
<FormField
|
||||||
)}
|
key={`project-${index}`}
|
||||||
{project.environments.map(
|
control={form.control}
|
||||||
(environment, envIndex) => {
|
name="accessedServices"
|
||||||
const services =
|
render={({ field }) => {
|
||||||
extractServices(environment);
|
|
||||||
return (
|
return (
|
||||||
<div
|
<FormItem
|
||||||
key={`env-${envIndex}`}
|
key={item.id}
|
||||||
className="border-l-2 border-muted pl-4"
|
className="flex flex-row items-start space-x-3 space-y-0"
|
||||||
>
|
>
|
||||||
{/* Environment Header with Checkbox */}
|
<FormControl>
|
||||||
<FormField
|
<Checkbox
|
||||||
key={`env-${envIndex}`}
|
checked={field.value?.includes(
|
||||||
control={form.control}
|
item.id,
|
||||||
name="accessedEnvironments"
|
)}
|
||||||
render={({ field: envField }) => (
|
onCheckedChange={(checked) => {
|
||||||
<FormItem className="flex flex-row items-center space-x-3 space-y-0 mb-2">
|
return checked
|
||||||
<FormControl>
|
? field.onChange([
|
||||||
<Checkbox
|
...(field.value || []),
|
||||||
checked={envField.value?.includes(
|
item.id,
|
||||||
environment.environmentId,
|
])
|
||||||
)}
|
: field.onChange(
|
||||||
onCheckedChange={(
|
field.value?.filter(
|
||||||
checked,
|
(value) =>
|
||||||
) => {
|
value !== item.id,
|
||||||
if (checked) {
|
),
|
||||||
// Add the environment
|
|
||||||
envField.onChange([
|
|
||||||
...(envField.value ||
|
|
||||||
[]),
|
|
||||||
environment.environmentId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Auto-select the project if not already selected
|
|
||||||
const currentProjects =
|
|
||||||
form.getValues(
|
|
||||||
"accessedProjects",
|
|
||||||
) || [];
|
|
||||||
if (
|
|
||||||
!currentProjects.includes(
|
|
||||||
project.projectId,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
form.setValue(
|
|
||||||
"accessedProjects",
|
|
||||||
[
|
|
||||||
...currentProjects,
|
|
||||||
project.projectId,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Remove the environment
|
|
||||||
envField.onChange(
|
|
||||||
envField.value?.filter(
|
|
||||||
(value) =>
|
|
||||||
value !==
|
|
||||||
environment.environmentId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Also remove all services from this environment
|
|
||||||
const currentServices =
|
|
||||||
form.getValues(
|
|
||||||
"accessedServices",
|
|
||||||
) || [];
|
|
||||||
const environmentServiceIds =
|
|
||||||
services.map(
|
|
||||||
(service) =>
|
|
||||||
service.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
form.setValue(
|
|
||||||
"accessedServices",
|
|
||||||
currentServices.filter(
|
|
||||||
(serviceId) =>
|
|
||||||
!environmentServiceIds.includes(
|
|
||||||
serviceId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-2 h-2 bg-blue-500 rounded-full" />
|
|
||||||
<FormLabel className="text-sm font-medium text-foreground cursor-pointer">
|
|
||||||
{environment.name}
|
|
||||||
</FormLabel>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
({services.length} services)
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Services */}
|
|
||||||
<div className="ml-4 space-y-2">
|
|
||||||
{services.length === 0 && (
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
No services found
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{services.map(
|
|
||||||
(service, serviceIndex) => (
|
|
||||||
<FormField
|
|
||||||
key={`service-${serviceIndex}`}
|
|
||||||
control={form.control}
|
|
||||||
name="accessedServices"
|
|
||||||
render={({
|
|
||||||
field: serviceField,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<FormItem
|
|
||||||
key={service.id}
|
|
||||||
className="flex flex-row items-center space-x-3 space-y-0"
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={serviceField.value?.includes(
|
|
||||||
service.id,
|
|
||||||
)}
|
|
||||||
onCheckedChange={(
|
|
||||||
checked,
|
|
||||||
) => {
|
|
||||||
if (checked) {
|
|
||||||
// Add the service
|
|
||||||
serviceField.onChange(
|
|
||||||
[
|
|
||||||
...(serviceField.value ||
|
|
||||||
[]),
|
|
||||||
service.id,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Auto-select the environment if not already selected
|
|
||||||
const currentEnvs =
|
|
||||||
form.getValues(
|
|
||||||
"accessedEnvironments",
|
|
||||||
) || [];
|
|
||||||
if (
|
|
||||||
!currentEnvs.includes(
|
|
||||||
environment.environmentId,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
form.setValue(
|
|
||||||
"accessedEnvironments",
|
|
||||||
[
|
|
||||||
...currentEnvs,
|
|
||||||
environment.environmentId,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-select the project if not already selected
|
|
||||||
const currentProjects =
|
|
||||||
form.getValues(
|
|
||||||
"accessedProjects",
|
|
||||||
) || [];
|
|
||||||
if (
|
|
||||||
!currentProjects.includes(
|
|
||||||
project.projectId,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
form.setValue(
|
|
||||||
"accessedProjects",
|
|
||||||
[
|
|
||||||
...currentProjects,
|
|
||||||
project.projectId,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Remove the service
|
|
||||||
serviceField.onChange(
|
|
||||||
serviceField.value?.filter(
|
|
||||||
(value) =>
|
|
||||||
value !==
|
|
||||||
service.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div
|
|
||||||
className={`w-1.5 h-1.5 rounded-full ${
|
|
||||||
service.type ===
|
|
||||||
"application"
|
|
||||||
? "bg-green-500"
|
|
||||||
: service.type ===
|
|
||||||
"compose"
|
|
||||||
? "bg-purple-500"
|
|
||||||
: "bg-orange-500"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<FormLabel className="text-sm text-muted-foreground cursor-pointer">
|
|
||||||
{service.name}
|
|
||||||
</FormLabel>
|
|
||||||
<span className="text-xs text-muted-foreground/70 capitalize">
|
|
||||||
({service.type})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
</FormControl>
|
||||||
)}
|
<FormLabel className="text-sm text-muted-foreground">
|
||||||
</div>
|
{item.name}
|
||||||
</div>
|
</FormLabel>
|
||||||
|
</FormItem>
|
||||||
);
|
);
|
||||||
},
|
}}
|
||||||
)}
|
/>
|
||||||
</div>
|
))}
|
||||||
</FormItem>
|
</FormItem>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -11,20 +11,11 @@ interface Props {
|
|||||||
export const DashboardLayout = ({ children }: Props) => {
|
export const DashboardLayout = ({ children }: Props) => {
|
||||||
const { data: haveRootAccess } = api.user.haveRootAccess.useQuery();
|
const { data: haveRootAccess } = api.user.haveRootAccess.useQuery();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
const { data: isUserSubscribed } = api.settings.isUserSubscribed.useQuery(
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
enabled: isCloud === true,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
refetchOnMount: false,
|
|
||||||
refetchOnReconnect: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Page>{children}</Page>
|
<Page>{children}</Page>
|
||||||
{isCloud === true && isUserSubscribed === true && (
|
{isCloud === true && (
|
||||||
<ChatwootWidget websiteToken="USCpQRKzHvFMssf3p6Eacae5" />
|
<ChatwootWidget websiteToken="USCpQRKzHvFMssf3p6Eacae5" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { SidebarTrigger } from "@/components/ui/sidebar";
|
|||||||
interface Props {
|
interface Props {
|
||||||
list: {
|
list: {
|
||||||
name: string;
|
name: string;
|
||||||
href?: string;
|
href: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,11 +29,11 @@ export const BreadcrumbSidebar = ({ list }: Props) => {
|
|||||||
{list.map((item, index) => (
|
{list.map((item, index) => (
|
||||||
<Fragment key={item.name}>
|
<Fragment key={item.name}>
|
||||||
<BreadcrumbItem className="block">
|
<BreadcrumbItem className="block">
|
||||||
<BreadcrumbLink href={item?.href} asChild={!!item?.href}>
|
<BreadcrumbLink href={item.href} asChild={!!item.href}>
|
||||||
{item.href ? (
|
{item.href ? (
|
||||||
<Link href={item?.href}>{item?.name}</Link>
|
<Link href={item.href}>{item.name}</Link>
|
||||||
) : (
|
) : (
|
||||||
item?.name
|
item.name
|
||||||
)}
|
)}
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { useEffect, useRef } from "react";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
|
|
||||||
type Props = React.ComponentPropsWithoutRef<typeof Input>;
|
|
||||||
|
|
||||||
export const FocusShortcutInput = (props: Props) => {
|
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
|
||||||
const isMod = e.metaKey || e.ctrlKey;
|
|
||||||
if (!isMod || e.key.toLowerCase() !== "k") return;
|
|
||||||
|
|
||||||
const target = e.target as HTMLElement | null;
|
|
||||||
if (target) {
|
|
||||||
const tag = target.tagName;
|
|
||||||
if (
|
|
||||||
target.isContentEditable ||
|
|
||||||
tag === "INPUT" ||
|
|
||||||
tag === "TEXTAREA" ||
|
|
||||||
tag === "SELECT" ||
|
|
||||||
target.getAttribute("role") === "textbox"
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
inputRef.current?.focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("keydown", onKeyDown);
|
|
||||||
return () => window.removeEventListener("keydown", onKeyDown);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <Input {...props} ref={inputRef} />;
|
|
||||||
};
|
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { Clipboard } from "lucide-react";
|
import { Clipboard, EyeIcon, EyeOffIcon } from "lucide-react";
|
||||||
import { useRef } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Input, type InputProps } from "../ui/input";
|
import { Input, type InputProps } from "../ui/input";
|
||||||
|
|
||||||
export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
||||||
|
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const togglePasswordVisibility = () => {
|
||||||
|
setIsPasswordVisible((prevVisibility) => !prevVisibility);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full items-center space-x-2">
|
<div className="flex w-full items-center space-x-2">
|
||||||
<Input ref={inputRef} type={"password"} {...props} />
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
type={isPasswordVisible ? "text" : "password"}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -20,13 +29,13 @@ export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
|
|||||||
>
|
>
|
||||||
<Clipboard className="size-4 text-muted-foreground" />
|
<Clipboard className="size-4 text-muted-foreground" />
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button onClick={togglePasswordVisibility} variant={"secondary"}>
|
<Button onClick={togglePasswordVisibility} variant={"secondary"}>
|
||||||
{isPasswordVisible ? (
|
{isPasswordVisible ? (
|
||||||
<EyeOffIcon className="size-4 text-muted-foreground" />
|
<EyeOffIcon className="size-4 text-muted-foreground" />
|
||||||
) : (
|
) : (
|
||||||
<EyeIcon className="size-4 text-muted-foreground" />
|
<EyeIcon className="size-4 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
</Button> */}
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -9,39 +8,18 @@ export interface InputProps
|
|||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, errorMessage, type, ...props }, ref) => {
|
({ className, errorMessage, type, ...props }, ref) => {
|
||||||
const [showPassword, setShowPassword] = React.useState(false);
|
|
||||||
const isPassword = type === "password";
|
|
||||||
const inputType = isPassword ? (showPassword ? "text" : "password") : type;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative w-full">
|
<input
|
||||||
<input
|
type={type}
|
||||||
type={inputType}
|
className={cn(
|
||||||
className={cn(
|
// bg-gray
|
||||||
// bg-gray
|
"flex h-10 w-full rounded-md bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
"flex h-10 w-full rounded-md bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border disabled:cursor-not-allowed disabled:opacity-50",
|
className,
|
||||||
isPassword && "pr-10", // Add padding for the eye icon
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
{isPassword && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground focus:outline-none"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
{showPassword ? (
|
|
||||||
<EyeOffIcon className="h-4 w-4" />
|
|
||||||
) : (
|
|
||||||
<EyeIcon className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
{errorMessage && (
|
{errorMessage && (
|
||||||
<span className="text-sm text-red-600 text-secondary-foreground">
|
<span className="text-sm text-red-600 text-secondary-foreground">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
CREATE TABLE "environment" (
|
|
||||||
"environmentId" text PRIMARY KEY NOT NULL,
|
|
||||||
"name" text NOT NULL,
|
|
||||||
"description" text,
|
|
||||||
"createdAt" text NOT NULL,
|
|
||||||
"projectId" text NOT NULL
|
|
||||||
);
|
|
||||||
ALTER TABLE "environment" ADD CONSTRAINT "environment_projectId_project_projectId_fk" FOREIGN KEY ("projectId") REFERENCES "public"."project"("projectId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Insertar un ambiente "production" para cada proyecto existente
|
|
||||||
INSERT INTO "environment" ("environmentId", "name", "description", "createdAt", "projectId")
|
|
||||||
SELECT
|
|
||||||
-- Generar un ID único para cada ambiente usando el projectId como base
|
|
||||||
'env_prod_' || "projectId" || '_' || EXTRACT(EPOCH FROM NOW())::text,
|
|
||||||
'production',
|
|
||||||
'Production environment',
|
|
||||||
NOW()::text,
|
|
||||||
"projectId"
|
|
||||||
FROM "project"
|
|
||||||
WHERE "projectId" NOT IN (
|
|
||||||
SELECT DISTINCT "projectId"
|
|
||||||
FROM "environment"
|
|
||||||
WHERE "name" = 'production'
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE "application" ADD COLUMN "environmentId" text;--> statement-breakpoint
|
|
||||||
ALTER TABLE "postgres" ADD COLUMN "environmentId" text;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mariadb" ADD COLUMN "environmentId" text;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mongo" ADD COLUMN "environmentId" text;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mysql" ADD COLUMN "environmentId" text;--> statement-breakpoint
|
|
||||||
ALTER TABLE "redis" ADD COLUMN "environmentId" text;--> statement-breakpoint
|
|
||||||
ALTER TABLE "compose" ADD COLUMN "environmentId" text;--> statement-breakpoint
|
|
||||||
|
|
||||||
|
|
||||||
-- Step 3: Update all services to point to their project's production environment
|
|
||||||
-- Update applications
|
|
||||||
UPDATE "application"
|
|
||||||
SET "environmentId" = (
|
|
||||||
SELECT e."environmentId"
|
|
||||||
FROM "environment" e
|
|
||||||
WHERE e."projectId" = "application"."projectId"
|
|
||||||
AND e."name" = 'production'
|
|
||||||
LIMIT 1
|
|
||||||
);--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Update compose
|
|
||||||
UPDATE "compose"
|
|
||||||
SET "environmentId" = (
|
|
||||||
SELECT e."environmentId"
|
|
||||||
FROM "environment" e
|
|
||||||
WHERE e."projectId" = "compose"."projectId"
|
|
||||||
AND e."name" = 'production'
|
|
||||||
LIMIT 1
|
|
||||||
);--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Update mariadb
|
|
||||||
UPDATE "mariadb"
|
|
||||||
SET "environmentId" = (
|
|
||||||
SELECT e."environmentId"
|
|
||||||
FROM "environment" e
|
|
||||||
WHERE e."projectId" = "mariadb"."projectId"
|
|
||||||
AND e."name" = 'production'
|
|
||||||
LIMIT 1
|
|
||||||
);--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Update mongo
|
|
||||||
UPDATE "mongo"
|
|
||||||
SET "environmentId" = (
|
|
||||||
SELECT e."environmentId"
|
|
||||||
FROM "environment" e
|
|
||||||
WHERE e."projectId" = "mongo"."projectId"
|
|
||||||
AND e."name" = 'production'
|
|
||||||
LIMIT 1
|
|
||||||
);--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Update mysql
|
|
||||||
UPDATE "mysql"
|
|
||||||
SET "environmentId" = (
|
|
||||||
SELECT e."environmentId"
|
|
||||||
FROM "environment" e
|
|
||||||
WHERE e."projectId" = "mysql"."projectId"
|
|
||||||
AND e."name" = 'production'
|
|
||||||
LIMIT 1
|
|
||||||
);--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Update postgres
|
|
||||||
UPDATE "postgres"
|
|
||||||
SET "environmentId" = (
|
|
||||||
SELECT e."environmentId"
|
|
||||||
FROM "environment" e
|
|
||||||
WHERE e."projectId" = "postgres"."projectId"
|
|
||||||
AND e."name" = 'production'
|
|
||||||
LIMIT 1
|
|
||||||
);--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Update redis
|
|
||||||
UPDATE "redis"
|
|
||||||
SET "environmentId" = (
|
|
||||||
SELECT e."environmentId"
|
|
||||||
FROM "environment" e
|
|
||||||
WHERE e."projectId" = "redis"."projectId"
|
|
||||||
AND e."name" = 'production'
|
|
||||||
LIMIT 1
|
|
||||||
);--> statement-breakpoint
|
|
||||||
|
|
||||||
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "application" DROP CONSTRAINT "application_projectId_project_projectId_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "postgres" DROP CONSTRAINT "postgres_projectId_project_projectId_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "mariadb" DROP CONSTRAINT "mariadb_projectId_project_projectId_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "mongo" DROP CONSTRAINT "mongo_projectId_project_projectId_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "mysql" DROP CONSTRAINT "mysql_projectId_project_projectId_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "redis" DROP CONSTRAINT "redis_projectId_project_projectId_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "compose" DROP CONSTRAINT "compose_projectId_project_projectId_fk";
|
|
||||||
--> statement-breakpoint
|
|
||||||
|
|
||||||
-- Step 4: Make environmentId columns NOT NULL
|
|
||||||
ALTER TABLE "application" ALTER COLUMN "environmentId" SET NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "compose" ALTER COLUMN "environmentId" SET NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mariadb" ALTER COLUMN "environmentId" SET NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mongo" ALTER COLUMN "environmentId" SET NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mysql" ALTER COLUMN "environmentId" SET NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "postgres" ALTER COLUMN "environmentId" SET NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "redis" ALTER COLUMN "environmentId" SET NOT NULL;--> statement-breakpoint
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ALTER TABLE "application" ADD CONSTRAINT "application_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "postgres" ADD CONSTRAINT "postgres_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mariadb" ADD CONSTRAINT "mariadb_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mongo" ADD CONSTRAINT "mongo_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "mysql" ADD CONSTRAINT "mysql_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "redis" ADD CONSTRAINT "redis_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "compose" ADD CONSTRAINT "compose_environmentId_environment_environmentId_fk" FOREIGN KEY ("environmentId") REFERENCES "public"."environment"("environmentId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
||||||
ALTER TABLE "application" DROP COLUMN "projectId";--> statement-breakpoint
|
|
||||||
ALTER TABLE "postgres" DROP COLUMN "projectId";--> statement-breakpoint
|
|
||||||
ALTER TABLE "mariadb" DROP COLUMN "projectId";--> statement-breakpoint
|
|
||||||
ALTER TABLE "mongo" DROP COLUMN "projectId";--> statement-breakpoint
|
|
||||||
ALTER TABLE "mysql" DROP COLUMN "projectId";--> statement-breakpoint
|
|
||||||
ALTER TABLE "redis" DROP COLUMN "projectId";--> statement-breakpoint
|
|
||||||
ALTER TABLE "compose" DROP COLUMN "projectId";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE "environment" ADD COLUMN "env" text DEFAULT '' NOT NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE "member" ADD COLUMN "accessedEnvironments" text[] DEFAULT ARRAY[]::text[] NOT NULL;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
ALTER TYPE "public"."notificationType" ADD VALUE 'ntfy';--> statement-breakpoint
|
|
||||||
CREATE TABLE "ntfy" (
|
|
||||||
"ntfyId" text PRIMARY KEY NOT NULL,
|
|
||||||
"serverUrl" text NOT NULL,
|
|
||||||
"topic" text NOT NULL,
|
|
||||||
"accessToken" text NOT NULL,
|
|
||||||
"priority" integer DEFAULT 3 NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "notification" ADD COLUMN "ntfyId" text;--> statement-breakpoint
|
|
||||||
ALTER TABLE "notification" ADD CONSTRAINT "notification_ntfyId_ntfy_ntfyId_fk" FOREIGN KEY ("ntfyId") REFERENCES "public"."ntfy"("ntfyId") ON DELETE cascade ON UPDATE no action;
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -750,34 +750,6 @@
|
|||||||
"when": 1754912062243,
|
"when": 1754912062243,
|
||||||
"tag": "0106_purple_maggott",
|
"tag": "0106_purple_maggott",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 107,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1756793713380,
|
|
||||||
"tag": "0107_loud_kang",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 108,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1756955718127,
|
|
||||||
"tag": "0108_lazy_next_avengers",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 109,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1757052053574,
|
|
||||||
"tag": "0109_remarkable_sauron",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 110,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1757189541734,
|
|
||||||
"tag": "0110_red_psynapse",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.25.1",
|
"version": "v0.24.12",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -20,11 +20,7 @@ export default async function handler(
|
|||||||
const application = await db.query.applications.findFirst({
|
const application = await db.query.applications.findFirst({
|
||||||
where: eq(applications.refreshToken, refreshToken as string),
|
where: eq(applications.refreshToken, refreshToken as string),
|
||||||
with: {
|
with: {
|
||||||
environment: {
|
project: true,
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bitbucket: true,
|
bitbucket: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,11 +27,7 @@ export default async function handler(
|
|||||||
const composeResult = await db.query.compose.findFirst({
|
const composeResult = await db.query.compose.findFirst({
|
||||||
where: eq(compose.refreshToken, refreshToken as string),
|
where: eq(compose.refreshToken, refreshToken as string),
|
||||||
with: {
|
with: {
|
||||||
environment: {
|
project: true,
|
||||||
with: {
|
|
||||||
project: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bitbucket: true,
|
bitbucket: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
1372
apps/dokploy/pages/dashboard/project/[projectId].tsx
Normal file
1372
apps/dokploy/pages/dashboard/project/[projectId].tsx
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -71,7 +71,7 @@ const Service = (
|
|||||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||||
const { applicationId, activeTab } = props;
|
const { applicationId, activeTab } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, environmentId } = router.query;
|
const { projectId } = router.query;
|
||||||
const [tab, setTab] = useState<TabState>(activeTab);
|
const [tab, setTab] = useState<TabState>(activeTab);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -97,20 +97,18 @@ const Service = (
|
|||||||
list={[
|
list={[
|
||||||
{ name: "Projects", href: "/dashboard/projects" },
|
{ name: "Projects", href: "/dashboard/projects" },
|
||||||
{
|
{
|
||||||
name: data?.environment.project.name || "",
|
name: data?.project?.name || "",
|
||||||
},
|
href: `/dashboard/project/${projectId}`,
|
||||||
{
|
|
||||||
name: data?.environment?.name || "",
|
|
||||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
|
href: `/dashboard/project/${projectId}/services/application/${applicationId}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
Application: {data?.name} - {data?.environment.project.name} | Dokploy
|
Application: {data?.name} - {data?.project.name} | Dokploy
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -217,7 +215,7 @@ const Service = (
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setTab(e as TabState);
|
setTab(e as TabState);
|
||||||
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/application/${applicationId}?tab=${e}`;
|
const newPath = `/dashboard/project/${projectId}/services/application/${applicationId}?tab=${e}`;
|
||||||
router.push(newPath);
|
router.push(newPath);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -381,7 +379,6 @@ export async function getServerSideProps(
|
|||||||
ctx: GetServerSidePropsContext<{
|
ctx: GetServerSidePropsContext<{
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
activeTab: TabState;
|
activeTab: TabState;
|
||||||
environmentId: string;
|
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const { query, params, req, res } = ctx;
|
const { query, params, req, res } = ctx;
|
||||||
@@ -423,7 +420,6 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
applicationId: params?.applicationId,
|
applicationId: params?.applicationId,
|
||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
environmentId: params?.environmentId,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
@@ -67,7 +67,7 @@ const Service = (
|
|||||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||||
const { composeId, activeTab } = props;
|
const { composeId, activeTab } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, environmentId } = router.query;
|
const { projectId } = router.query;
|
||||||
const [tab, setTab] = useState<TabState>(activeTab);
|
const [tab, setTab] = useState<TabState>(activeTab);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -88,20 +88,18 @@ const Service = (
|
|||||||
list={[
|
list={[
|
||||||
{ name: "Projects", href: "/dashboard/projects" },
|
{ name: "Projects", href: "/dashboard/projects" },
|
||||||
{
|
{
|
||||||
name: data?.environment?.project?.name || "",
|
name: data?.project?.name || "",
|
||||||
},
|
href: `/dashboard/project/${projectId}`,
|
||||||
{
|
|
||||||
name: data?.environment?.name || "",
|
|
||||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
|
href: `/dashboard/project/${projectId}/services/compose/${composeId}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
Compose: {data?.name} - {data?.environment?.project?.name} | Dokploy
|
Compose: {data?.name} - {data?.project.name} | Dokploy
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -210,7 +208,7 @@ const Service = (
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setTab(e as TabState);
|
setTab(e as TabState);
|
||||||
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/compose/${composeId}?tab=${e}`;
|
const newPath = `/dashboard/project/${projectId}/services/compose/${composeId}?tab=${e}`;
|
||||||
router.push(newPath);
|
router.push(newPath);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -377,7 +375,6 @@ export async function getServerSideProps(
|
|||||||
ctx: GetServerSidePropsContext<{
|
ctx: GetServerSidePropsContext<{
|
||||||
composeId: string;
|
composeId: string;
|
||||||
activeTab: TabState;
|
activeTab: TabState;
|
||||||
environmentId: string;
|
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const { query, params, req, res } = ctx;
|
const { query, params, req, res } = ctx;
|
||||||
@@ -417,7 +414,6 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
composeId: params?.composeId,
|
composeId: params?.composeId,
|
||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
environmentId: params?.environmentId,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
@@ -55,7 +55,7 @@ const Mariadb = (
|
|||||||
|
|
||||||
const { mariadbId, activeTab } = props;
|
const { mariadbId, activeTab } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, environmentId } = router.query;
|
const { projectId } = router.query;
|
||||||
const [tab, setSab] = useState<TabState>(activeTab);
|
const [tab, setSab] = useState<TabState>(activeTab);
|
||||||
const { data } = api.mariadb.one.useQuery({ mariadbId });
|
const { data } = api.mariadb.one.useQuery({ mariadbId });
|
||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
@@ -69,22 +69,19 @@ const Mariadb = (
|
|||||||
list={[
|
list={[
|
||||||
{ name: "Projects", href: "/dashboard/projects" },
|
{ name: "Projects", href: "/dashboard/projects" },
|
||||||
{
|
{
|
||||||
name: data?.environment?.project?.name || "",
|
name: data?.project?.name || "",
|
||||||
},
|
href: `/dashboard/project/${projectId}`,
|
||||||
{
|
|
||||||
name: data?.environment?.name || "",
|
|
||||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
|
href: `/dashboard/project/${projectId}/services/mariadb/${mariadbId}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
Database: {data?.name} - {data?.environment?.project?.name} |
|
Database: {data?.name} - {data?.project.name} | Dokploy
|
||||||
Dokploy
|
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl w-full">
|
<Card className="h-full bg-sidebar p-2.5 rounded-xl w-full">
|
||||||
@@ -182,7 +179,7 @@ const Mariadb = (
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/mariadb/${mariadbId}?tab=${e}`;
|
const newPath = `/dashboard/project/${projectId}/services/mariadb/${mariadbId}?tab=${e}`;
|
||||||
|
|
||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
@@ -303,11 +300,7 @@ Mariadb.getLayout = (page: ReactElement) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
ctx: GetServerSidePropsContext<{
|
ctx: GetServerSidePropsContext<{ mariadbId: string; activeTab: TabState }>,
|
||||||
mariadbId: string;
|
|
||||||
activeTab: TabState;
|
|
||||||
environmentId: string;
|
|
||||||
}>,
|
|
||||||
) {
|
) {
|
||||||
const { query, params, req, res } = ctx;
|
const { query, params, req, res } = ctx;
|
||||||
const activeTab = query.tab;
|
const activeTab = query.tab;
|
||||||
@@ -345,7 +338,6 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
mariadbId: params?.mariadbId,
|
mariadbId: params?.mariadbId,
|
||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
environmentId: params?.environmentId,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
@@ -54,7 +54,7 @@ const Mongo = (
|
|||||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||||
const { mongoId, activeTab } = props;
|
const { mongoId, activeTab } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, environmentId } = router.query;
|
const { projectId } = router.query;
|
||||||
const [tab, setSab] = useState<TabState>(activeTab);
|
const [tab, setSab] = useState<TabState>(activeTab);
|
||||||
const { data } = api.mongo.one.useQuery({ mongoId });
|
const { data } = api.mongo.one.useQuery({ mongoId });
|
||||||
|
|
||||||
@@ -69,20 +69,18 @@ const Mongo = (
|
|||||||
list={[
|
list={[
|
||||||
{ name: "Projects", href: "/dashboard/projects" },
|
{ name: "Projects", href: "/dashboard/projects" },
|
||||||
{
|
{
|
||||||
name: data?.environment?.project?.name || "",
|
name: data?.project?.name || "",
|
||||||
},
|
href: `/dashboard/project/${projectId}`,
|
||||||
{
|
|
||||||
name: data?.environment?.name || "",
|
|
||||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
|
href: `/dashboard/project/${projectId}/services/mongo/${mongoId}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
Database: {data?.name} - {data?.environment?.project?.name} | Dokploy
|
Database: {data?.name} - {data?.project.name} | Dokploy
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -182,7 +180,7 @@ const Mongo = (
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/mongo/${mongoId}?tab=${e}`;
|
const newPath = `/dashboard/project/${projectId}/services/mongo/${mongoId}?tab=${e}`;
|
||||||
|
|
||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
@@ -304,11 +302,7 @@ Mongo.getLayout = (page: ReactElement) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
ctx: GetServerSidePropsContext<{
|
ctx: GetServerSidePropsContext<{ mongoId: string; activeTab: TabState }>,
|
||||||
mongoId: string;
|
|
||||||
activeTab: TabState;
|
|
||||||
environmentId: string;
|
|
||||||
}>,
|
|
||||||
) {
|
) {
|
||||||
const { query, params, req, res } = ctx;
|
const { query, params, req, res } = ctx;
|
||||||
const activeTab = query.tab;
|
const activeTab = query.tab;
|
||||||
@@ -346,7 +340,6 @@ export async function getServerSideProps(
|
|||||||
trpcState: helpers.dehydrate(),
|
trpcState: helpers.dehydrate(),
|
||||||
mongoId: params?.mongoId,
|
mongoId: params?.mongoId,
|
||||||
activeTab: (activeTab || "general") as TabState,
|
activeTab: (activeTab || "general") as TabState,
|
||||||
environmentId: params?.environmentId,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
@@ -54,7 +54,7 @@ const MySql = (
|
|||||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||||
const { mysqlId, activeTab } = props;
|
const { mysqlId, activeTab } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, environmentId } = router.query;
|
const { projectId } = router.query;
|
||||||
const [tab, setSab] = useState<TabState>(activeTab);
|
const [tab, setSab] = useState<TabState>(activeTab);
|
||||||
const { data } = api.mysql.one.useQuery({ mysqlId });
|
const { data } = api.mysql.one.useQuery({ mysqlId });
|
||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
@@ -68,22 +68,19 @@ const MySql = (
|
|||||||
list={[
|
list={[
|
||||||
{ name: "Projects", href: "/dashboard/projects" },
|
{ name: "Projects", href: "/dashboard/projects" },
|
||||||
{
|
{
|
||||||
name: data?.environment?.project?.name || "",
|
name: data?.project?.name || "",
|
||||||
},
|
href: `/dashboard/project/${projectId}`,
|
||||||
{
|
|
||||||
name: data?.environment?.name || "",
|
|
||||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
|
href: `/dashboard/project/${projectId}/services/mysql/${mysqlId}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
Database: {data?.name} - {data?.environment?.project?.name} |
|
Database: {data?.name} - {data?.project.name} | Dokploy
|
||||||
Dokploy
|
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -183,7 +180,7 @@ const MySql = (
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/mysql/${mysqlId}?tab=${e}`;
|
const newPath = `/dashboard/project/${projectId}/services/mysql/${mysqlId}?tab=${e}`;
|
||||||
|
|
||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
@@ -289,11 +286,7 @@ MySql.getLayout = (page: ReactElement) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
ctx: GetServerSidePropsContext<{
|
ctx: GetServerSidePropsContext<{ mysqlId: string; activeTab: TabState }>,
|
||||||
mysqlId: string;
|
|
||||||
activeTab: TabState;
|
|
||||||
environmentId: string;
|
|
||||||
}>,
|
|
||||||
) {
|
) {
|
||||||
const { query, params, req, res } = ctx;
|
const { query, params, req, res } = ctx;
|
||||||
const activeTab = query.tab;
|
const activeTab = query.tab;
|
||||||
@@ -54,7 +54,7 @@ const Postgresql = (
|
|||||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||||
const { postgresId, activeTab } = props;
|
const { postgresId, activeTab } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, environmentId } = router.query;
|
const { projectId } = router.query;
|
||||||
const [tab, setSab] = useState<TabState>(activeTab);
|
const [tab, setSab] = useState<TabState>(activeTab);
|
||||||
const { data } = api.postgres.one.useQuery({ postgresId });
|
const { data } = api.postgres.one.useQuery({ postgresId });
|
||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
@@ -68,20 +68,18 @@ const Postgresql = (
|
|||||||
list={[
|
list={[
|
||||||
{ name: "Projects", href: "/dashboard/projects" },
|
{ name: "Projects", href: "/dashboard/projects" },
|
||||||
{
|
{
|
||||||
name: data?.environment?.project?.name || "",
|
name: data?.project?.name || "",
|
||||||
},
|
href: `/dashboard/project/${projectId}`,
|
||||||
{
|
|
||||||
name: data?.environment?.name || "",
|
|
||||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
|
href: `/dashboard/project/${projectId}/services/postgres/${postgresId}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
Database: {data?.name} - {data?.environment?.project?.name} | Dokploy
|
Database: {data?.name} - {data?.project.name} | Dokploy
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -181,11 +179,9 @@ const Postgresql = (
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/postgres/${postgresId}?tab=${e}`;
|
const newPath = `/dashboard/project/${projectId}/services/postgres/${postgresId}?tab=${e}`;
|
||||||
|
|
||||||
router.push(newPath, undefined, {
|
router.push(newPath, undefined, { shallow: true });
|
||||||
shallow: true,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
<div className="flex flex-row items-center justify-between w-full gap-4 overflow-x-scroll">
|
||||||
@@ -232,11 +228,7 @@ const Postgresql = (
|
|||||||
{data?.serverId && isCloud ? (
|
{data?.serverId && isCloud ? (
|
||||||
<ContainerPaidMonitoring
|
<ContainerPaidMonitoring
|
||||||
appName={data?.appName || ""}
|
appName={data?.appName || ""}
|
||||||
baseUrl={`${
|
baseUrl={`${data?.serverId ? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}` : "http://localhost:4500"}`}
|
||||||
data?.serverId
|
|
||||||
? `http://${data?.server?.ipAddress}:${data?.server?.metricsConfig?.server?.port}`
|
|
||||||
: "http://localhost:4500"
|
|
||||||
}`}
|
|
||||||
token={
|
token={
|
||||||
data?.server?.metricsConfig?.server?.token || ""
|
data?.server?.metricsConfig?.server?.token || ""
|
||||||
}
|
}
|
||||||
@@ -292,11 +284,7 @@ Postgresql.getLayout = (page: ReactElement) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
ctx: GetServerSidePropsContext<{
|
ctx: GetServerSidePropsContext<{ postgresId: string; activeTab: TabState }>,
|
||||||
postgresId: string;
|
|
||||||
activeTab: TabState;
|
|
||||||
environmentId: string;
|
|
||||||
}>,
|
|
||||||
) {
|
) {
|
||||||
const { query, params, req, res } = ctx;
|
const { query, params, req, res } = ctx;
|
||||||
const activeTab = query.tab;
|
const activeTab = query.tab;
|
||||||
@@ -53,7 +53,7 @@ const Redis = (
|
|||||||
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
const [_toggleMonitoring, _setToggleMonitoring] = useState(false);
|
||||||
const { redisId, activeTab } = props;
|
const { redisId, activeTab } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { projectId, environmentId } = router.query;
|
const { projectId } = router.query;
|
||||||
const [tab, setSab] = useState<TabState>(activeTab);
|
const [tab, setSab] = useState<TabState>(activeTab);
|
||||||
const { data } = api.redis.one.useQuery({ redisId });
|
const { data } = api.redis.one.useQuery({ redisId });
|
||||||
|
|
||||||
@@ -68,20 +68,18 @@ const Redis = (
|
|||||||
list={[
|
list={[
|
||||||
{ name: "Projects", href: "/dashboard/projects" },
|
{ name: "Projects", href: "/dashboard/projects" },
|
||||||
{
|
{
|
||||||
name: data?.environment?.project?.name || "",
|
name: data?.project?.name || "",
|
||||||
},
|
href: `/dashboard/project/${projectId}`,
|
||||||
{
|
|
||||||
name: data?.environment?.name || "",
|
|
||||||
href: `/dashboard/project/${projectId}/environment/${environmentId}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: data?.name || "",
|
name: data?.name || "",
|
||||||
|
href: `/dashboard/project/${projectId}/services/redis/${redisId}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
Database: {data?.name} - {data?.environment?.project?.name} | Dokploy
|
Database: {data?.name} - {data?.project.name} | Dokploy
|
||||||
</title>
|
</title>
|
||||||
</Head>
|
</Head>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -181,7 +179,7 @@ const Redis = (
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
const newPath = `/dashboard/project/${projectId}/environment/${environmentId}/services/redis/${redisId}?tab=${e}`;
|
const newPath = `/dashboard/project/${projectId}/services/redis/${redisId}?tab=${e}`;
|
||||||
|
|
||||||
router.push(newPath, undefined, { shallow: true });
|
router.push(newPath, undefined, { shallow: true });
|
||||||
}}
|
}}
|
||||||
@@ -293,11 +291,7 @@ Redis.getLayout = (page: ReactElement) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
ctx: GetServerSidePropsContext<{
|
ctx: GetServerSidePropsContext<{ redisId: string; activeTab: TabState }>,
|
||||||
redisId: string;
|
|
||||||
activeTab: TabState;
|
|
||||||
environmentId: string;
|
|
||||||
}>,
|
|
||||||
) {
|
) {
|
||||||
const { query, params, req, res } = ctx;
|
const { query, params, req, res } = ctx;
|
||||||
const activeTab = query.tab;
|
const activeTab = query.tab;
|
||||||
@@ -11,7 +11,6 @@ import { deploymentRouter } from "./routers/deployment";
|
|||||||
import { destinationRouter } from "./routers/destination";
|
import { destinationRouter } from "./routers/destination";
|
||||||
import { dockerRouter } from "./routers/docker";
|
import { dockerRouter } from "./routers/docker";
|
||||||
import { domainRouter } from "./routers/domain";
|
import { domainRouter } from "./routers/domain";
|
||||||
import { environmentRouter } from "./routers/environment";
|
|
||||||
import { gitProviderRouter } from "./routers/git-provider";
|
import { gitProviderRouter } from "./routers/git-provider";
|
||||||
import { giteaRouter } from "./routers/gitea";
|
import { giteaRouter } from "./routers/gitea";
|
||||||
import { githubRouter } from "./routers/github";
|
import { githubRouter } from "./routers/github";
|
||||||
@@ -85,7 +84,6 @@ export const appRouter = createTRPCRouter({
|
|||||||
schedule: scheduleRouter,
|
schedule: scheduleRouter,
|
||||||
rollback: rollbackRouter,
|
rollback: rollbackRouter,
|
||||||
volumeBackups: volumeBackupsRouter,
|
volumeBackups: volumeBackupsRouter,
|
||||||
environment: environmentRouter,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import {
|
|||||||
apiUpdateAi,
|
apiUpdateAi,
|
||||||
deploySuggestionSchema,
|
deploySuggestionSchema,
|
||||||
} from "@dokploy/server/db/schema/ai";
|
} from "@dokploy/server/db/schema/ai";
|
||||||
import {
|
import { createDomain, createMount } from "@dokploy/server/index";
|
||||||
createDomain,
|
|
||||||
createMount,
|
|
||||||
findEnvironmentById,
|
|
||||||
} from "@dokploy/server/index";
|
|
||||||
import {
|
import {
|
||||||
deleteAiSettings,
|
deleteAiSettings,
|
||||||
getAiSettingById,
|
getAiSettingById,
|
||||||
@@ -181,12 +177,10 @@ export const aiRouter = createTRPCRouter({
|
|||||||
deploy: protectedProcedure
|
deploy: protectedProcedure
|
||||||
.input(deploySuggestionSchema)
|
.input(deploySuggestionSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
environment.projectId,
|
input.projectId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -198,6 +192,8 @@ export const aiRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
|
|
||||||
const projectName = slugify(`${project.name} ${input.id}`);
|
const projectName = slugify(`${project.name} ${input.id}`);
|
||||||
|
|
||||||
const compose = await createComposeByTemplate({
|
const compose = await createComposeByTemplate({
|
||||||
@@ -209,7 +205,6 @@ export const aiRouter = createTRPCRouter({
|
|||||||
sourceType: "raw",
|
sourceType: "raw",
|
||||||
appName: `${projectName}-${generatePassword(6)}`,
|
appName: `${projectName}-${generatePassword(6)}`,
|
||||||
isolatedDeployment: true,
|
isolatedDeployment: true,
|
||||||
environmentId: input.environmentId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (input.domains && input.domains?.length > 0) {
|
if (input.domains && input.domains?.length > 0) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
createApplication,
|
createApplication,
|
||||||
deleteAllMiddlewares,
|
deleteAllMiddlewares,
|
||||||
findApplicationById,
|
findApplicationById,
|
||||||
findEnvironmentById,
|
|
||||||
findGitProviderById,
|
findGitProviderById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
getApplicationStats,
|
getApplicationStats,
|
||||||
@@ -24,7 +23,6 @@ import {
|
|||||||
unzipDrop,
|
unzipDrop,
|
||||||
updateApplication,
|
updateApplication,
|
||||||
updateApplicationStatus,
|
updateApplicationStatus,
|
||||||
updateDeploymentStatus,
|
|
||||||
writeConfig,
|
writeConfig,
|
||||||
writeConfigRemote,
|
writeConfigRemote,
|
||||||
// uploadFileSchema
|
// uploadFileSchema
|
||||||
@@ -41,10 +39,8 @@ import {
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import {
|
import {
|
||||||
apiCreateApplication,
|
apiCreateApplication,
|
||||||
apiDeployApplication,
|
|
||||||
apiFindMonitoringStats,
|
apiFindMonitoringStats,
|
||||||
apiFindOneApplication,
|
apiFindOneApplication,
|
||||||
apiRedeployApplication,
|
|
||||||
apiReloadApplication,
|
apiReloadApplication,
|
||||||
apiSaveBitbucketProvider,
|
apiSaveBitbucketProvider,
|
||||||
apiSaveBuildType,
|
apiSaveBuildType,
|
||||||
@@ -59,7 +55,7 @@ import {
|
|||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup";
|
import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup";
|
||||||
import { cancelDeployment, deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import { uploadFileSchema } from "@/utils/schema";
|
import { uploadFileSchema } from "@/utils/schema";
|
||||||
|
|
||||||
export const applicationRouter = createTRPCRouter({
|
export const applicationRouter = createTRPCRouter({
|
||||||
@@ -67,14 +63,10 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.input(apiCreateApplication)
|
.input(apiCreateApplication)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
// Get project from environment
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -87,13 +79,13 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newApplication = await createApplication(input);
|
const newApplication = await createApplication(input);
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
@@ -105,7 +97,6 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
return newApplication;
|
return newApplication;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.log("error", error);
|
|
||||||
if (error instanceof TRPCError) {
|
if (error instanceof TRPCError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -129,8 +120,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -185,7 +175,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !==
|
||||||
ctx.session.activeOrganizationId
|
ctx.session.activeOrganizationId
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -222,8 +212,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -258,17 +247,14 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return application;
|
return result[0];
|
||||||
}),
|
}),
|
||||||
|
|
||||||
stop: protectedProcedure
|
stop: protectedProcedure
|
||||||
.input(apiFindOneApplication)
|
.input(apiFindOneApplication)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const service = await findApplicationById(input.applicationId);
|
const service = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (service.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
service.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to stop this application",
|
message: "You are not authorized to stop this application",
|
||||||
@@ -288,10 +274,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.input(apiFindOneApplication)
|
.input(apiFindOneApplication)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const service = await findApplicationById(input.applicationId);
|
const service = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (service.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
service.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to start this application",
|
message: "You are not authorized to start this application",
|
||||||
@@ -309,12 +292,11 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
redeploy: protectedProcedure
|
redeploy: protectedProcedure
|
||||||
.input(apiRedeployApplication)
|
.input(apiFindOneApplication)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -323,8 +305,8 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
applicationId: input.applicationId,
|
applicationId: input.applicationId,
|
||||||
titleLog: input.title || "Rebuild deployment",
|
titleLog: "Rebuild deployment",
|
||||||
descriptionLog: input.description || "",
|
descriptionLog: "",
|
||||||
type: "redeploy",
|
type: "redeploy",
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
server: !!application.serverId,
|
server: !!application.serverId,
|
||||||
@@ -349,8 +331,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -368,8 +349,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -394,8 +374,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -422,8 +401,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -451,8 +429,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -478,8 +455,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -505,8 +481,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -529,8 +504,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -555,8 +529,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -617,8 +590,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -632,8 +604,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -659,8 +630,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -673,12 +643,11 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
deploy: protectedProcedure
|
deploy: protectedProcedure
|
||||||
.input(apiDeployApplication)
|
.input(apiFindOneApplication)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -687,8 +656,8 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
applicationId: input.applicationId,
|
applicationId: input.applicationId,
|
||||||
titleLog: input.title || "Manual deployment",
|
titleLog: "Manual deployment",
|
||||||
descriptionLog: input.description || "",
|
descriptionLog: "",
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
server: !!application.serverId,
|
server: !!application.serverId,
|
||||||
@@ -714,8 +683,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -730,8 +698,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -767,10 +734,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const app = await findApplicationById(input.applicationId as string);
|
const app = await findApplicationById(input.applicationId as string);
|
||||||
|
|
||||||
if (
|
if (app.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
app.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this application",
|
message: "You are not authorized to deploy this application",
|
||||||
@@ -813,8 +777,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -850,14 +813,13 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
targetEnvironmentId: z.string(),
|
targetProjectId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -865,16 +827,11 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetEnvironment = await findEnvironmentById(
|
const targetProject = await findProjectById(input.targetProjectId);
|
||||||
input.targetEnvironmentId,
|
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
targetEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move to this environment",
|
message: "You are not authorized to move to this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -882,7 +839,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
const updatedApplication = await db
|
const updatedApplication = await db
|
||||||
.update(applications)
|
.update(applications)
|
||||||
.set({
|
.set({
|
||||||
environmentId: input.targetEnvironmentId,
|
projectId: input.targetProjectId,
|
||||||
})
|
})
|
||||||
.where(eq(applications.applicationId, input.applicationId))
|
.where(eq(applications.applicationId, input.applicationId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -897,55 +854,4 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return updatedApplication;
|
return updatedApplication;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
cancelDeployment: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (
|
|
||||||
application.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to cancel this deployment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && application.serverId) {
|
|
||||||
try {
|
|
||||||
await updateApplicationStatus(input.applicationId, "idle");
|
|
||||||
|
|
||||||
if (application.deployments[0]) {
|
|
||||||
await updateDeploymentStatus(
|
|
||||||
application.deployments[0].deploymentId,
|
|
||||||
"done",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await cancelDeployment({
|
|
||||||
applicationId: input.applicationId,
|
|
||||||
applicationType: "application",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: "Deployment cancellation requested",
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message:
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: "Failed to cancel deployment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Deployment cancellation only available in cloud version",
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
deleteMount,
|
deleteMount,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
findDomainsByComposeId,
|
findDomainsByComposeId,
|
||||||
findEnvironmentById,
|
|
||||||
findGitProviderById,
|
findGitProviderById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
findServerById,
|
findServerById,
|
||||||
@@ -29,7 +28,6 @@ import {
|
|||||||
startCompose,
|
startCompose,
|
||||||
stopCompose,
|
stopCompose,
|
||||||
updateCompose,
|
updateCompose,
|
||||||
updateDeploymentStatus,
|
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import {
|
import {
|
||||||
type CompleteTemplate,
|
type CompleteTemplate,
|
||||||
@@ -49,17 +47,15 @@ import { db } from "@/server/db";
|
|||||||
import {
|
import {
|
||||||
apiCreateCompose,
|
apiCreateCompose,
|
||||||
apiDeleteCompose,
|
apiDeleteCompose,
|
||||||
apiDeployCompose,
|
|
||||||
apiFetchServices,
|
apiFetchServices,
|
||||||
apiFindCompose,
|
apiFindCompose,
|
||||||
apiRandomizeCompose,
|
apiRandomizeCompose,
|
||||||
apiRedeployCompose,
|
|
||||||
apiUpdateCompose,
|
apiUpdateCompose,
|
||||||
compose as composeTable,
|
compose as composeTable,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import type { DeploymentJob } from "@/server/queues/queue-types";
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
||||||
import { cancelDeployment, deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import { generatePassword } from "@/templates/utils";
|
import { generatePassword } from "@/templates/utils";
|
||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
|
|
||||||
@@ -68,14 +64,10 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiCreateCompose)
|
.input(apiCreateCompose)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
try {
|
try {
|
||||||
// Get project from environment
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -87,15 +79,14 @@ export const composeRouter = createTRPCRouter({
|
|||||||
message: "You need to use a server to create a compose",
|
message: "You need to use a server to create a compose",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newService = await createCompose({
|
const newService = await createCompose(input);
|
||||||
...input,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await addNewService(
|
await addNewService(
|
||||||
@@ -124,10 +115,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this compose",
|
message: "You are not authorized to access this compose",
|
||||||
@@ -178,10 +166,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiUpdateCompose)
|
.input(apiUpdateCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to update this compose",
|
message: "You are not authorized to update this compose",
|
||||||
@@ -203,7 +188,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
const composeResult = await findComposeById(input.composeId);
|
const composeResult = await findComposeById(input.composeId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
composeResult.environment.project.organizationId !==
|
composeResult.project.organizationId !==
|
||||||
ctx.session.activeOrganizationId
|
ctx.session.activeOrganizationId
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -211,6 +196,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
message: "You are not authorized to delete this compose",
|
message: "You are not authorized to delete this compose",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
4;
|
||||||
|
|
||||||
const result = await db
|
const result = await db
|
||||||
.delete(composeTable)
|
.delete(composeTable)
|
||||||
@@ -229,16 +215,13 @@ export const composeRouter = createTRPCRouter({
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return composeResult;
|
return result[0];
|
||||||
}),
|
}),
|
||||||
cleanQueues: protectedProcedure
|
cleanQueues: protectedProcedure
|
||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to clean this compose",
|
message: "You are not authorized to clean this compose",
|
||||||
@@ -251,10 +234,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiFetchServices)
|
.input(apiFetchServices)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to load this compose",
|
message: "You are not authorized to load this compose",
|
||||||
@@ -271,10 +251,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
)
|
)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to load this compose",
|
message: "You are not authorized to load this compose",
|
||||||
@@ -293,8 +270,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
compose.environment.project.organizationId !==
|
compose.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -320,10 +296,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiRandomizeCompose)
|
.input(apiRandomizeCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to randomize this compose",
|
message: "You are not authorized to randomize this compose",
|
||||||
@@ -335,10 +308,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiRandomizeCompose)
|
.input(apiRandomizeCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to randomize this compose",
|
message: "You are not authorized to randomize this compose",
|
||||||
@@ -353,10 +323,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to get this compose",
|
message: "You are not authorized to get this compose",
|
||||||
@@ -370,14 +337,11 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
deploy: protectedProcedure
|
deploy: protectedProcedure
|
||||||
.input(apiDeployCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
|
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this compose",
|
message: "You are not authorized to deploy this compose",
|
||||||
@@ -385,10 +349,10 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
composeId: input.composeId,
|
composeId: input.composeId,
|
||||||
titleLog: input.title || "Manual deployment",
|
titleLog: "Manual deployment",
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "compose",
|
applicationType: "compose",
|
||||||
descriptionLog: input.description || "",
|
descriptionLog: "",
|
||||||
server: !!compose.serverId,
|
server: !!compose.serverId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -407,13 +371,10 @@ export const composeRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
redeploy: protectedProcedure
|
redeploy: protectedProcedure
|
||||||
.input(apiRedeployCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to redeploy this compose",
|
message: "You are not authorized to redeploy this compose",
|
||||||
@@ -421,10 +382,10 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
composeId: input.composeId,
|
composeId: input.composeId,
|
||||||
titleLog: input.title || "Rebuild deployment",
|
titleLog: "Rebuild deployment",
|
||||||
type: "redeploy",
|
type: "redeploy",
|
||||||
applicationType: "compose",
|
applicationType: "compose",
|
||||||
descriptionLog: input.description || "",
|
descriptionLog: "",
|
||||||
server: !!compose.serverId,
|
server: !!compose.serverId,
|
||||||
};
|
};
|
||||||
if (IS_CLOUD && compose.serverId) {
|
if (IS_CLOUD && compose.serverId) {
|
||||||
@@ -445,10 +406,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to stop this compose",
|
message: "You are not authorized to stop this compose",
|
||||||
@@ -462,10 +420,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to stop this compose",
|
message: "You are not authorized to stop this compose",
|
||||||
@@ -480,10 +435,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
|
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to get this compose",
|
message: "You are not authorized to get this compose",
|
||||||
@@ -496,10 +448,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to refresh this compose",
|
message: "You are not authorized to refresh this compose",
|
||||||
@@ -513,19 +462,17 @@ export const composeRouter = createTRPCRouter({
|
|||||||
deployTemplate: protectedProcedure
|
deployTemplate: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
environmentId: z.string(),
|
projectId: z.string(),
|
||||||
serverId: z.string().optional(),
|
serverId: z.string().optional(),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
baseUrl: z.string().optional(),
|
baseUrl: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
environment.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -543,7 +490,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
const admin = await findUserById(ctx.user.ownerId);
|
const admin = await findUserById(ctx.user.ownerId);
|
||||||
let serverIp = admin.serverIp || "127.0.0.1";
|
let serverIp = admin.serverIp || "127.0.0.1";
|
||||||
|
|
||||||
const project = await findProjectById(environment.projectId);
|
const project = await findProjectById(input.projectId);
|
||||||
|
|
||||||
if (input.serverId) {
|
if (input.serverId) {
|
||||||
const server = await findServerById(input.serverId);
|
const server = await findServerById(input.serverId);
|
||||||
@@ -644,10 +591,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to disconnect this git provider",
|
message: "You are not authorized to disconnect this git provider",
|
||||||
@@ -703,38 +647,30 @@ export const composeRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
composeId: z.string(),
|
composeId: z.string(),
|
||||||
targetEnvironmentId: z.string(),
|
targetProjectId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move this compose",
|
message: "You are not authorized to move this compose",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetEnvironment = await findEnvironmentById(
|
const targetProject = await findProjectById(input.targetProjectId);
|
||||||
input.targetEnvironmentId,
|
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
targetEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move to this environment",
|
message: "You are not authorized to move to this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedCompose = await db
|
const updatedCompose = await db
|
||||||
.update(composeTable)
|
.update(composeTable)
|
||||||
.set({
|
.set({
|
||||||
environmentId: input.targetEnvironmentId,
|
projectId: input.targetProjectId,
|
||||||
})
|
})
|
||||||
.where(eq(composeTable.composeId, input.composeId))
|
.where(eq(composeTable.composeId, input.composeId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -762,8 +698,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
compose.environment.project.organizationId !==
|
compose.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -834,8 +769,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
compose.environment.project.organizationId !==
|
compose.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -929,57 +863,4 @@ export const composeRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
cancelDeployment: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (
|
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to cancel this deployment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && compose.serverId) {
|
|
||||||
try {
|
|
||||||
await updateCompose(input.composeId, {
|
|
||||||
composeStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (compose.deployments[0]) {
|
|
||||||
await updateDeploymentStatus(
|
|
||||||
compose.deployments[0].deploymentId,
|
|
||||||
"done",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await cancelDeployment({
|
|
||||||
composeId: input.composeId,
|
|
||||||
applicationType: "compose",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: "Deployment cancellation requested",
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message:
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: "Failed to cancel deployment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Deployment cancellation only available in cloud version",
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -44,10 +43,7 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
.input(apiFindAllByCompose)
|
.input(apiFindAllByCompose)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this compose",
|
message: "You are not authorized to access this compose",
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
if (input.domainType === "compose" && input.composeId) {
|
if (input.domainType === "compose" && input.composeId) {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (
|
||||||
compose.environment.project.organizationId !==
|
compose.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -45,7 +44,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
} else if (input.domainType === "application" && input.applicationId) {
|
} else if (input.domainType === "application" && input.applicationId) {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !==
|
||||||
ctx.session.activeOrganizationId
|
ctx.session.activeOrganizationId
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -71,8 +70,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -85,10 +83,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
.input(apiFindCompose)
|
.input(apiFindCompose)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const compose = await findComposeById(input.composeId);
|
const compose = await findComposeById(input.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this compose",
|
message: "You are not authorized to access this compose",
|
||||||
@@ -127,8 +122,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
if (currentDomain.applicationId) {
|
if (currentDomain.applicationId) {
|
||||||
const newApp = await findApplicationById(currentDomain.applicationId);
|
const newApp = await findApplicationById(currentDomain.applicationId);
|
||||||
if (
|
if (
|
||||||
newApp.environment.project.organizationId !==
|
newApp.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -138,8 +132,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
} else if (currentDomain.composeId) {
|
} else if (currentDomain.composeId) {
|
||||||
const newCompose = await findComposeById(currentDomain.composeId);
|
const newCompose = await findComposeById(currentDomain.composeId);
|
||||||
if (
|
if (
|
||||||
newCompose.environment.project.organizationId !==
|
newCompose.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -151,8 +144,8 @@ export const domainRouter = createTRPCRouter({
|
|||||||
currentDomain.previewDeploymentId,
|
currentDomain.previewDeploymentId,
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
newPreviewDeployment.application.environment.project
|
newPreviewDeployment.application.project.organizationId !==
|
||||||
.organizationId !== ctx.session.activeOrganizationId
|
ctx.session.activeOrganizationId
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -182,8 +175,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
if (domain.applicationId) {
|
if (domain.applicationId) {
|
||||||
const application = await findApplicationById(domain.applicationId);
|
const application = await findApplicationById(domain.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -192,10 +184,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
} else if (domain.composeId) {
|
} else if (domain.composeId) {
|
||||||
const compose = await findComposeById(domain.composeId);
|
const compose = await findComposeById(domain.composeId);
|
||||||
if (
|
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
compose.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this compose",
|
message: "You are not authorized to access this compose",
|
||||||
@@ -211,7 +200,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
if (domain.applicationId) {
|
if (domain.applicationId) {
|
||||||
const application = await findApplicationById(domain.applicationId);
|
const application = await findApplicationById(domain.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !==
|
||||||
ctx.session.activeOrganizationId
|
ctx.session.activeOrganizationId
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -222,8 +211,7 @@ export const domainRouter = createTRPCRouter({
|
|||||||
} else if (domain.composeId) {
|
} else if (domain.composeId) {
|
||||||
const compose = await findComposeById(domain.composeId);
|
const compose = await findComposeById(domain.composeId);
|
||||||
if (
|
if (
|
||||||
compose.environment.project.organizationId !==
|
compose.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
|
|||||||
@@ -1,343 +0,0 @@
|
|||||||
import {
|
|
||||||
addNewEnvironment,
|
|
||||||
checkEnvironmentAccess,
|
|
||||||
createEnvironment,
|
|
||||||
deleteEnvironment,
|
|
||||||
duplicateEnvironment,
|
|
||||||
findEnvironmentById,
|
|
||||||
findEnvironmentsByProjectId,
|
|
||||||
findMemberById,
|
|
||||||
updateEnvironmentById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiCreateEnvironment,
|
|
||||||
apiDuplicateEnvironment,
|
|
||||||
apiFindOneEnvironment,
|
|
||||||
apiRemoveEnvironment,
|
|
||||||
apiUpdateEnvironment,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
|
|
||||||
// Helper function to filter services within an environment based on user permissions
|
|
||||||
const filterEnvironmentServices = (
|
|
||||||
environment: any,
|
|
||||||
accessedServices: string[],
|
|
||||||
) => ({
|
|
||||||
...environment,
|
|
||||||
applications: environment.applications.filter((app: any) =>
|
|
||||||
accessedServices.includes(app.applicationId),
|
|
||||||
),
|
|
||||||
mariadb: environment.mariadb.filter((db: any) =>
|
|
||||||
accessedServices.includes(db.mariadbId),
|
|
||||||
),
|
|
||||||
mongo: environment.mongo.filter((db: any) =>
|
|
||||||
accessedServices.includes(db.mongoId),
|
|
||||||
),
|
|
||||||
mysql: environment.mysql.filter((db: any) =>
|
|
||||||
accessedServices.includes(db.mysqlId),
|
|
||||||
),
|
|
||||||
postgres: environment.postgres.filter((db: any) =>
|
|
||||||
accessedServices.includes(db.postgresId),
|
|
||||||
),
|
|
||||||
redis: environment.redis.filter((db: any) =>
|
|
||||||
accessedServices.includes(db.redisId),
|
|
||||||
),
|
|
||||||
compose: environment.compose.filter((comp: any) =>
|
|
||||||
accessedServices.includes(comp.composeId),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const environmentRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateEnvironment)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
// Check if user has access to the project
|
|
||||||
// This would typically involve checking project ownership/membership
|
|
||||||
// For now, we'll use a basic organization check
|
|
||||||
|
|
||||||
if (input.name === "production") {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Environment name cannot be production",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const environment = await createEnvironment(input);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
await addNewEnvironment(
|
|
||||||
ctx.user.id,
|
|
||||||
environment.environmentId,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return environment;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `Error creating the environment: ${error instanceof Error ? error.message : error}`,
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneEnvironment)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
await checkEnvironmentAccess(
|
|
||||||
ctx.user.id,
|
|
||||||
input.environmentId,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
"access",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
if (
|
|
||||||
environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to access this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check environment access and filter services for members
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
const { accessedEnvironments, accessedServices } =
|
|
||||||
await findMemberById(ctx.user.id, ctx.session.activeOrganizationId);
|
|
||||||
|
|
||||||
if (!accessedEnvironments.includes(environment.environmentId)) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to access this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter services based on member permissions
|
|
||||||
const filteredEnvironment = filterEnvironmentServices(
|
|
||||||
environment,
|
|
||||||
accessedServices,
|
|
||||||
);
|
|
||||||
|
|
||||||
return filteredEnvironment;
|
|
||||||
}
|
|
||||||
|
|
||||||
return environment;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Environment not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
byProjectId: protectedProcedure
|
|
||||||
.input(z.object({ projectId: z.string() }))
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const environments = await findEnvironmentsByProjectId(input.projectId);
|
|
||||||
|
|
||||||
// Check organization access
|
|
||||||
if (
|
|
||||||
environments.some(
|
|
||||||
(environment) =>
|
|
||||||
environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to access this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter environments for members based on their permissions
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
const { accessedEnvironments, accessedServices } =
|
|
||||||
await findMemberById(ctx.user.id, ctx.session.activeOrganizationId);
|
|
||||||
|
|
||||||
// Filter environments to only show those the member has access to
|
|
||||||
const filteredEnvironments = environments
|
|
||||||
.filter((environment) =>
|
|
||||||
accessedEnvironments.includes(environment.environmentId),
|
|
||||||
)
|
|
||||||
.map((environment) =>
|
|
||||||
filterEnvironmentServices(environment, accessedServices),
|
|
||||||
);
|
|
||||||
|
|
||||||
return filteredEnvironments;
|
|
||||||
}
|
|
||||||
|
|
||||||
return environments;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `Error fetching environments: ${error instanceof Error ? error.message : error}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
remove: protectedProcedure
|
|
||||||
.input(apiRemoveEnvironment)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
await checkEnvironmentAccess(
|
|
||||||
ctx.user.id,
|
|
||||||
input.environmentId,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
"access",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
if (
|
|
||||||
environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to access this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check environment access for members
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
const { accessedEnvironments } = await findMemberById(
|
|
||||||
ctx.user.id,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!accessedEnvironments.includes(environment.environmentId)) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to delete this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedEnvironment = await deleteEnvironment(input.environmentId);
|
|
||||||
return deletedEnvironment;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `Error deleting the environment: ${error instanceof Error ? error.message : error}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateEnvironment)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const { environmentId, ...updateData } = input;
|
|
||||||
|
|
||||||
if (updateData.name === "production") {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Environment name cannot be production",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
await checkEnvironmentAccess(
|
|
||||||
ctx.user.id,
|
|
||||||
environmentId,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
"access",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const currentEnvironment = await findEnvironmentById(environmentId);
|
|
||||||
if (
|
|
||||||
currentEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to access this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check environment access for members
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
const { accessedEnvironments } = await findMemberById(
|
|
||||||
ctx.user.id,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!accessedEnvironments.includes(currentEnvironment.environmentId)
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to update this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const environment = await updateEnvironmentById(
|
|
||||||
environmentId,
|
|
||||||
updateData,
|
|
||||||
);
|
|
||||||
return environment;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `Error updating the environment: ${error instanceof Error ? error.message : error}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
duplicate: protectedProcedure
|
|
||||||
.input(apiDuplicateEnvironment)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
await checkEnvironmentAccess(
|
|
||||||
ctx.user.id,
|
|
||||||
input.environmentId,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
"access",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
if (
|
|
||||||
environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to access this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check environment access for members
|
|
||||||
if (ctx.user.role === "member") {
|
|
||||||
const { accessedEnvironments } = await findMemberById(
|
|
||||||
ctx.user.id,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!accessedEnvironments.includes(environment.environmentId)) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "FORBIDDEN",
|
|
||||||
message: "You are not allowed to duplicate this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const duplicatedEnvironment = await duplicateEnvironment(input);
|
|
||||||
return duplicatedEnvironment;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `Error duplicating the environment: ${error instanceof Error ? error.message : error}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
deployMariadb,
|
deployMariadb,
|
||||||
findBackupsByDbId,
|
findBackupsByDbId,
|
||||||
findMariadbById,
|
findMariadbById,
|
||||||
findEnvironmentById,
|
|
||||||
findProjectById,
|
findProjectById,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
@@ -42,14 +41,10 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiCreateMariaDB)
|
.input(apiCreateMariaDB)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
// Get project from environment
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -62,15 +57,14 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newMariadb = await createMariadb({
|
const newMariadb = await createMariadb(input);
|
||||||
...input,
|
|
||||||
});
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await addNewService(
|
await addNewService(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
@@ -107,10 +101,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
const mariadb = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this Mariadb",
|
message: "You are not authorized to access this Mariadb",
|
||||||
@@ -123,10 +114,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiFindOneMariaDB)
|
.input(apiFindOneMariaDB)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const service = await findMariadbById(input.mariadbId);
|
const service = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (service.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
service.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to start this Mariadb",
|
message: "You are not authorized to start this Mariadb",
|
||||||
@@ -163,10 +151,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiSaveExternalPortMariaDB)
|
.input(apiSaveExternalPortMariaDB)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMariadbById(input.mariadbId);
|
const mongo = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this external port",
|
message: "You are not authorized to save this external port",
|
||||||
@@ -182,10 +167,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiDeployMariaDB)
|
.input(apiDeployMariaDB)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
const mariadb = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this Mariadb",
|
message: "You are not authorized to deploy this Mariadb",
|
||||||
@@ -206,10 +188,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiDeployMariaDB)
|
.input(apiDeployMariaDB)
|
||||||
.subscription(async ({ input, ctx }) => {
|
.subscription(async ({ input, ctx }) => {
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
const mariadb = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this Mariadb",
|
message: "You are not authorized to deploy this Mariadb",
|
||||||
@@ -226,10 +205,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiChangeMariaDBStatus)
|
.input(apiChangeMariaDBStatus)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMariadbById(input.mariadbId);
|
const mongo = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to change this Mariadb status",
|
message: "You are not authorized to change this Mariadb status",
|
||||||
@@ -253,10 +229,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mongo = await findMariadbById(input.mariadbId);
|
const mongo = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to delete this Mariadb",
|
message: "You are not authorized to delete this Mariadb",
|
||||||
@@ -282,10 +255,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiSaveEnvironmentVariablesMariaDB)
|
.input(apiSaveEnvironmentVariablesMariaDB)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
const mariadb = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this environment",
|
message: "You are not authorized to save this environment",
|
||||||
@@ -308,10 +278,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiResetMariadb)
|
.input(apiResetMariadb)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
const mariadb = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to reload this Mariadb",
|
message: "You are not authorized to reload this Mariadb",
|
||||||
@@ -341,10 +308,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { mariadbId, ...rest } = input;
|
const { mariadbId, ...rest } = input;
|
||||||
const mariadb = await findMariadbById(mariadbId);
|
const mariadb = await findMariadbById(mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to update this Mariadb",
|
message: "You are not authorized to update this Mariadb",
|
||||||
@@ -367,31 +331,23 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
mariadbId: z.string(),
|
mariadbId: z.string(),
|
||||||
targetEnvironmentId: z.string(),
|
targetProjectId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
const mariadb = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move this mariadb",
|
message: "You are not authorized to move this mariadb",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetEnvironment = await findEnvironmentById(
|
const targetProject = await findProjectById(input.targetProjectId);
|
||||||
input.targetEnvironmentId,
|
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
targetEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move to this environment",
|
message: "You are not authorized to move to this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,7 +355,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
const updatedMariadb = await db
|
const updatedMariadb = await db
|
||||||
.update(mariadbTable)
|
.update(mariadbTable)
|
||||||
.set({
|
.set({
|
||||||
environmentId: input.targetEnvironmentId,
|
projectId: input.targetProjectId,
|
||||||
})
|
})
|
||||||
.where(eq(mariadbTable.mariadbId, input.mariadbId))
|
.where(eq(mariadbTable.mariadbId, input.mariadbId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -418,10 +374,7 @@ export const mariadbRouter = createTRPCRouter({
|
|||||||
.input(apiRebuildMariadb)
|
.input(apiRebuildMariadb)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
const mariadb = await findMariadbById(input.mariadbId);
|
||||||
if (
|
if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mariadb.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to rebuild this MariaDB database",
|
message: "You are not authorized to rebuild this MariaDB database",
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
deployMongo,
|
deployMongo,
|
||||||
findBackupsByDbId,
|
findBackupsByDbId,
|
||||||
findMongoById,
|
findMongoById,
|
||||||
findEnvironmentById,
|
|
||||||
findProjectById,
|
findProjectById,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
rebuildDatabase,
|
rebuildDatabase,
|
||||||
@@ -42,14 +41,10 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiCreateMongo)
|
.input(apiCreateMongo)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
// Get project from environment
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -62,15 +57,14 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newMongo = await createMongo({
|
const newMongo = await createMongo(input);
|
||||||
...input,
|
|
||||||
});
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await addNewService(
|
await addNewService(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
@@ -112,10 +106,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this mongo",
|
message: "You are not authorized to access this mongo",
|
||||||
@@ -129,10 +120,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const service = await findMongoById(input.mongoId);
|
const service = await findMongoById(input.mongoId);
|
||||||
|
|
||||||
if (
|
if (service.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
service.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to start this mongo",
|
message: "You are not authorized to start this mongo",
|
||||||
@@ -155,10 +143,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
|
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to stop this mongo",
|
message: "You are not authorized to stop this mongo",
|
||||||
@@ -180,10 +165,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiSaveExternalPortMongo)
|
.input(apiSaveExternalPortMongo)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this external port",
|
message: "You are not authorized to save this external port",
|
||||||
@@ -199,10 +181,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiDeployMongo)
|
.input(apiDeployMongo)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this mongo",
|
message: "You are not authorized to deploy this mongo",
|
||||||
@@ -222,10 +201,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiDeployMongo)
|
.input(apiDeployMongo)
|
||||||
.subscription(async ({ input, ctx }) => {
|
.subscription(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this mongo",
|
message: "You are not authorized to deploy this mongo",
|
||||||
@@ -242,10 +218,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiChangeMongoStatus)
|
.input(apiChangeMongoStatus)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to change this mongo status",
|
message: "You are not authorized to change this mongo status",
|
||||||
@@ -260,10 +233,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiResetMongo)
|
.input(apiResetMongo)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to reload this mongo",
|
message: "You are not authorized to reload this mongo",
|
||||||
@@ -302,10 +272,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
|
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to delete this mongo",
|
message: "You are not authorized to delete this mongo",
|
||||||
@@ -331,10 +298,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiSaveEnvironmentVariablesMongo)
|
.input(apiSaveEnvironmentVariablesMongo)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this environment",
|
message: "You are not authorized to save this environment",
|
||||||
@@ -358,10 +322,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { mongoId, ...rest } = input;
|
const { mongoId, ...rest } = input;
|
||||||
const mongo = await findMongoById(mongoId);
|
const mongo = await findMongoById(mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to update this mongo",
|
message: "You are not authorized to update this mongo",
|
||||||
@@ -384,31 +345,23 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
mongoId: z.string(),
|
mongoId: z.string(),
|
||||||
targetEnvironmentId: z.string(),
|
targetProjectId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move this mongo",
|
message: "You are not authorized to move this mongo",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetEnvironment = await findEnvironmentById(
|
const targetProject = await findProjectById(input.targetProjectId);
|
||||||
input.targetEnvironmentId,
|
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
targetEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move to this environment",
|
message: "You are not authorized to move to this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +369,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
const updatedMongo = await db
|
const updatedMongo = await db
|
||||||
.update(mongoTable)
|
.update(mongoTable)
|
||||||
.set({
|
.set({
|
||||||
environmentId: input.targetEnvironmentId,
|
projectId: input.targetProjectId,
|
||||||
})
|
})
|
||||||
.where(eq(mongoTable.mongoId, input.mongoId))
|
.where(eq(mongoTable.mongoId, input.mongoId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -435,10 +388,7 @@ export const mongoRouter = createTRPCRouter({
|
|||||||
.input(apiRebuildMongo)
|
.input(apiRebuildMongo)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMongoById(input.mongoId);
|
const mongo = await findMongoById(input.mongoId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to rebuild this MongoDB database",
|
message: "You are not authorized to rebuild this MongoDB database",
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import {
|
|||||||
deleteMount,
|
deleteMount,
|
||||||
findApplicationById,
|
findApplicationById,
|
||||||
findMountById,
|
findMountById,
|
||||||
findMountOrganizationId,
|
|
||||||
getServiceContainer,
|
getServiceContainer,
|
||||||
updateMount,
|
updateMount,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
apiCreateMount,
|
apiCreateMount,
|
||||||
@@ -26,39 +24,16 @@ export const mountRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
remove: protectedProcedure
|
remove: protectedProcedure
|
||||||
.input(apiRemoveMount)
|
.input(apiRemoveMount)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }) => {
|
||||||
const organizationId = await findMountOrganizationId(input.mountId);
|
|
||||||
if (organizationId !== ctx.session.activeOrganizationId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this mount",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await deleteMount(input.mountId);
|
return await deleteMount(input.mountId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
one: protectedProcedure
|
one: protectedProcedure.input(apiFindOneMount).query(async ({ input }) => {
|
||||||
.input(apiFindOneMount)
|
return await findMountById(input.mountId);
|
||||||
.query(async ({ input, ctx }) => {
|
}),
|
||||||
const organizationId = await findMountOrganizationId(input.mountId);
|
|
||||||
if (organizationId !== ctx.session.activeOrganizationId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this mount",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await findMountById(input.mountId);
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateMount)
|
.input(apiUpdateMount)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }) => {
|
||||||
const organizationId = await findMountOrganizationId(input.mountId);
|
|
||||||
if (organizationId !== ctx.session.activeOrganizationId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this mount",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateMount(input.mountId, input);
|
return await updateMount(input.mountId, input);
|
||||||
}),
|
}),
|
||||||
allNamedByApplicationId: protectedProcedure
|
allNamedByApplicationId: protectedProcedure
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
createMysql,
|
createMysql,
|
||||||
deployMySql,
|
deployMySql,
|
||||||
findBackupsByDbId,
|
findBackupsByDbId,
|
||||||
findEnvironmentById,
|
|
||||||
findMySqlById,
|
findMySqlById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
@@ -43,14 +42,10 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiCreateMySql)
|
.input(apiCreateMySql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
// Get project from environment
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -62,7 +57,8 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
message: "You need to use a server to create a MySQL",
|
message: "You need to use a server to create a MySQL",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
1;
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -70,9 +66,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newMysql = await createMysql({
|
const newMysql = await createMysql(input);
|
||||||
...input,
|
|
||||||
});
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await addNewService(
|
await addNewService(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
@@ -113,10 +107,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
const mysql = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this MySQL",
|
message: "You are not authorized to access this MySQL",
|
||||||
@@ -129,10 +120,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiFindOneMySql)
|
.input(apiFindOneMySql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const service = await findMySqlById(input.mysqlId);
|
const service = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (service.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
service.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to start this MySQL",
|
message: "You are not authorized to start this MySQL",
|
||||||
@@ -154,10 +142,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiFindOneMySql)
|
.input(apiFindOneMySql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
const mongo = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to stop this MySQL",
|
message: "You are not authorized to stop this MySQL",
|
||||||
@@ -178,10 +163,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiSaveExternalPortMySql)
|
.input(apiSaveExternalPortMySql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
const mongo = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this external port",
|
message: "You are not authorized to save this external port",
|
||||||
@@ -197,10 +179,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiDeployMySql)
|
.input(apiDeployMySql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
const mysql = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this MySQL",
|
message: "You are not authorized to deploy this MySQL",
|
||||||
@@ -220,10 +199,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiDeployMySql)
|
.input(apiDeployMySql)
|
||||||
.subscription(async ({ input, ctx }) => {
|
.subscription(async ({ input, ctx }) => {
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
const mysql = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this MySQL",
|
message: "You are not authorized to deploy this MySQL",
|
||||||
@@ -240,10 +216,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiChangeMySqlStatus)
|
.input(apiChangeMySqlStatus)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
const mongo = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to change this MySQL status",
|
message: "You are not authorized to change this MySQL status",
|
||||||
@@ -258,10 +231,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiResetMysql)
|
.input(apiResetMysql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
const mysql = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to reload this MySQL",
|
message: "You are not authorized to reload this MySQL",
|
||||||
@@ -297,10 +267,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
const mongo = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to delete this MySQL",
|
message: "You are not authorized to delete this MySQL",
|
||||||
@@ -326,10 +293,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiSaveEnvironmentVariablesMySql)
|
.input(apiSaveEnvironmentVariablesMySql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
const mysql = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this environment",
|
message: "You are not authorized to save this environment",
|
||||||
@@ -353,10 +317,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { mysqlId, ...rest } = input;
|
const { mysqlId, ...rest } = input;
|
||||||
const mysql = await findMySqlById(mysqlId);
|
const mysql = await findMySqlById(mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to update this MySQL",
|
message: "You are not authorized to update this MySQL",
|
||||||
@@ -379,31 +340,23 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
mysqlId: z.string(),
|
mysqlId: z.string(),
|
||||||
targetEnvironmentId: z.string(),
|
targetProjectId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
const mysql = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move this mysql",
|
message: "You are not authorized to move this mysql",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetEnvironment = await findEnvironmentById(
|
const targetProject = await findProjectById(input.targetProjectId);
|
||||||
input.targetEnvironmentId,
|
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
targetEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move to this environment",
|
message: "You are not authorized to move to this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +364,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
const updatedMysql = await db
|
const updatedMysql = await db
|
||||||
.update(mysqlTable)
|
.update(mysqlTable)
|
||||||
.set({
|
.set({
|
||||||
environmentId: input.targetEnvironmentId,
|
projectId: input.targetProjectId,
|
||||||
})
|
})
|
||||||
.where(eq(mysqlTable.mysqlId, input.mysqlId))
|
.where(eq(mysqlTable.mysqlId, input.mysqlId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -430,10 +383,7 @@ export const mysqlRouter = createTRPCRouter({
|
|||||||
.input(apiRebuildMysql)
|
.input(apiRebuildMysql)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
const mysql = await findMySqlById(input.mysqlId);
|
||||||
if (
|
if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mysql.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to rebuild this MySQL database",
|
message: "You are not authorized to rebuild this MySQL database",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
createDiscordNotification,
|
createDiscordNotification,
|
||||||
createEmailNotification,
|
createEmailNotification,
|
||||||
createGotifyNotification,
|
createGotifyNotification,
|
||||||
createNtfyNotification,
|
|
||||||
createSlackNotification,
|
createSlackNotification,
|
||||||
createTelegramNotification,
|
createTelegramNotification,
|
||||||
findNotificationById,
|
findNotificationById,
|
||||||
@@ -11,14 +10,12 @@ import {
|
|||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendNtfyNotification,
|
|
||||||
sendServerThresholdNotifications,
|
sendServerThresholdNotifications,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
updateDiscordNotification,
|
updateDiscordNotification,
|
||||||
updateEmailNotification,
|
updateEmailNotification,
|
||||||
updateGotifyNotification,
|
updateGotifyNotification,
|
||||||
updateNtfyNotification,
|
|
||||||
updateSlackNotification,
|
updateSlackNotification,
|
||||||
updateTelegramNotification,
|
updateTelegramNotification,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
@@ -36,20 +33,17 @@ import {
|
|||||||
apiCreateDiscord,
|
apiCreateDiscord,
|
||||||
apiCreateEmail,
|
apiCreateEmail,
|
||||||
apiCreateGotify,
|
apiCreateGotify,
|
||||||
apiCreateNtfy,
|
|
||||||
apiCreateSlack,
|
apiCreateSlack,
|
||||||
apiCreateTelegram,
|
apiCreateTelegram,
|
||||||
apiFindOneNotification,
|
apiFindOneNotification,
|
||||||
apiTestDiscordConnection,
|
apiTestDiscordConnection,
|
||||||
apiTestEmailConnection,
|
apiTestEmailConnection,
|
||||||
apiTestGotifyConnection,
|
apiTestGotifyConnection,
|
||||||
apiTestNtfyConnection,
|
|
||||||
apiTestSlackConnection,
|
apiTestSlackConnection,
|
||||||
apiTestTelegramConnection,
|
apiTestTelegramConnection,
|
||||||
apiUpdateDiscord,
|
apiUpdateDiscord,
|
||||||
apiUpdateEmail,
|
apiUpdateEmail,
|
||||||
apiUpdateGotify,
|
apiUpdateGotify,
|
||||||
apiUpdateNtfy,
|
|
||||||
apiUpdateSlack,
|
apiUpdateSlack,
|
||||||
apiUpdateTelegram,
|
apiUpdateTelegram,
|
||||||
notifications,
|
notifications,
|
||||||
@@ -327,7 +321,6 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
discord: true,
|
discord: true,
|
||||||
email: true,
|
email: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
|
||||||
},
|
},
|
||||||
orderBy: desc(notifications.createdAt),
|
orderBy: desc(notifications.createdAt),
|
||||||
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
||||||
@@ -453,64 +446,6 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
createNtfy: adminProcedure
|
|
||||||
.input(apiCreateNtfy)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createNtfyNotification(
|
|
||||||
input,
|
|
||||||
ctx.session.activeOrganizationId,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error creating the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
updateNtfy: adminProcedure
|
|
||||||
.input(apiUpdateNtfy)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const notification = await findNotificationById(input.notificationId);
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
notification.organizationId !== ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateNtfyNotification({
|
|
||||||
...input,
|
|
||||||
organizationId: ctx.session.activeOrganizationId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
testNtfyConnection: adminProcedure
|
|
||||||
.input(apiTestNtfyConnection)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
await sendNtfyNotification(
|
|
||||||
input,
|
|
||||||
"Test Notification",
|
|
||||||
"",
|
|
||||||
"view, visit Dokploy on Github, https://github.com/dokploy/dokploy, clear=true;",
|
|
||||||
"Hi, From Dokploy 👋",
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error testing the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
|
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
|
||||||
return await db.query.notifications.findMany({
|
return await db.query.notifications.findMany({
|
||||||
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
|||||||
@@ -27,44 +27,22 @@ export const portRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
one: protectedProcedure
|
one: protectedProcedure.input(apiFindOnePort).query(async ({ input }) => {
|
||||||
.input(apiFindOnePort)
|
try {
|
||||||
.query(async ({ input, ctx }) => {
|
return await finPortById(input.portId);
|
||||||
try {
|
} catch (error) {
|
||||||
const port = await finPortById(input.portId);
|
throw new TRPCError({
|
||||||
if (
|
code: "BAD_REQUEST",
|
||||||
port.application.environment.project.organizationId !==
|
message: "Port not found",
|
||||||
ctx.session.activeOrganizationId
|
cause: error,
|
||||||
) {
|
});
|
||||||
throw new TRPCError({
|
}
|
||||||
code: "UNAUTHORIZED",
|
}),
|
||||||
message: "You are not authorized to access this port",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return port;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Port not found",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
delete: protectedProcedure
|
delete: protectedProcedure
|
||||||
.input(apiFindOnePort)
|
.input(apiFindOnePort)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }) => {
|
||||||
const port = await finPortById(input.portId);
|
|
||||||
if (
|
|
||||||
port.application.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this port",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
return await removePortById(input.portId);
|
return removePortById(input.portId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error ? error.message : "Error input: Deleting port";
|
error instanceof Error ? error.message : "Error input: Deleting port";
|
||||||
@@ -76,19 +54,9 @@ export const portRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdatePort)
|
.input(apiUpdatePort)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }) => {
|
||||||
const port = await finPortById(input.portId);
|
|
||||||
if (
|
|
||||||
port.application.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this port",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
return await updatePortById(input.portId, input);
|
return updatePortById(input.portId, input);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error ? error.message : "Error updating the port";
|
error instanceof Error ? error.message : "Error updating the port";
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
createPostgres,
|
createPostgres,
|
||||||
deployPostgres,
|
deployPostgres,
|
||||||
findBackupsByDbId,
|
findBackupsByDbId,
|
||||||
findEnvironmentById,
|
|
||||||
findPostgresById,
|
findPostgresById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
@@ -42,14 +41,10 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.input(apiCreatePostgres)
|
.input(apiCreatePostgres)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
// Get project from environment
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -62,15 +57,14 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newPostgres = await createPostgres({
|
const newPostgres = await createPostgres(input);
|
||||||
...input,
|
|
||||||
});
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await addNewService(
|
await addNewService(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
@@ -113,8 +107,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -129,10 +122,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const service = await findPostgresById(input.postgresId);
|
const service = await findPostgresById(input.postgresId);
|
||||||
|
|
||||||
if (
|
if (service.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
service.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to start this Postgres",
|
message: "You are not authorized to start this Postgres",
|
||||||
@@ -155,8 +145,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -180,8 +169,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -199,8 +187,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -223,8 +210,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.subscription(async ({ input, ctx }) => {
|
.subscription(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -243,8 +229,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -270,8 +255,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -296,8 +280,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -322,8 +305,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -355,8 +337,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
const { postgresId, ...rest } = input;
|
const { postgresId, ...rest } = input;
|
||||||
const postgres = await findPostgresById(postgresId);
|
const postgres = await findPostgresById(postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -380,14 +361,13 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
postgresId: z.string(),
|
postgresId: z.string(),
|
||||||
targetEnvironmentId: z.string(),
|
targetProjectId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -395,16 +375,11 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetEnvironment = await findEnvironmentById(
|
const targetProject = await findProjectById(input.targetProjectId);
|
||||||
input.targetEnvironmentId,
|
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
targetEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move to this environment",
|
message: "You are not authorized to move to this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +387,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
const updatedPostgres = await db
|
const updatedPostgres = await db
|
||||||
.update(postgresTable)
|
.update(postgresTable)
|
||||||
.set({
|
.set({
|
||||||
environmentId: input.targetEnvironmentId,
|
projectId: input.targetProjectId,
|
||||||
})
|
})
|
||||||
.where(eq(postgresTable.postgresId, input.postgresId))
|
.where(eq(postgresTable.postgresId, input.postgresId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -432,8 +407,7 @@ export const postgresRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const postgres = await findPostgresById(input.postgresId);
|
const postgres = await findPostgresById(input.postgresId);
|
||||||
if (
|
if (
|
||||||
postgres.environment.project.organizationId !==
|
postgres.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ export const previewDeploymentRouter = createTRPCRouter({
|
|||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -32,7 +31,7 @@ export const previewDeploymentRouter = createTRPCRouter({
|
|||||||
input.previewDeploymentId,
|
input.previewDeploymentId,
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
previewDeployment.application.environment.project.organizationId !==
|
previewDeployment.application.project.organizationId !==
|
||||||
ctx.session.activeOrganizationId
|
ctx.session.activeOrganizationId
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -50,7 +49,7 @@ export const previewDeploymentRouter = createTRPCRouter({
|
|||||||
input.previewDeploymentId,
|
input.previewDeploymentId,
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
previewDeployment.application.environment.project.organizationId !==
|
previewDeployment.application.project.organizationId !==
|
||||||
ctx.session.activeOrganizationId
|
ctx.session.activeOrganizationId
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
deleteProject,
|
deleteProject,
|
||||||
findApplicationById,
|
findApplicationById,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
findEnvironmentById,
|
|
||||||
findMariadbById,
|
findMariadbById,
|
||||||
findMemberById,
|
findMemberById,
|
||||||
findMongoById,
|
findMongoById,
|
||||||
@@ -44,7 +43,6 @@ import {
|
|||||||
apiUpdateProject,
|
apiUpdateProject,
|
||||||
applications,
|
applications,
|
||||||
compose,
|
compose,
|
||||||
environments,
|
|
||||||
mariadb,
|
mariadb,
|
||||||
mongo,
|
mongo,
|
||||||
mysql,
|
mysql,
|
||||||
@@ -82,7 +80,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await addNewProject(
|
await addNewProject(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.project.projectId,
|
project.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -119,42 +117,29 @@ export const projectRouter = createTRPCRouter({
|
|||||||
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
||||||
),
|
),
|
||||||
with: {
|
with: {
|
||||||
environments: {
|
applications: {
|
||||||
with: {
|
where: buildServiceFilter(
|
||||||
applications: {
|
applications.applicationId,
|
||||||
where: buildServiceFilter(
|
accessedServices,
|
||||||
applications.applicationId,
|
),
|
||||||
accessedServices,
|
},
|
||||||
),
|
compose: {
|
||||||
},
|
where: buildServiceFilter(compose.composeId, accessedServices),
|
||||||
compose: {
|
},
|
||||||
where: buildServiceFilter(
|
mariadb: {
|
||||||
compose.composeId,
|
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
||||||
accessedServices,
|
},
|
||||||
),
|
mongo: {
|
||||||
},
|
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
||||||
mariadb: {
|
},
|
||||||
where: buildServiceFilter(
|
mysql: {
|
||||||
mariadb.mariadbId,
|
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
||||||
accessedServices,
|
},
|
||||||
),
|
postgres: {
|
||||||
},
|
where: buildServiceFilter(postgres.postgresId, accessedServices),
|
||||||
mongo: {
|
},
|
||||||
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
redis: {
|
||||||
},
|
where: buildServiceFilter(redis.redisId, accessedServices),
|
||||||
mysql: {
|
|
||||||
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
|
||||||
},
|
|
||||||
postgres: {
|
|
||||||
where: buildServiceFilter(
|
|
||||||
postgres.postgresId,
|
|
||||||
accessedServices,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
redis: {
|
|
||||||
where: buildServiceFilter(redis.redisId, accessedServices),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -179,22 +164,15 @@ export const projectRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
all: protectedProcedure.query(async ({ ctx }) => {
|
all: protectedProcedure.query(async ({ ctx }) => {
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
const { accessedProjects, accessedEnvironments, accessedServices } =
|
const { accessedProjects, accessedServices } = await findMemberById(
|
||||||
await findMemberById(ctx.user.id, ctx.session.activeOrganizationId);
|
ctx.user.id,
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
|
|
||||||
if (accessedProjects.length === 0) {
|
if (accessedProjects.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build environment filter
|
|
||||||
const environmentFilter =
|
|
||||||
accessedEnvironments.length === 0
|
|
||||||
? sql`false`
|
|
||||||
: sql`${environments.environmentId} IN (${sql.join(
|
|
||||||
accessedEnvironments.map((envId) => sql`${envId}`),
|
|
||||||
sql`, `,
|
|
||||||
)})`;
|
|
||||||
|
|
||||||
return await db.query.projects.findMany({
|
return await db.query.projects.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
sql`${projects.projectId} IN (${sql.join(
|
sql`${projects.projectId} IN (${sql.join(
|
||||||
@@ -204,39 +182,31 @@ export const projectRouter = createTRPCRouter({
|
|||||||
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
eq(projects.organizationId, ctx.session.activeOrganizationId),
|
||||||
),
|
),
|
||||||
with: {
|
with: {
|
||||||
environments: {
|
applications: {
|
||||||
where: environmentFilter,
|
where: buildServiceFilter(
|
||||||
with: {
|
applications.applicationId,
|
||||||
applications: {
|
accessedServices,
|
||||||
where: buildServiceFilter(
|
),
|
||||||
applications.applicationId,
|
with: { domains: true },
|
||||||
accessedServices,
|
},
|
||||||
),
|
mariadb: {
|
||||||
with: { domains: true },
|
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
||||||
},
|
},
|
||||||
mariadb: {
|
mongo: {
|
||||||
where: buildServiceFilter(mariadb.mariadbId, accessedServices),
|
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
||||||
},
|
},
|
||||||
mongo: {
|
mysql: {
|
||||||
where: buildServiceFilter(mongo.mongoId, accessedServices),
|
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
||||||
},
|
},
|
||||||
mysql: {
|
postgres: {
|
||||||
where: buildServiceFilter(mysql.mysqlId, accessedServices),
|
where: buildServiceFilter(postgres.postgresId, accessedServices),
|
||||||
},
|
},
|
||||||
postgres: {
|
redis: {
|
||||||
where: buildServiceFilter(
|
where: buildServiceFilter(redis.redisId, accessedServices),
|
||||||
postgres.postgresId,
|
},
|
||||||
accessedServices,
|
compose: {
|
||||||
),
|
where: buildServiceFilter(compose.composeId, accessedServices),
|
||||||
},
|
with: { domains: true },
|
||||||
redis: {
|
|
||||||
where: buildServiceFilter(redis.redisId, accessedServices),
|
|
||||||
},
|
|
||||||
compose: {
|
|
||||||
where: buildServiceFilter(compose.composeId, accessedServices),
|
|
||||||
with: { domains: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: desc(projects.createdAt),
|
orderBy: desc(projects.createdAt),
|
||||||
@@ -245,23 +215,19 @@ export const projectRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return await db.query.projects.findMany({
|
return await db.query.projects.findMany({
|
||||||
with: {
|
with: {
|
||||||
environments: {
|
applications: {
|
||||||
with: {
|
with: {
|
||||||
applications: {
|
domains: true,
|
||||||
with: {
|
},
|
||||||
domains: true,
|
},
|
||||||
},
|
mariadb: true,
|
||||||
},
|
mongo: true,
|
||||||
mariadb: true,
|
mysql: true,
|
||||||
mongo: true,
|
postgres: true,
|
||||||
mysql: true,
|
redis: true,
|
||||||
postgres: true,
|
compose: {
|
||||||
redis: true,
|
with: {
|
||||||
compose: {
|
domains: true,
|
||||||
with: {
|
|
||||||
domains: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -322,7 +288,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
duplicate: protectedProcedure
|
duplicate: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
sourceEnvironmentId: z.string(),
|
sourceProjectId: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
includeServices: z.boolean().default(true),
|
includeServices: z.boolean().default(true),
|
||||||
@@ -356,15 +322,9 @@ export const projectRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get source project
|
// Get source project
|
||||||
const sourceEnvironment = input.duplicateInSameProject
|
const sourceProject = await findProjectById(input.sourceProjectId);
|
||||||
? await findEnvironmentById(input.sourceEnvironmentId)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (
|
if (sourceProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
input.duplicateInSameProject &&
|
|
||||||
sourceEnvironment?.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
@@ -373,17 +333,15 @@ export const projectRouter = createTRPCRouter({
|
|||||||
|
|
||||||
// Create new project or use existing one
|
// Create new project or use existing one
|
||||||
const targetProject = input.duplicateInSameProject
|
const targetProject = input.duplicateInSameProject
|
||||||
? sourceEnvironment
|
? sourceProject
|
||||||
: await createProject(
|
: await createProject(
|
||||||
{
|
{
|
||||||
name: input.name,
|
name: input.name,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
env: sourceEnvironment?.project.env,
|
env: sourceProject.env,
|
||||||
},
|
},
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
).then((value) => value.environment);
|
);
|
||||||
|
|
||||||
console.log("targetProject", targetProject);
|
|
||||||
|
|
||||||
if (input.includeServices) {
|
if (input.includeServices) {
|
||||||
const servicesToDuplicate = input.selectedServices || [];
|
const servicesToDuplicate = input.selectedServices || [];
|
||||||
@@ -416,7 +374,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${application.name} (copy)`
|
? `${application.name} (copy)`
|
||||||
: application.name,
|
: application.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
projectId: targetProject.projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
@@ -486,7 +444,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${postgres.name} (copy)`
|
? `${postgres.name} (copy)`
|
||||||
: postgres.name,
|
: postgres.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
projectId: targetProject.projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mount of mounts) {
|
for (const mount of mounts) {
|
||||||
@@ -522,7 +480,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${mariadb.name} (copy)`
|
? `${mariadb.name} (copy)`
|
||||||
: mariadb.name,
|
: mariadb.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
projectId: targetProject.projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mount of mounts) {
|
for (const mount of mounts) {
|
||||||
@@ -558,7 +516,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${mongo.name} (copy)`
|
? `${mongo.name} (copy)`
|
||||||
: mongo.name,
|
: mongo.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
projectId: targetProject.projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mount of mounts) {
|
for (const mount of mounts) {
|
||||||
@@ -594,7 +552,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${mysql.name} (copy)`
|
? `${mysql.name} (copy)`
|
||||||
: mysql.name,
|
: mysql.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
projectId: targetProject.projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mount of mounts) {
|
for (const mount of mounts) {
|
||||||
@@ -630,7 +588,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${redis.name} (copy)`
|
? `${redis.name} (copy)`
|
||||||
: redis.name,
|
: redis.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
projectId: targetProject.projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mount of mounts) {
|
for (const mount of mounts) {
|
||||||
@@ -665,7 +623,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
name: input.duplicateInSameProject
|
name: input.duplicateInSameProject
|
||||||
? `${compose.name} (copy)`
|
? `${compose.name} (copy)`
|
||||||
: compose.name,
|
: compose.name,
|
||||||
environmentId: targetProject?.environmentId || "",
|
projectId: targetProject.projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const mount of mounts) {
|
for (const mount of mounts) {
|
||||||
@@ -700,7 +658,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
if (!input.duplicateInSameProject && ctx.user.role === "member") {
|
if (!input.duplicateInSameProject && ctx.user.role === "member") {
|
||||||
await addNewProject(
|
await addNewProject(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
targetProject?.projectId || "",
|
targetProject.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ export const redirectsRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -35,8 +34,7 @@ export const redirectsRouter = createTRPCRouter({
|
|||||||
const redirect = await findRedirectById(input.redirectId);
|
const redirect = await findRedirectById(input.redirectId);
|
||||||
const application = await findApplicationById(redirect.applicationId);
|
const application = await findApplicationById(redirect.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -51,8 +49,7 @@ export const redirectsRouter = createTRPCRouter({
|
|||||||
const redirect = await findRedirectById(input.redirectId);
|
const redirect = await findRedirectById(input.redirectId);
|
||||||
const application = await findApplicationById(redirect.applicationId);
|
const application = await findApplicationById(redirect.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -67,8 +64,7 @@ export const redirectsRouter = createTRPCRouter({
|
|||||||
const redirect = await findRedirectById(input.redirectId);
|
const redirect = await findRedirectById(input.redirectId);
|
||||||
const application = await findApplicationById(redirect.applicationId);
|
const application = await findApplicationById(redirect.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
createMount,
|
createMount,
|
||||||
createRedis,
|
createRedis,
|
||||||
deployRedis,
|
deployRedis,
|
||||||
findEnvironmentById,
|
|
||||||
findProjectById,
|
findProjectById,
|
||||||
findRedisById,
|
findRedisById,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
@@ -41,14 +40,10 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiCreateRedis)
|
.input(apiCreateRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
// Get project from environment
|
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
|
||||||
const project = await findProjectById(environment.projectId);
|
|
||||||
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await checkServiceAccess(
|
await checkServiceAccess(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
project.projectId,
|
input.projectId,
|
||||||
ctx.session.activeOrganizationId,
|
ctx.session.activeOrganizationId,
|
||||||
"create",
|
"create",
|
||||||
);
|
);
|
||||||
@@ -61,15 +56,14 @@ export const redisRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const project = await findProjectById(input.projectId);
|
||||||
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
if (project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this project",
|
message: "You are not authorized to access this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newRedis = await createRedis({
|
const newRedis = await createRedis(input);
|
||||||
...input,
|
|
||||||
});
|
|
||||||
if (ctx.user.role === "member") {
|
if (ctx.user.role === "member") {
|
||||||
await addNewService(
|
await addNewService(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
@@ -86,7 +80,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
type: "volume",
|
type: "volume",
|
||||||
});
|
});
|
||||||
|
|
||||||
return newRedis;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -104,10 +98,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to access this Redis",
|
message: "You are not authorized to access this Redis",
|
||||||
@@ -120,10 +111,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiFindOneRedis)
|
.input(apiFindOneRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to start this Redis",
|
message: "You are not authorized to start this Redis",
|
||||||
@@ -145,10 +133,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiResetRedis)
|
.input(apiResetRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to reload this Redis",
|
message: "You are not authorized to reload this Redis",
|
||||||
@@ -178,10 +163,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiFindOneRedis)
|
.input(apiFindOneRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to stop this Redis",
|
message: "You are not authorized to stop this Redis",
|
||||||
@@ -202,10 +184,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiSaveExternalPortRedis)
|
.input(apiSaveExternalPortRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findRedisById(input.redisId);
|
const mongo = await findRedisById(input.redisId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this external port",
|
message: "You are not authorized to save this external port",
|
||||||
@@ -221,10 +200,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiDeployRedis)
|
.input(apiDeployRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this Redis",
|
message: "You are not authorized to deploy this Redis",
|
||||||
@@ -244,10 +220,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiDeployRedis)
|
.input(apiDeployRedis)
|
||||||
.subscription(async ({ input, ctx }) => {
|
.subscription(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to deploy this Redis",
|
message: "You are not authorized to deploy this Redis",
|
||||||
@@ -263,10 +236,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiChangeRedisStatus)
|
.input(apiChangeRedisStatus)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const mongo = await findRedisById(input.redisId);
|
const mongo = await findRedisById(input.redisId);
|
||||||
if (
|
if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
mongo.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to change this Redis status",
|
message: "You are not authorized to change this Redis status",
|
||||||
@@ -291,10 +261,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
|
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to delete this Redis",
|
message: "You are not authorized to delete this Redis",
|
||||||
@@ -317,10 +284,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiSaveEnvironmentVariablesRedis)
|
.input(apiSaveEnvironmentVariablesRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to save this environment",
|
message: "You are not authorized to save this environment",
|
||||||
@@ -360,31 +324,23 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
redisId: z.string(),
|
redisId: z.string(),
|
||||||
targetEnvironmentId: z.string(),
|
targetProjectId: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move this redis",
|
message: "You are not authorized to move this redis",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetEnvironment = await findEnvironmentById(
|
const targetProject = await findProjectById(input.targetProjectId);
|
||||||
input.targetEnvironmentId,
|
if (targetProject.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
);
|
|
||||||
if (
|
|
||||||
targetEnvironment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to move to this environment",
|
message: "You are not authorized to move to this project",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +348,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
const updatedRedis = await db
|
const updatedRedis = await db
|
||||||
.update(redisTable)
|
.update(redisTable)
|
||||||
.set({
|
.set({
|
||||||
environmentId: input.targetEnvironmentId,
|
projectId: input.targetProjectId,
|
||||||
})
|
})
|
||||||
.where(eq(redisTable.redisId, input.redisId))
|
.where(eq(redisTable.redisId, input.redisId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -411,10 +367,7 @@ export const redisRouter = createTRPCRouter({
|
|||||||
.input(apiRebuildRedis)
|
.input(apiRebuildRedis)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const redis = await findRedisById(input.redisId);
|
const redis = await findRedisById(input.redisId);
|
||||||
if (
|
if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||||
redis.environment.project.organizationId !==
|
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
message: "You are not authorized to rebuild this Redis database",
|
message: "You are not authorized to rebuild this Redis database",
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { removeRollbackById, rollback } from "@dokploy/server";
|
||||||
findRollbackById,
|
|
||||||
removeRollbackById,
|
|
||||||
rollback,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||||
import { apiFindOneRollback } from "@/server/db/schema";
|
import { apiFindOneRollback } from "@/server/db/schema";
|
||||||
@@ -26,18 +22,8 @@ export const rollbackRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
rollback: protectedProcedure
|
rollback: protectedProcedure
|
||||||
.input(apiFindOneRollback)
|
.input(apiFindOneRollback)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input }) => {
|
||||||
try {
|
try {
|
||||||
const currentRollback = await findRollbackById(input.rollbackId);
|
|
||||||
if (
|
|
||||||
currentRollback?.deployment?.application?.environment?.project
|
|
||||||
.organizationId !== ctx.session.activeOrganizationId
|
|
||||||
) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to rollback this deployment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await rollback(input.rollbackId);
|
return await rollback(input.rollbackId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ export const securityRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const application = await findApplicationById(input.applicationId);
|
const application = await findApplicationById(input.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -35,8 +34,7 @@ export const securityRouter = createTRPCRouter({
|
|||||||
const security = await findSecurityById(input.securityId);
|
const security = await findSecurityById(input.securityId);
|
||||||
const application = await findApplicationById(security.applicationId);
|
const application = await findApplicationById(security.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -51,8 +49,7 @@ export const securityRouter = createTRPCRouter({
|
|||||||
const security = await findSecurityById(input.securityId);
|
const security = await findSecurityById(input.securityId);
|
||||||
const application = await findApplicationById(security.applicationId);
|
const application = await findApplicationById(security.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
@@ -67,8 +64,7 @@ export const securityRouter = createTRPCRouter({
|
|||||||
const security = await findSecurityById(input.securityId);
|
const security = await findSecurityById(input.securityId);
|
||||||
const application = await findApplicationById(security.applicationId);
|
const application = await findApplicationById(security.applicationId);
|
||||||
if (
|
if (
|
||||||
application.environment.project.organizationId !==
|
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||||
ctx.session.activeOrganizationId
|
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "UNAUTHORIZED",
|
code: "UNAUTHORIZED",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
|
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq, sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import { dump, load } from "js-yaml";
|
import { dump, load } from "js-yaml";
|
||||||
import { scheduledJobs, scheduleJob } from "node-schedule";
|
import { scheduledJobs, scheduleJob } from "node-schedule";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -60,8 +60,6 @@ import {
|
|||||||
apiServerSchema,
|
apiServerSchema,
|
||||||
apiTraefikConfig,
|
apiTraefikConfig,
|
||||||
apiUpdateDockerCleanup,
|
apiUpdateDockerCleanup,
|
||||||
projects,
|
|
||||||
server,
|
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { removeJob, schedule } from "@/server/utils/backup";
|
import { removeJob, schedule } from "@/server/utils/backup";
|
||||||
import packageInfo from "../../../package.json";
|
import packageInfo from "../../../package.json";
|
||||||
@@ -708,18 +706,6 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
isCloud: publicProcedure.query(async () => {
|
isCloud: publicProcedure.query(async () => {
|
||||||
return IS_CLOUD;
|
return IS_CLOUD;
|
||||||
}),
|
}),
|
||||||
isUserSubscribed: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
const haveServers = await db.query.server.findMany({
|
|
||||||
where: eq(server.organizationId, ctx.session?.activeOrganizationId || ""),
|
|
||||||
});
|
|
||||||
const haveProjects = await db.query.projects.findMany({
|
|
||||||
where: eq(
|
|
||||||
projects.organizationId,
|
|
||||||
ctx.session?.activeOrganizationId || "",
|
|
||||||
),
|
|
||||||
});
|
|
||||||
return haveServers.length > 0 || haveProjects.length > 0;
|
|
||||||
}),
|
|
||||||
health: publicProcedure.query(async () => {
|
health: publicProcedure.query(async () => {
|
||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import * as schema from "./schema";
|
|||||||
declare global {
|
declare global {
|
||||||
var db: PostgresJsDatabase<typeof schema> | undefined;
|
var db: PostgresJsDatabase<typeof schema> | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbUrl =
|
const dbUrl =
|
||||||
process.env.DATABASE_URL ||
|
process.env.DATABASE_URL ||
|
||||||
"postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
|
"postgres://dokploy:amukds4wi9001583845717ad2@dokploy-postgres:5432/dokploy";
|
||||||
|
|||||||
@@ -23,30 +23,3 @@ export const deploy = async (jobData: DeploymentJob) => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type CancelDeploymentData =
|
|
||||||
| { applicationId: string; applicationType: "application" }
|
|
||||||
| { composeId: string; applicationType: "compose" };
|
|
||||||
|
|
||||||
export const cancelDeployment = async (cancelData: CancelDeploymentData) => {
|
|
||||||
try {
|
|
||||||
const result = await fetch(`${process.env.SERVER_URL}/cancel-deployment`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-API-Key": process.env.API_KEY || "NO-DEFINED",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(cancelData),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.ok) {
|
|
||||||
const errorData = await result.json().catch(() => ({}));
|
|
||||||
throw new Error(errorData.message || "Failed to cancel deployment");
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await result.json();
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -112,10 +112,6 @@ export const member = pgTable("member", {
|
|||||||
.array()
|
.array()
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(sql`ARRAY[]::text[]`),
|
.default(sql`ARRAY[]::text[]`),
|
||||||
accessedEnvironments: text("accessedEnvironments")
|
|
||||||
.array()
|
|
||||||
.notNull()
|
|
||||||
.default(sql`ARRAY[]::text[]`),
|
|
||||||
accessedServices: text("accesedServices")
|
accessedServices: text("accesedServices")
|
||||||
.array()
|
.array()
|
||||||
.notNull()
|
.notNull()
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const apiUpdateAi = createSchema
|
|||||||
.omit({ organizationId: true });
|
.omit({ organizationId: true });
|
||||||
|
|
||||||
export const deploySuggestionSchema = z.object({
|
export const deploySuggestionSchema = z.object({
|
||||||
environmentId: z.string().min(1),
|
projectId: z.string().min(1),
|
||||||
id: z.string().min(1),
|
id: z.string().min(1),
|
||||||
dockerCompose: z.string().min(1),
|
dockerCompose: z.string().min(1),
|
||||||
envVariables: z.string(),
|
envVariables: z.string(),
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { z } from "zod";
|
|||||||
import { bitbucket } from "./bitbucket";
|
import { bitbucket } from "./bitbucket";
|
||||||
import { deployments } from "./deployment";
|
import { deployments } from "./deployment";
|
||||||
import { domains } from "./domain";
|
import { domains } from "./domain";
|
||||||
import { environments } from "./environment";
|
|
||||||
import { gitea } from "./gitea";
|
import { gitea } from "./gitea";
|
||||||
import { github } from "./github";
|
import { github } from "./github";
|
||||||
import { gitlab } from "./gitlab";
|
import { gitlab } from "./gitlab";
|
||||||
@@ -180,9 +179,9 @@ export const applications = pgTable("application", {
|
|||||||
registryId: text("registryId").references(() => registry.registryId, {
|
registryId: text("registryId").references(() => registry.registryId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
environmentId: text("environmentId")
|
projectId: text("projectId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
githubId: text("githubId").references(() => github.githubId, {
|
githubId: text("githubId").references(() => github.githubId, {
|
||||||
onDelete: "set null",
|
onDelete: "set null",
|
||||||
}),
|
}),
|
||||||
@@ -203,9 +202,9 @@ export const applications = pgTable("application", {
|
|||||||
export const applicationsRelations = relations(
|
export const applicationsRelations = relations(
|
||||||
applications,
|
applications,
|
||||||
({ one, many }) => ({
|
({ one, many }) => ({
|
||||||
environment: one(environments, {
|
project: one(projects, {
|
||||||
fields: [applications.environmentId],
|
fields: [applications.projectId],
|
||||||
references: [environments.environmentId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
deployments: many(deployments),
|
deployments: many(deployments),
|
||||||
customGitSSHKey: one(sshKeys, {
|
customGitSSHKey: one(sshKeys, {
|
||||||
@@ -274,7 +273,7 @@ const createSchema = createInsertSchema(applications, {
|
|||||||
customGitBuildPath: z.string().optional(),
|
customGitBuildPath: z.string().optional(),
|
||||||
customGitUrl: z.string().optional(),
|
customGitUrl: z.string().optional(),
|
||||||
buildPath: z.string().optional(),
|
buildPath: z.string().optional(),
|
||||||
environmentId: z.string(),
|
projectId: z.string(),
|
||||||
sourceType: z
|
sourceType: z
|
||||||
.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"])
|
.enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"])
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -318,7 +317,7 @@ export const apiCreateApplication = createSchema.pick({
|
|||||||
name: true,
|
name: true,
|
||||||
appName: true,
|
appName: true,
|
||||||
description: true,
|
description: true,
|
||||||
environmentId: true,
|
projectId: true,
|
||||||
serverId: true,
|
serverId: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -328,26 +327,6 @@ export const apiFindOneApplication = createSchema
|
|||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiDeployApplication = createSchema
|
|
||||||
.pick({
|
|
||||||
applicationId: true,
|
|
||||||
})
|
|
||||||
.extend({
|
|
||||||
applicationId: z.string().min(1),
|
|
||||||
title: z.string().optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiRedeployApplication = createSchema
|
|
||||||
.pick({
|
|
||||||
applicationId: true,
|
|
||||||
})
|
|
||||||
.extend({
|
|
||||||
applicationId: z.string().min(1),
|
|
||||||
title: z.string().optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiReloadApplication = createSchema
|
export const apiReloadApplication = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
appName: true,
|
appName: true,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { backups } from "./backups";
|
|||||||
import { bitbucket } from "./bitbucket";
|
import { bitbucket } from "./bitbucket";
|
||||||
import { deployments } from "./deployment";
|
import { deployments } from "./deployment";
|
||||||
import { domains } from "./domain";
|
import { domains } from "./domain";
|
||||||
import { environments } from "./environment";
|
|
||||||
import { gitea } from "./gitea";
|
import { gitea } from "./gitea";
|
||||||
import { github } from "./github";
|
import { github } from "./github";
|
||||||
import { gitlab } from "./gitlab";
|
import { gitlab } from "./gitlab";
|
||||||
@@ -85,9 +84,9 @@ export const compose = pgTable("compose", {
|
|||||||
.default(false),
|
.default(false),
|
||||||
triggerType: triggerType("triggerType").default("push"),
|
triggerType: triggerType("triggerType").default("push"),
|
||||||
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
|
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
|
||||||
environmentId: text("environmentId")
|
projectId: text("projectId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
@@ -110,9 +109,9 @@ export const compose = pgTable("compose", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const composeRelations = relations(compose, ({ one, many }) => ({
|
export const composeRelations = relations(compose, ({ one, many }) => ({
|
||||||
environment: one(environments, {
|
project: one(projects, {
|
||||||
fields: [compose.environmentId],
|
fields: [compose.projectId],
|
||||||
references: [environments.environmentId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
deployments: many(deployments),
|
deployments: many(deployments),
|
||||||
mounts: many(mounts),
|
mounts: many(mounts),
|
||||||
@@ -150,7 +149,7 @@ const createSchema = createInsertSchema(compose, {
|
|||||||
description: z.string(),
|
description: z.string(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
composeFile: z.string().optional(),
|
composeFile: z.string().optional(),
|
||||||
environmentId: z.string(),
|
projectId: z.string(),
|
||||||
customGitSSHKeyId: z.string().optional(),
|
customGitSSHKeyId: z.string().optional(),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
composePath: z.string().min(1),
|
composePath: z.string().min(1),
|
||||||
@@ -161,7 +160,7 @@ const createSchema = createInsertSchema(compose, {
|
|||||||
export const apiCreateCompose = createSchema.pick({
|
export const apiCreateCompose = createSchema.pick({
|
||||||
name: true,
|
name: true,
|
||||||
description: true,
|
description: true,
|
||||||
environmentId: true,
|
projectId: true,
|
||||||
composeType: true,
|
composeType: true,
|
||||||
appName: true,
|
appName: true,
|
||||||
serverId: true,
|
serverId: true,
|
||||||
@@ -170,7 +169,7 @@ export const apiCreateCompose = createSchema.pick({
|
|||||||
|
|
||||||
export const apiCreateComposeByTemplate = createSchema
|
export const apiCreateComposeByTemplate = createSchema
|
||||||
.pick({
|
.pick({
|
||||||
environmentId: true,
|
projectId: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
id: z.string().min(1),
|
id: z.string().min(1),
|
||||||
@@ -181,18 +180,6 @@ export const apiFindCompose = z.object({
|
|||||||
composeId: z.string().min(1),
|
composeId: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiDeployCompose = z.object({
|
|
||||||
composeId: z.string().min(1),
|
|
||||||
title: z.string().optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiRedeployCompose = z.object({
|
|
||||||
composeId: z.string().min(1),
|
|
||||||
title: z.string().optional(),
|
|
||||||
description: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiDeleteCompose = z.object({
|
export const apiDeleteCompose = z.object({
|
||||||
composeId: z.string().min(1),
|
composeId: z.string().min(1),
|
||||||
deleteVolumes: z.boolean(),
|
deleteVolumes: z.boolean(),
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { relations } from "drizzle-orm";
|
|
||||||
import { pgTable, text } from "drizzle-orm/pg-core";
|
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { applications } from "./application";
|
|
||||||
import { compose } from "./compose";
|
|
||||||
import { mariadb } from "./mariadb";
|
|
||||||
import { mongo } from "./mongo";
|
|
||||||
import { mysql } from "./mysql";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
import { projects } from "./project";
|
|
||||||
import { redis } from "./redis";
|
|
||||||
|
|
||||||
export const environments = pgTable("environment", {
|
|
||||||
environmentId: text("environmentId")
|
|
||||||
.notNull()
|
|
||||||
.primaryKey()
|
|
||||||
.$defaultFn(() => nanoid()),
|
|
||||||
name: text("name").notNull(),
|
|
||||||
description: text("description"),
|
|
||||||
createdAt: text("createdAt")
|
|
||||||
.notNull()
|
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
|
||||||
env: text("env").notNull().default(""),
|
|
||||||
projectId: text("projectId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => projects.projectId, { onDelete: "cascade" }),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const environmentRelations = relations(
|
|
||||||
environments,
|
|
||||||
({ one, many }) => ({
|
|
||||||
project: one(projects, {
|
|
||||||
fields: [environments.projectId],
|
|
||||||
references: [projects.projectId],
|
|
||||||
}),
|
|
||||||
applications: many(applications),
|
|
||||||
mariadb: many(mariadb),
|
|
||||||
postgres: many(postgres),
|
|
||||||
mysql: many(mysql),
|
|
||||||
redis: many(redis),
|
|
||||||
mongo: many(mongo),
|
|
||||||
compose: many(compose),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const createSchema = createInsertSchema(environments, {
|
|
||||||
environmentId: z.string().min(1),
|
|
||||||
name: z.string().min(1),
|
|
||||||
description: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiCreateEnvironment = createSchema.pick({
|
|
||||||
name: true,
|
|
||||||
description: true,
|
|
||||||
projectId: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiFindOneEnvironment = createSchema
|
|
||||||
.pick({
|
|
||||||
environmentId: true,
|
|
||||||
})
|
|
||||||
.required();
|
|
||||||
|
|
||||||
export const apiRemoveEnvironment = createSchema
|
|
||||||
.pick({
|
|
||||||
environmentId: true,
|
|
||||||
})
|
|
||||||
.required();
|
|
||||||
|
|
||||||
export const apiUpdateEnvironment = createSchema.partial().extend({
|
|
||||||
environmentId: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiDuplicateEnvironment = createSchema
|
|
||||||
.pick({
|
|
||||||
environmentId: true,
|
|
||||||
name: true,
|
|
||||||
description: true,
|
|
||||||
})
|
|
||||||
.required({
|
|
||||||
environmentId: true,
|
|
||||||
name: true,
|
|
||||||
});
|
|
||||||
@@ -8,7 +8,6 @@ export * from "./compose";
|
|||||||
export * from "./deployment";
|
export * from "./deployment";
|
||||||
export * from "./destination";
|
export * from "./destination";
|
||||||
export * from "./domain";
|
export * from "./domain";
|
||||||
export * from "./environment";
|
|
||||||
export * from "./git-provider";
|
export * from "./git-provider";
|
||||||
export * from "./gitea";
|
export * from "./gitea";
|
||||||
export * from "./github";
|
export * from "./github";
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { backups } from "./backups";
|
import { backups } from "./backups";
|
||||||
import { environments } from "./environment";
|
|
||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import {
|
import {
|
||||||
applicationStatus,
|
applicationStatus,
|
||||||
@@ -66,19 +66,18 @@ export const mariadb = pgTable("mariadb", {
|
|||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
|
projectId: text("projectId")
|
||||||
environmentId: text("environmentId")
|
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
|
export const mariadbRelations = relations(mariadb, ({ one, many }) => ({
|
||||||
environment: one(environments, {
|
project: one(projects, {
|
||||||
fields: [mariadb.environmentId],
|
fields: [mariadb.projectId],
|
||||||
references: [environments.environmentId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
backups: many(backups),
|
backups: many(backups),
|
||||||
mounts: many(mounts),
|
mounts: many(mounts),
|
||||||
@@ -95,19 +94,8 @@ const createSchema = createInsertSchema(mariadb, {
|
|||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
databaseName: z.string().min(1),
|
databaseName: z.string().min(1),
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
databasePassword: z
|
databasePassword: z.string(),
|
||||||
.string()
|
databaseRootPassword: z.string().optional(),
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
}),
|
|
||||||
databaseRootPassword: z
|
|
||||||
.string()
|
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
dockerImage: z.string().default("mariadb:6"),
|
dockerImage: z.string().default("mariadb:6"),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
@@ -115,7 +103,7 @@ const createSchema = createInsertSchema(mariadb, {
|
|||||||
memoryLimit: z.string().optional(),
|
memoryLimit: z.string().optional(),
|
||||||
cpuReservation: z.string().optional(),
|
cpuReservation: z.string().optional(),
|
||||||
cpuLimit: z.string().optional(),
|
cpuLimit: z.string().optional(),
|
||||||
environmentId: z.string(),
|
projectId: z.string(),
|
||||||
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
@@ -136,7 +124,7 @@ export const apiCreateMariaDB = createSchema
|
|||||||
appName: true,
|
appName: true,
|
||||||
dockerImage: true,
|
dockerImage: true,
|
||||||
databaseRootPassword: true,
|
databaseRootPassword: true,
|
||||||
environmentId: true,
|
projectId: true,
|
||||||
description: true,
|
description: true,
|
||||||
databaseName: true,
|
databaseName: true,
|
||||||
databaseUser: true,
|
databaseUser: true,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { backups } from "./backups";
|
import { backups } from "./backups";
|
||||||
import { environments } from "./environment";
|
|
||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import {
|
import {
|
||||||
applicationStatus,
|
applicationStatus,
|
||||||
@@ -62,10 +62,9 @@ export const mongo = pgTable("mongo", {
|
|||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
|
projectId: text("projectId")
|
||||||
environmentId: text("environmentId")
|
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
@@ -73,9 +72,9 @@ export const mongo = pgTable("mongo", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const mongoRelations = relations(mongo, ({ one, many }) => ({
|
export const mongoRelations = relations(mongo, ({ one, many }) => ({
|
||||||
environment: one(environments, {
|
project: one(projects, {
|
||||||
fields: [mongo.environmentId],
|
fields: [mongo.projectId],
|
||||||
references: [environments.environmentId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
backups: many(backups),
|
backups: many(backups),
|
||||||
mounts: many(mounts),
|
mounts: many(mounts),
|
||||||
@@ -90,12 +89,7 @@ const createSchema = createInsertSchema(mongo, {
|
|||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
mongoId: z.string(),
|
mongoId: z.string(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
databasePassword: z
|
databasePassword: z.string(),
|
||||||
.string()
|
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
}),
|
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
dockerImage: z.string().default("mongo:15"),
|
dockerImage: z.string().default("mongo:15"),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
@@ -104,7 +98,7 @@ const createSchema = createInsertSchema(mongo, {
|
|||||||
memoryLimit: z.string().optional(),
|
memoryLimit: z.string().optional(),
|
||||||
cpuReservation: z.string().optional(),
|
cpuReservation: z.string().optional(),
|
||||||
cpuLimit: z.string().optional(),
|
cpuLimit: z.string().optional(),
|
||||||
environmentId: z.string(),
|
projectId: z.string(),
|
||||||
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
@@ -125,7 +119,7 @@ export const apiCreateMongo = createSchema
|
|||||||
name: true,
|
name: true,
|
||||||
appName: true,
|
appName: true,
|
||||||
dockerImage: true,
|
dockerImage: true,
|
||||||
environmentId: true,
|
projectId: true,
|
||||||
description: true,
|
description: true,
|
||||||
databaseUser: true,
|
databaseUser: true,
|
||||||
databasePassword: true,
|
databasePassword: true,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { backups } from "./backups";
|
import { backups } from "./backups";
|
||||||
import { environments } from "./environment";
|
|
||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import {
|
import {
|
||||||
applicationStatus,
|
applicationStatus,
|
||||||
@@ -64,19 +64,18 @@ export const mysql = pgTable("mysql", {
|
|||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
|
projectId: text("projectId")
|
||||||
environmentId: text("environmentId")
|
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mysqlRelations = relations(mysql, ({ one, many }) => ({
|
export const mysqlRelations = relations(mysql, ({ one, many }) => ({
|
||||||
environment: one(environments, {
|
project: one(projects, {
|
||||||
fields: [mysql.environmentId],
|
fields: [mysql.projectId],
|
||||||
references: [environments.environmentId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
backups: many(backups),
|
backups: many(backups),
|
||||||
mounts: many(mounts),
|
mounts: many(mounts),
|
||||||
@@ -93,19 +92,8 @@ const createSchema = createInsertSchema(mysql, {
|
|||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
databaseName: z.string().min(1),
|
databaseName: z.string().min(1),
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
databasePassword: z
|
databasePassword: z.string(),
|
||||||
.string()
|
databaseRootPassword: z.string().optional(),
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
}),
|
|
||||||
databaseRootPassword: z
|
|
||||||
.string()
|
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
dockerImage: z.string().default("mysql:8"),
|
dockerImage: z.string().default("mysql:8"),
|
||||||
command: z.string().optional(),
|
command: z.string().optional(),
|
||||||
env: z.string().optional(),
|
env: z.string().optional(),
|
||||||
@@ -113,6 +101,7 @@ const createSchema = createInsertSchema(mysql, {
|
|||||||
memoryLimit: z.string().optional(),
|
memoryLimit: z.string().optional(),
|
||||||
cpuReservation: z.string().optional(),
|
cpuReservation: z.string().optional(),
|
||||||
cpuLimit: z.string().optional(),
|
cpuLimit: z.string().optional(),
|
||||||
|
projectId: z.string(),
|
||||||
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
@@ -132,7 +121,7 @@ export const apiCreateMySql = createSchema
|
|||||||
name: true,
|
name: true,
|
||||||
appName: true,
|
appName: true,
|
||||||
dockerImage: true,
|
dockerImage: true,
|
||||||
environmentId: true,
|
projectId: true,
|
||||||
description: true,
|
description: true,
|
||||||
databaseName: true,
|
databaseName: true,
|
||||||
databaseUser: true,
|
databaseUser: true,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export const notificationType = pgEnum("notificationType", [
|
|||||||
"discord",
|
"discord",
|
||||||
"email",
|
"email",
|
||||||
"gotify",
|
"gotify",
|
||||||
"ntfy",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const notifications = pgTable("notification", {
|
export const notifications = pgTable("notification", {
|
||||||
@@ -45,9 +44,6 @@ export const notifications = pgTable("notification", {
|
|||||||
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
|
||||||
onDelete: "cascade",
|
|
||||||
}),
|
|
||||||
organizationId: text("organizationId")
|
organizationId: text("organizationId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => organization.id, { onDelete: "cascade" }),
|
.references(() => organization.id, { onDelete: "cascade" }),
|
||||||
@@ -105,17 +101,6 @@ export const gotify = pgTable("gotify", {
|
|||||||
decoration: boolean("decoration"),
|
decoration: boolean("decoration"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ntfy = pgTable("ntfy", {
|
|
||||||
ntfyId: text("ntfyId")
|
|
||||||
.notNull()
|
|
||||||
.primaryKey()
|
|
||||||
.$defaultFn(() => nanoid()),
|
|
||||||
serverUrl: text("serverUrl").notNull(),
|
|
||||||
topic: text("topic").notNull(),
|
|
||||||
accessToken: text("accessToken").notNull(),
|
|
||||||
priority: integer("priority").notNull().default(3),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||||
slack: one(slack, {
|
slack: one(slack, {
|
||||||
fields: [notifications.slackId],
|
fields: [notifications.slackId],
|
||||||
@@ -137,10 +122,6 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|||||||
fields: [notifications.gotifyId],
|
fields: [notifications.gotifyId],
|
||||||
references: [gotify.gotifyId],
|
references: [gotify.gotifyId],
|
||||||
}),
|
}),
|
||||||
ntfy: one(ntfy, {
|
|
||||||
fields: [notifications.ntfyId],
|
|
||||||
references: [ntfy.ntfyId],
|
|
||||||
}),
|
|
||||||
organization: one(organization, {
|
organization: one(organization, {
|
||||||
fields: [notifications.organizationId],
|
fields: [notifications.organizationId],
|
||||||
references: [organization.id],
|
references: [organization.id],
|
||||||
@@ -303,36 +284,6 @@ export const apiTestGotifyConnection = apiCreateGotify
|
|||||||
decoration: z.boolean().optional(),
|
decoration: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateNtfy = notificationsSchema
|
|
||||||
.pick({
|
|
||||||
appBuildError: true,
|
|
||||||
databaseBackup: true,
|
|
||||||
dokployRestart: true,
|
|
||||||
name: true,
|
|
||||||
appDeploy: true,
|
|
||||||
dockerCleanup: true,
|
|
||||||
})
|
|
||||||
.extend({
|
|
||||||
serverUrl: z.string().min(1),
|
|
||||||
topic: z.string().min(1),
|
|
||||||
accessToken: z.string().min(1),
|
|
||||||
priority: z.number().min(1),
|
|
||||||
})
|
|
||||||
.required();
|
|
||||||
|
|
||||||
export const apiUpdateNtfy = apiCreateNtfy.partial().extend({
|
|
||||||
notificationId: z.string().min(1),
|
|
||||||
ntfyId: z.string().min(1),
|
|
||||||
organizationId: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiTestNtfyConnection = apiCreateNtfy.pick({
|
|
||||||
serverUrl: true,
|
|
||||||
topic: true,
|
|
||||||
accessToken: true,
|
|
||||||
priority: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const apiFindOneNotification = notificationsSchema
|
export const apiFindOneNotification = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
notificationId: true,
|
notificationId: true,
|
||||||
@@ -352,9 +303,7 @@ export const apiSendTest = notificationsSchema
|
|||||||
password: z.string(),
|
password: z.string(),
|
||||||
toAddresses: z.array(z.string()),
|
toAddresses: z.array(z.string()),
|
||||||
serverUrl: z.string(),
|
serverUrl: z.string(),
|
||||||
topic: z.string(),
|
|
||||||
appToken: z.string(),
|
appToken: z.string(),
|
||||||
accessToken: z.string(),
|
|
||||||
priority: z.number(),
|
priority: z.number(),
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { createInsertSchema } from "drizzle-zod";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { backups } from "./backups";
|
import { backups } from "./backups";
|
||||||
import { environments } from "./environment";
|
|
||||||
import { mounts } from "./mount";
|
import { mounts } from "./mount";
|
||||||
|
import { projects } from "./project";
|
||||||
import { server } from "./server";
|
import { server } from "./server";
|
||||||
import {
|
import {
|
||||||
applicationStatus,
|
applicationStatus,
|
||||||
@@ -64,19 +64,18 @@ export const postgres = pgTable("postgres", {
|
|||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
|
projectId: text("projectId")
|
||||||
environmentId: text("environmentId")
|
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => environments.environmentId, { onDelete: "cascade" }),
|
.references(() => projects.projectId, { onDelete: "cascade" }),
|
||||||
serverId: text("serverId").references(() => server.serverId, {
|
serverId: text("serverId").references(() => server.serverId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const postgresRelations = relations(postgres, ({ one, many }) => ({
|
export const postgresRelations = relations(postgres, ({ one, many }) => ({
|
||||||
environment: one(environments, {
|
project: one(projects, {
|
||||||
fields: [postgres.environmentId],
|
fields: [postgres.projectId],
|
||||||
references: [environments.environmentId],
|
references: [projects.projectId],
|
||||||
}),
|
}),
|
||||||
backups: many(backups),
|
backups: many(backups),
|
||||||
mounts: many(mounts),
|
mounts: many(mounts),
|
||||||
@@ -89,12 +88,7 @@ export const postgresRelations = relations(postgres, ({ one, many }) => ({
|
|||||||
const createSchema = createInsertSchema(postgres, {
|
const createSchema = createInsertSchema(postgres, {
|
||||||
postgresId: z.string(),
|
postgresId: z.string(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
databasePassword: z
|
databasePassword: z.string(),
|
||||||
.string()
|
|
||||||
.regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, {
|
|
||||||
message:
|
|
||||||
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility",
|
|
||||||
}),
|
|
||||||
databaseName: z.string().min(1),
|
databaseName: z.string().min(1),
|
||||||
databaseUser: z.string().min(1),
|
databaseUser: z.string().min(1),
|
||||||
dockerImage: z.string().default("postgres:15"),
|
dockerImage: z.string().default("postgres:15"),
|
||||||
@@ -104,7 +98,7 @@ const createSchema = createInsertSchema(postgres, {
|
|||||||
memoryLimit: z.string().optional(),
|
memoryLimit: z.string().optional(),
|
||||||
cpuReservation: z.string().optional(),
|
cpuReservation: z.string().optional(),
|
||||||
cpuLimit: z.string().optional(),
|
cpuLimit: z.string().optional(),
|
||||||
environmentId: z.string(),
|
projectId: z.string(),
|
||||||
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
applicationStatus: z.enum(["idle", "running", "done", "error"]),
|
||||||
externalPort: z.number(),
|
externalPort: z.number(),
|
||||||
createdAt: z.string(),
|
createdAt: z.string(),
|
||||||
@@ -128,7 +122,7 @@ export const apiCreatePostgres = createSchema
|
|||||||
databaseUser: true,
|
databaseUser: true,
|
||||||
databasePassword: true,
|
databasePassword: true,
|
||||||
dockerImage: true,
|
dockerImage: true,
|
||||||
environmentId: true,
|
projectId: true,
|
||||||
description: true,
|
description: true,
|
||||||
serverId: true,
|
serverId: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import { createInsertSchema } from "drizzle-zod";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { organization } from "./account";
|
import { organization } from "./account";
|
||||||
import { environments } from "./environment";
|
import { applications } from "./application";
|
||||||
|
import { compose } from "./compose";
|
||||||
|
import { mariadb } from "./mariadb";
|
||||||
|
import { mongo } from "./mongo";
|
||||||
|
import { mysql } from "./mysql";
|
||||||
|
import { postgres } from "./postgres";
|
||||||
|
import { redis } from "./redis";
|
||||||
|
|
||||||
export const projects = pgTable("project", {
|
export const projects = pgTable("project", {
|
||||||
projectId: text("projectId")
|
projectId: text("projectId")
|
||||||
@@ -24,7 +30,13 @@ export const projects = pgTable("project", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const projectRelations = relations(projects, ({ many, one }) => ({
|
export const projectRelations = relations(projects, ({ many, one }) => ({
|
||||||
environments: many(environments),
|
mysql: many(mysql),
|
||||||
|
postgres: many(postgres),
|
||||||
|
mariadb: many(mariadb),
|
||||||
|
applications: many(applications),
|
||||||
|
mongo: many(mongo),
|
||||||
|
redis: many(redis),
|
||||||
|
compose: many(compose),
|
||||||
organization: one(organization, {
|
organization: one(organization, {
|
||||||
fields: [projects.organizationId],
|
fields: [projects.organizationId],
|
||||||
references: [organization.id],
|
references: [organization.id],
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user