refactor(server): split logic in to packages

This commit is contained in:
Mauricio Siu
2024-09-29 02:28:58 -06:00
parent 21dee4abac
commit 9b7aacc934
280 changed files with 28569 additions and 196 deletions

View File

@@ -0,0 +1,21 @@
version: "2"
services:
zookeeper:
image: "confluentinc/cp-zookeeper:latest"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
kafka:
image: "confluentinc/cp-kafka:latest"
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
ports:
- "9092:9092"

View File

@@ -1,14 +1,29 @@
{
"name": "my-app",
"scripts": {
"dev": "tsx watch src/index.ts"
"dev": "tsx watch src/index.ts",
"tsc": "tsc --project tsconfig.json"
},
"dependencies": {
"react": "18.2.0",
"react-dom": "18.2.0",
"@dokploy/builders": "workspace:*",
"@hono/node-server": "^1.12.1",
"hono": "^4.5.8",
"dotenv": "^16.3.1"
"dotenv": "^16.3.1",
"@upstash/qstash": "2.7.9",
"ioredis": "5.4.1",
"nats": "2.28.2",
"bullmq": "5.13.2",
"@nerimity/mimiqueue": "1.2.3",
"timers": "0.1.1",
"redis": "4.7.0",
"date-fns": "4.1.0"
},
"devDependencies": {
"typescript": "^5.4.2",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/node": "^20.11.17",
"tsx": "^4.7.1"
}

View File

@@ -1,66 +1,57 @@
import { serve } from "@hono/node-server";
import { config } from "dotenv";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { validateLemonSqueezyLicense } from "./utils";
config();
import "dotenv/config";
import { createClient } from "redis";
import { Queue } from "@nerimity/mimiqueue";
import { deployApplication } from "@dokploy/builders";
// import { setTimeout } from "timers/promises";
const app = new Hono();
app.use(
"/*",
cors({
origin: ["http://localhost:3000", "http://localhost:3001"], // Ajusta esto a los orígenes de tu aplicación Next.js
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowHeaders: ["Content-Type", "Authorization"],
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
maxAge: 600,
credentials: true,
}),
);
export const LEMON_SQUEEZY_API_KEY = process.env.LEMON_SQUEEZY_API_KEY;
export const LEMON_SQUEEZY_STORE_ID = process.env.LEMON_SQUEEZY_STORE_ID;
app.get("/v1/health", (c) => {
return c.text("Hello Hono!");
const redisClient = createClient({
socket: {
host: "localhost",
port: 6379,
},
// password: "xlfvpQ0ma2BkkkPX",
});
app.post("/v1/validate-license", async (c) => {
const { licenseKey } = await c.req.json();
if (!licenseKey) {
return c.json({ error: "License key is required" }, 400);
}
try {
const licenseValidation = await validateLemonSqueezyLicense(licenseKey);
if (licenseValidation.valid) {
return c.json({
valid: true,
message: "License is valid",
metadata: licenseValidation.meta,
});
}
return c.json(
app.post("/publish", async (c) => {
const { userId, applicationId } = await c.req.json();
queue
.add(
{
valid: false,
message: licenseValidation.error || "Invalid license",
userId,
applicationId,
},
400,
);
} catch (error) {
console.error("Error during license validation:", error);
return c.json({ error: "Internal server error" }, 500);
}
});
{ groupName: userId },
)
.then((res) => {
console.log(res);
});
return c.json({ message: `Despliegue encolado para el usuario ${userId}` });
});
// await redisClient.connect();
// await redisClient.flushAll();
const queue = new Queue({
name: "deployments",
process: async (data) => {
// await setTimeout(8000);
await deployApplication({
applicationId: data.applicationId,
titleLog: "HHHHH",
descriptionLog: "",
});
return { done: "lol", data };
},
redisClient,
});
const port = 4000;
console.log(`Server is running on port ${port}`);
(async () => {
await redisClient.connect();
await redisClient.flushAll();
})();
serve({
fetch: app.fetch,
port,
});
console.log("Starting Server ✅");
serve({ fetch: app.fetch, port });

82
apps/api/src/test.ts Normal file
View File

@@ -0,0 +1,82 @@
import { Hono } from "hono";
import { Client } from "@upstash/qstash";
import { serve } from "@hono/node-server";
import dotenv from "dotenv";
import Redis from "ioredis";
dotenv.config();
const redis = new Redis({
host: "localhost",
port: 7777,
password: "xlfvpQ0ma2BkkkPX",
});
// redis.set("test", "test");
// console.log(await redis.get("test"));
// console.log(await redis.get("user-1-processing"));
const app = new Hono();
console.log("QStash Token:", process.env.PUBLIC_URL);
const qstash = new Client({
token: process.env.QSTASH_TOKEN as string,
});
const queue = qstash.queue({
queueName: "deployments",
});
// Endpoint que publica un mensaje en QStash
app.post("/enqueue", async (c) => {
const { userId, deploymentId } = await c.req.json();
const response = await qstash.publishJSON({
url: `${process.env.PUBLIC_URL}/process`, // Endpoint para procesar la tarea
body: { userId, deploymentId }, // Datos del despliegue
});
return c.json({ message: "Task enqueued", id: response.messageId });
});
// Endpoint que recibe el mensaje procesado
app.post("/process", async (c) => {
const { userId, deploymentId } = await c.req.json();
const isProcessing = await redis.get(`user-${userId}-processing`);
console.log(`isProcessing for user ${userId}:`, isProcessing);
if (isProcessing === "true") {
console.log(
`User ${userId} is already processing a deployment. Queuing the next one.`,
);
return c.json(
{
status: "User is already processing a deployment, waiting...",
},
{
status: 400,
},
);
}
redis.set(`user-${userId}-processing`, "true");
try {
await new Promise((resolve) => setTimeout(resolve, 5000));
} catch (error) {
} finally {
await redis.del(`user-${userId}-processing`);
}
return c.json({ status: "Processed", userId, deploymentId });
});
// Inicia el servidor en el puerto 3000
const port = 3000;
console.log(`Server is running on port http://localhost:${port}`);
serve({
fetch: app.fetch,
port,
});
// 18

View File

@@ -1,12 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"skipLibCheck": true,
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"traceResolution": true,
"diagnostics": true
}
}

View File

@@ -19,7 +19,6 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { api } from "@/utils/api";
import copy from "copy-to-clipboard";

View File

@@ -11,45 +11,46 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/** @type {import("next").NextConfig} */
const nextConfig = {
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
webpack: (config) => {
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, "templates/**/*.yml"),
to: ({ context, absoluteFilename }) => {
const relativePath = path.relative(
path.resolve(__dirname, "templates"),
absoluteFilename || context,
);
return path.join(__dirname, ".next", "templates", relativePath);
},
globOptions: {
ignore: ["**/node_modules/**"],
},
},
],
}),
);
return config;
},
reactStrictMode: true,
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
transpilePackages: ["@dokploy/builders"],
webpack: (config) => {
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, "templates/**/*.yml"),
to: ({ context, absoluteFilename }) => {
const relativePath = path.relative(
path.resolve(__dirname, "templates"),
absoluteFilename || context
);
return path.join(__dirname, ".next", "templates", relativePath);
},
globOptions: {
ignore: ["**/node_modules/**"],
},
},
],
})
);
return config;
},
/**
* If you are using `appDir` then you must comment the below `i18n` config out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
},
/**
* If you are using `appDir` then you must comment the below `i18n` config out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
export default nextConfig;

View File

@@ -34,6 +34,7 @@
"test": "vitest --config __test__/vitest.config.ts"
},
"dependencies": {
"@dokploy/builders": "workspace:*",
"rotating-file-stream": "3.2.3",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.1",

View File

@@ -18,6 +18,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.status(401).json({ message: "Unauthorized" });
return;
}
console.log(user);
// @ts-ignore
return createOpenApiNextHandler({
router: appRouter,

View File

@@ -6,14 +6,23 @@ import {
apiRemoveUser,
users,
} from "@/server/db/schema";
// import {
// } from "@dokploy/builders";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
// import {
// createInvitation,
// getUserByToken,
// removeUserByAuthId,
// } from "../services/admin";
import {
createInvitation,
findAdmin,
createInvitation,
getUserByToken,
removeUserByAuthId,
} from "../services/admin";
} from "@dokploy/builders";
import { adminProcedure, createTRPCRouter, publicProcedure } from "../trpc";
export const adminRouter = createTRPCRouter({

View File

@@ -18,7 +18,7 @@ import {
apiSaveGitlabProvider,
apiUpdateApplication,
applications,
} from "@/server/db/schema/application";
} from "@/server/db/schema";
import {
type DeploymentJob,
cleanQueuesByApplication,
@@ -55,7 +55,7 @@ import {
getApplicationStats,
updateApplication,
updateApplicationStatus,
} from "../services/application";
} from "@dokploy/builders";
import { removeDeployments } from "../services/deployment";
import { addNewService, checkServiceAccess } from "../services/user";

View File

@@ -8,7 +8,7 @@ import {
apiSaveEnvironmentVariablesMariaDB,
apiSaveExternalPortMariaDB,
apiUpdateMariaDB,
} from "@/server/db/schema/mariadb";
} from "@/server/db/schema";
import {
removeService,
startService,

View File

@@ -8,7 +8,7 @@ import {
apiSaveEnvironmentVariablesMongo,
apiSaveExternalPortMongo,
apiUpdateMongo,
} from "@/server/db/schema/mongo";
} from "@/server/db/schema";
import {
removeService,
startService,

View File

@@ -8,7 +8,7 @@ import {
apiSaveEnvironmentVariablesMySql,
apiSaveExternalPortMySql,
apiUpdateMySql,
} from "@/server/db/schema/mysql";
} from "@/server/db/schema";
import {
removeService,
startService,

View File

@@ -3,7 +3,7 @@ import {
apiCreatePort,
apiFindOnePort,
apiUpdatePort,
} from "@/server/db/schema/port";
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import {
createPort,

View File

@@ -8,7 +8,7 @@ import {
apiSaveEnvironmentVariablesPostgres,
apiSaveExternalPortPostgres,
apiUpdatePostgres,
} from "@/server/db/schema/postgres";
} from "@/server/db/schema";
import {
removeService,
startService,

View File

@@ -15,7 +15,7 @@ import {
apiRemoveProject,
apiUpdateProject,
projects,
} from "@/server/db/schema/project";
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { desc, eq, sql } from "drizzle-orm";
import type { AnyPgColumn } from "drizzle-orm/pg-core";

View File

@@ -8,7 +8,7 @@ import {
apiSaveEnvironmentVariablesRedis,
apiSaveExternalPortRedis,
apiUpdateRedis,
} from "@/server/db/schema/redis";
} from "@/server/db/schema";
import {
removeService,
startService,

View File

@@ -1,7 +1,7 @@
import { docker } from "@/server/constants";
import { db } from "@/server/db";
import { type apiCreateApplication, applications } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { generateAppName } from "@/server/db/schema";
import { getAdvancedStats } from "@/server/monitoring/utilts";
import {
buildApplication,

View File

@@ -2,7 +2,7 @@ import { join } from "node:path";
import { paths } from "@/server/constants";
import { db } from "@/server/db";
import { type apiCreateCompose, compose } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { generateAppName } from "@/server/db/schema";
import {
buildCompose,
getBuildComposeCommand,

View File

@@ -1,7 +1,7 @@
import { generateRandomPassword } from "@/server/auth/random-password";
import { db } from "@/server/db";
import { type apiCreateMariaDB, backups, mariadb } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { generateAppName } from "@/server/db/schema";
import { buildMariadb } from "@/server/utils/databases/mariadb";
import { pullImage } from "@/server/utils/docker/utils";
import { generatePassword } from "@/templates/utils";

View File

@@ -1,7 +1,7 @@
import { generateRandomPassword } from "@/server/auth/random-password";
import { db } from "@/server/db";
import { type apiCreateMongo, backups, mongo } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { generateAppName } from "@/server/db/schema";
import { buildMongo } from "@/server/utils/databases/mongo";
import { pullImage } from "@/server/utils/docker/utils";
import { generatePassword } from "@/templates/utils";

View File

@@ -1,7 +1,7 @@
import { generateRandomPassword } from "@/server/auth/random-password";
import { db } from "@/server/db";
import { type apiCreateMySql, backups, mysql } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { generateAppName } from "@/server/db/schema";
import { buildMysql } from "@/server/utils/databases/mysql";
import { pullImage } from "@/server/utils/docker/utils";
import { generatePassword } from "@/templates/utils";

View File

@@ -1,7 +1,7 @@
import { generateRandomPassword } from "@/server/auth/random-password";
import { db } from "@/server/db";
import { type apiCreatePostgres, backups, postgres } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { generateAppName } from "@/server/db/schema";
import { buildPostgres } from "@/server/utils/databases/postgres";
import { pullImage } from "@/server/utils/docker/utils";
import { generatePassword } from "@/templates/utils";

View File

@@ -1,7 +1,7 @@
import { generateRandomPassword } from "@/server/auth/random-password";
import { db } from "@/server/db";
import { type apiCreateRedis, redis } from "@/server/db/schema";
import { generateAppName } from "@/server/db/schema/utils";
import { generateAppName } from "@/server/db/schema";
import { buildRedis } from "@/server/utils/databases/redis";
import { pullImage } from "@/server/utils/docker/utils";
import { generatePassword } from "@/templates/utils";

View File

@@ -1,4 +1,5 @@
import { db } from "@/server/db";
import { type apiCreateServer, server } from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { desc, eq } from "drizzle-orm";

View File

@@ -1,7 +1,13 @@
import { type PostgresJsDatabase, drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
// import { sc } from "@dokploy/schema";
// import * as schema from "@dokploy/schema";
// schema
import * as schema from "./schema";
// type Schema = typeof schema;
// type Schema = typeof schema;
declare global {
// eslint-disable-next-line no-var -- only var works here
var db: PostgresJsDatabase<typeof schema> | undefined;

View File

@@ -1,30 +1 @@
export * from "./application";
export * from "./postgres";
export * from "./user";
export * from "./admin";
export * from "./auth";
export * from "./project";
export * from "./domain";
export * from "./mariadb";
export * from "./mongo";
export * from "./mysql";
export * from "./backups";
export * from "./destination";
export * from "./deployment";
export * from "./mount";
export * from "./certificate";
export * from "./session";
export * from "./redirects";
export * from "./security";
export * from "./port";
export * from "./redis";
export * from "./shared";
export * from "./compose";
export * from "./registry";
export * from "./notification";
export * from "./ssh-key";
export * from "./git-provider";
export * from "./bitbucket";
export * from "./github";
export * from "./gitlab";
export * from "./server";
export * from "@dokploy/builders";

View File

@@ -23,7 +23,6 @@ class LogRotationManager {
if (isActive) {
await this.activateStream();
}
console.log(`Log rotation initialized. Active: ${isActive}`);
}
private async getStateFromDB(): Promise<boolean> {

View File

@@ -1,3 +0,0 @@
import { findServerById } from "@/server/api/services/server";
import { Client } from "ssh2";
import { readSSHKey } from "../filesystem/ssh";

View File

@@ -0,0 +1,25 @@
import esbuild from "esbuild";
try {
esbuild
.build({
entryPoints: ["./src/**/*.ts"],
bundle: true,
platform: "node",
format: "cjs",
target: "node18",
outExtension: { ".js": ".js" },
minify: true,
outdir: "dist",
tsconfig: "tsconfig.server.json",
packages: "external",
alias: {
"@/server": "./src",
},
})
.catch(() => {
return process.exit(1);
});
} catch (error) {
console.log(error);
}

View File

@@ -0,0 +1,156 @@
{
"name": "@dokploy/builders",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"scripts": {
"dev": "tsup --config ./tsup.ts --watch",
"build": "tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json",
"tsc": "tsc --project tsconfig.server.json",
"build:types": "tsc --emitDeclarationOnly --experimenta-dts"
},
"dependencies": {
"tsc-alias": "1.8.10",
"esbuild": "0.20.2",
"esbuild-plugin-alias-path": "2.0.2",
"esbuild-plugin-alias": "0.2.1",
"tiny-glob": "^0.2.9",
"rotating-file-stream": "3.2.3",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.1",
"@codemirror/language": "^6.10.1",
"@codemirror/legacy-modes": "6.4.0",
"@codemirror/view": "6.29.0",
"@dokploy/trpc-openapi": "0.0.4",
"@faker-js/faker": "^8.4.1",
"@hookform/resolvers": "^3.3.4",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@octokit/auth-app": "^6.0.4",
"@octokit/webhooks": "^13.2.7",
"@radix-ui/react-accordion": "1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.7",
"@react-email/components": "^0.0.21",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.16.0",
"@trpc/client": "^10.43.6",
"@trpc/next": "^10.43.6",
"@trpc/react-query": "^10.43.6",
"@trpc/server": "^10.43.6",
"@uiw/codemirror-theme-github": "^4.22.1",
"@uiw/react-codemirror": "^4.22.1",
"@xterm/addon-attach": "0.10.0",
"@xterm/xterm": "^5.4.0",
"adm-zip": "^0.5.14",
"bcrypt": "5.1.1",
"bl": "6.0.11",
"boxen": "^7.1.1",
"bullmq": "5.4.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"copy-to-clipboard": "^3.3.3",
"copy-webpack-plugin": "^12.0.2",
"date-fns": "3.6.0",
"dockerode": "4.0.2",
"dockerode-compose": "^1.4.0",
"dockerstats": "2.4.2",
"dotenv": "16.4.5",
"drizzle-orm": "^0.30.8",
"drizzle-zod": "0.5.1",
"hi-base32": "^0.5.1",
"input-otp": "^1.2.4",
"js-yaml": "4.1.0",
"k6": "^0.0.0",
"lodash": "4.17.21",
"lucia": "^3.0.1",
"lucide-react": "^0.312.0",
"nanoid": "3",
"next": "^14.1.3",
"next-themes": "^0.2.1",
"node-os-utils": "1.3.7",
"node-pty": "1.0.0",
"node-schedule": "2.1.1",
"nodemailer": "6.9.14",
"octokit": "3.1.2",
"otpauth": "^9.2.3",
"postgres": "3.4.4",
"public-ip": "6.0.2",
"qrcode": "^1.5.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.49.3",
"recharts": "^2.12.7",
"slugify": "^1.6.6",
"sonner": "^1.4.0",
"superjson": "^2.2.1",
"swagger-ui-react": "^5.17.14",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"tar-fs": "3.0.5",
"undici": "^6.19.2",
"use-resize-observer": "9.1.0",
"ws": "8.16.0",
"xterm-addon-fit": "^0.8.0",
"zod": "^3.23.4",
"zod-form-data": "^2.0.2",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"ssh2": "1.15.0"
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@types/adm-zip": "^0.5.5",
"@types/bcrypt": "5.0.2",
"@types/dockerode": "3.3.23",
"@types/js-yaml": "4.0.9",
"@types/lodash": "4.17.4",
"@types/node": "^18.17.0",
"@types/node-os-utils": "1.3.4",
"@types/node-schedule": "2.1.6",
"@types/nodemailer": "^6.4.15",
"@types/qrcode": "^1.5.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/swagger-ui-react": "^4.18.3",
"@types/tar-fs": "2.0.4",
"@types/ws": "8.5.10",
"autoprefixer": "^10.4.14",
"drizzle-kit": "^0.21.1",
"esbuild": "0.20.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.7",
"localtunnel": "2.0.2",
"memfs": "^4.11.0",
"postcss": "^8.4.31",
"prettier": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1",
"tsconfig-paths": "4.2.0",
"tsx": "^4.7.0",
"typescript": "^5.4.2",
"vite-tsconfig-paths": "4.3.2",
"vitest": "^1.6.0",
"xterm-readline": "1.1.1",
"@types/ssh2": "1.15.1",
"tsup": "6.4.0"
}
}

View File

@@ -0,0 +1,113 @@
import { webcrypto } from "node:crypto";
import type { IncomingMessage, ServerResponse } from "node:http";
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
import type { Session, User } from "lucia/dist/core.js";
import { findAdminByAuthId } from "@/server/services/admin";
import { findUserByAuthId } from "@/server/services/user";
import { db } from "../db";
import { type DatabaseUser, auth, sessionTable } from "../db/schema";
globalThis.crypto = webcrypto as Crypto;
export const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, auth);
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: false,
},
},
sessionExpiresIn: new TimeSpan(1, "d"),
getUserAttributes: (attributes) => {
return {
email: attributes.email,
rol: attributes.rol,
secret: attributes.secret !== null,
adminId: attributes.adminId,
};
},
});
declare module "lucia" {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: Omit<DatabaseUser, "id"> & {
authId: string;
adminId: string;
};
}
}
export type ReturnValidateToken = Promise<{
user: (User & { authId: string; adminId: string }) | null;
session: Session | null;
}>;
export async function validateRequest(
req: IncomingMessage,
res: ServerResponse,
): ReturnValidateToken {
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await lucia.validateSession(sessionId);
if (result?.session?.fresh) {
res.appendHeader(
"Set-Cookie",
lucia.createSessionCookie(result.session.id).serialize(),
);
}
if (!result.session) {
res.appendHeader(
"Set-Cookie",
lucia.createBlankSessionCookie().serialize(),
);
}
if (result.user) {
if (result.user?.rol === "admin") {
const admin = await findAdminByAuthId(result.user.id);
result.user.adminId = admin.adminId;
} else if (result.user?.rol === "user") {
const userResult = await findUserByAuthId(result.user.id);
result.user.adminId = userResult.adminId;
}
}
return {
session: result.session,
...((result.user && {
user: {
authId: result.user.id,
email: result.user.email,
rol: result.user.rol,
id: result.user.id,
secret: result.user.secret,
adminId: result.user.adminId,
},
}) || {
user: null,
}),
};
}
export async function validateWebSocketRequest(
req: IncomingMessage,
): Promise<{ user: User; session: Session } | { user: null; session: null }> {
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await lucia.validateSession(sessionId);
return result;
}

View File

@@ -0,0 +1,20 @@
import bcrypt from "bcrypt";
export const generateRandomPassword = async () => {
const passwordLength = 16;
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let randomPassword = "";
for (let i = 0; i < passwordLength; i++) {
randomPassword += characters.charAt(
Math.floor(Math.random() * characters.length),
);
}
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(randomPassword, saltRounds);
return { randomPassword, hashedPassword };
};

View File

@@ -0,0 +1,49 @@
import type { IncomingMessage } from "node:http";
import { TimeSpan } from "lucia";
import { Lucia } from "lucia/dist/core.js";
import { type ReturnValidateToken, adapter } from "./auth";
export const luciaToken = new Lucia(adapter, {
sessionCookie: {
attributes: {
secure: false,
},
},
sessionExpiresIn: new TimeSpan(365, "d"),
getUserAttributes: (attributes) => {
return {
email: attributes.email,
rol: attributes.rol,
secret: attributes.secret !== null,
};
},
});
export const validateBearerToken = async (
req: IncomingMessage,
): ReturnValidateToken => {
const authorizationHeader = req.headers.authorization;
const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
if (!sessionId) {
return {
user: null,
session: null,
};
}
const result = await luciaToken.validateSession(sessionId);
return {
session: result.session,
...((result.user && {
user: {
adminId: result.user.adminId,
authId: result.user.id,
email: result.user.email,
rol: result.user.rol,
id: result.user.id,
secret: result.user.secret,
},
}) || {
user: null,
}),
};
};

View File

@@ -0,0 +1,39 @@
import path from "node:path";
import Docker from "dockerode";
export const IS_CLOUD = process.env.IS_CLOUD === "true";
export const docker = new Docker();
export const paths = (isServer = false) => {
if (isServer) {
const BASE_PATH = "/etc/dokploy";
return {
BASE_PATH,
MAIN_TRAEFIK_PATH: `${BASE_PATH}/traefik`,
DYNAMIC_TRAEFIK_PATH: `${BASE_PATH}/traefik/dynamic`,
LOGS_PATH: `${BASE_PATH}/logs`,
APPLICATIONS_PATH: `${BASE_PATH}/applications`,
COMPOSE_PATH: `${BASE_PATH}/compose`,
SSH_PATH: `${BASE_PATH}/ssh`,
CERTIFICATES_PATH: `${BASE_PATH}/certificates`,
MONITORING_PATH: `${BASE_PATH}/monitoring`,
REGISTRY_PATH: `${BASE_PATH}/registry`,
};
}
const BASE_PATH =
process.env.NODE_ENV === "production"
? "/etc/dokploy"
: path.join(process.cwd(), ".docker");
return {
BASE_PATH,
MAIN_TRAEFIK_PATH: `${BASE_PATH}/traefik`,
DYNAMIC_TRAEFIK_PATH: `${BASE_PATH}/traefik/dynamic`,
LOGS_PATH: `${BASE_PATH}/logs`,
APPLICATIONS_PATH: `${BASE_PATH}/applications`,
COMPOSE_PATH: `${BASE_PATH}/compose`,
SSH_PATH: `${BASE_PATH}/ssh`,
CERTIFICATES_PATH: `${BASE_PATH}/certificates`,
MONITORING_PATH: `${BASE_PATH}/monitoring`,
REGISTRY_PATH: `${BASE_PATH}/registry`,
};
};

View File

@@ -0,0 +1,14 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./server/db/schema/index.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL || "",
},
out: "drizzle",
migrations: {
table: "migrations",
schema: "public",
},
});

View File

@@ -0,0 +1,28 @@
import { type PostgresJsDatabase, drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
// import { sc } from "@dokploy/schema";
import * as schema from "./schema";
// schema
// import * as schema from "@dokploy/schema";
// type Schema = typeof schema;
// type Schema = typeof schema;
declare global {
// eslint-disable-next-line no-var -- only var works here
var db: PostgresJsDatabase<typeof schema> | undefined;
}
export let db: PostgresJsDatabase<typeof schema>;
if (process.env.NODE_ENV === "production") {
db = drizzle(postgres(process.env.DATABASE_URL || ""), {
schema,
});
} else {
if (!global.db)
global.db = drizzle(postgres(process.env.DATABASE_URL || ""), {
schema,
});
db = global.db;
}

View File

@@ -0,0 +1,21 @@
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
const connectionString = process.env.DATABASE_URL || "";
const sql = postgres(connectionString, { max: 1 });
const db = drizzle(sql);
export const migration = async () =>
await migrate(db, { migrationsFolder: "drizzle" })
.then(() => {
console.log("Migration complete");
sql.end();
})
.catch((error) => {
console.log("Migration failed", error);
})
.finally(() => {
sql.end();
});

View File

@@ -0,0 +1,23 @@
import { sql } from "drizzle-orm";
// Credits to Louistiti from Drizzle Discord: https://discord.com/channels/1043890932593987624/1130802621750448160/1143083373535973406
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
const connectionString = process.env.DATABASE_URL || "";
const pg = postgres(connectionString, { max: 1 });
const db = drizzle(pg);
const clearDb = async (): Promise<void> => {
try {
const tablesQuery = sql<string>`DROP SCHEMA public CASCADE; CREATE SCHEMA public; DROP schema drizzle CASCADE;`;
const tables = await db.execute(tablesQuery);
console.log(tables);
await pg.end();
} catch (error) {
console.error("Error to clean database", error);
} finally {
}
};
clearDb();

View File

@@ -10,7 +10,6 @@ import {
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { bitbucket, github, gitlab, server } from ".";
import { deployments } from "./deployment";
import { domains } from "./domain";
import { mounts } from "./mount";
@@ -22,6 +21,10 @@ import { security } from "./security";
import { applicationStatus } from "./shared";
import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils";
import { github } from "./github";
import { gitlab } from "./gitlab";
import { bitbucket } from "./bitbucket";
import { server } from "./server";
export const sourceType = pgEnum("sourceType", [
"docker",
@@ -41,7 +44,7 @@ export const buildType = pgEnum("buildType", [
]);
// TODO: refactor this types
interface HealthCheckSwarm {
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
Timeout?: number | undefined;
@@ -49,14 +52,14 @@ interface HealthCheckSwarm {
Retries?: number | undefined;
}
interface RestartPolicySwarm {
export interface RestartPolicySwarm {
Condition?: string | undefined;
Delay?: number | undefined;
MaxAttempts?: number | undefined;
Window?: number | undefined;
}
interface PlacementSwarm {
export interface PlacementSwarm {
Constraints?: string[] | undefined;
Preferences?: Array<{ Spread: { SpreadDescriptor: string } }> | undefined;
MaxReplicas?: number | undefined;
@@ -68,7 +71,7 @@ interface PlacementSwarm {
| undefined;
}
interface UpdateConfigSwarm {
export interface UpdateConfigSwarm {
Parallelism: number;
Delay?: number | undefined;
FailureAction?: string | undefined;
@@ -77,7 +80,7 @@ interface UpdateConfigSwarm {
Order: string;
}
interface ServiceModeSwarm {
export interface ServiceModeSwarm {
Replicated?: { Replicas?: number | undefined } | undefined;
Global?: {} | undefined;
ReplicatedJob?:
@@ -89,13 +92,13 @@ interface ServiceModeSwarm {
GlobalJob?: {} | undefined;
}
interface NetworkSwarm {
export interface NetworkSwarm {
Target?: string | undefined;
Aliases?: string[] | undefined;
DriverOpts?: { [key: string]: string } | undefined;
}
interface LabelsSwarm {
export interface LabelsSwarm {
[name: string]: string;
}

View File

@@ -1,16 +1,19 @@
import { sshKeys } from "@/server/db/schema/ssh-key";
import { sshKeys } from "./ssh-key";
import { relations } from "drizzle-orm";
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { bitbucket, github, gitlab, server } from ".";
import { deployments } from "./deployment";
import { domains } from "./domain";
import { mounts } from "./mount";
import { projects } from "./project";
import { applicationStatus } from "./shared";
import { generateAppName } from "./utils";
import { github } from "./github";
import { gitlab } from "./gitlab";
import { bitbucket } from "./bitbucket";
import { server } from "./server";
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
"git",

View File

@@ -1,4 +1,4 @@
import { domain } from "@/server/db/validations/domain";
import { domain } from "../validations/domain";
import { relations } from "drizzle-orm";
import {
boolean,

View File

@@ -0,0 +1,31 @@
export * from "./application";
export * from "./postgres";
export * from "./user";
export * from "./admin";
export * from "./auth";
export * from "./project";
export * from "./domain";
export * from "./mariadb";
export * from "./mongo";
export * from "./mysql";
export * from "./backups";
export * from "./destination";
export * from "./deployment";
export * from "./mount";
export * from "./certificate";
export * from "./session";
export * from "./redirects";
export * from "./security";
export * from "./port";
export * from "./redis";
export * from "./shared";
export * from "./compose";
export * from "./registry";
export * from "./notification";
export * from "./ssh-key";
export * from "./git-provider";
export * from "./bitbucket";
export * from "./github";
export * from "./gitlab";
export * from "./server";
export * from "./utils";

View File

@@ -1,4 +1,3 @@
import { generatePassword } from "@/templates/utils";
import { relations } from "drizzle-orm";
import { integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";

View File

@@ -3,19 +3,18 @@ import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import {
applications,
compose,
mariadb,
mongo,
mysql,
postgres,
redis,
} from ".";
import { admins } from "./admin";
import { deployments } from "./deployment";
import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils";
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 server = pgTable("server", {
serverId: text("serverId")

View File

@@ -1,6 +1,6 @@
import { applications } from "@/server/db/schema/application";
import { compose } from "@/server/db/schema/compose";
import { sshKeyCreate, sshKeyType } from "@/server/db/validations";
import { applications } from "./application";
import { compose } from "./compose";
import { sshKeyCreate, sshKeyType } from "../validations";
import { relations } from "drizzle-orm";
import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";

View File

@@ -0,0 +1,35 @@
import bc from "bcrypt";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { users } from "./schema";
const connectionString = process.env.DATABASE_URL || "";
const pg = postgres(connectionString, { max: 1 });
const db = drizzle(pg);
function password(txt: string) {
return bc.hashSync(txt, 10);
}
async function seed() {
console.log("> Seed:", process.env.DATABASE_PATH, "\n");
// const authenticationR = await db
// .insert(users)
// .values([
// {
// email: "user1@hotmail.com",
// password: password("12345671"),
// },
// ])
// .onConflictDoNothing()
// .returning();
// console.log("\nSemillas Update:", authenticationR.length);
}
seed().catch((e) => {
console.error(e);
process.exit(1);
});

View File

@@ -0,0 +1,46 @@
import { z } from "zod";
export const domain = z
.object({
host: z.string().min(1, { message: "Add a hostname" }),
path: z.string().min(1).optional(),
port: z
.number()
.min(1, { message: "Port must be at least 1" })
.max(65535, { message: "Port must be 65535 or below" })
.optional(),
https: z.boolean().optional(),
certificateType: z.enum(["letsencrypt", "none"]).optional(),
})
.superRefine((input, ctx) => {
if (input.https && !input.certificateType) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["certificateType"],
message: "Required",
});
}
});
export const domainCompose = z
.object({
host: z.string().min(1, { message: "Host is required" }),
path: z.string().min(1).optional(),
port: z
.number()
.min(1, { message: "Port must be at least 1" })
.max(65535, { message: "Port must be 65535 or below" })
.optional(),
https: z.boolean().optional(),
certificateType: z.enum(["letsencrypt", "none"]).optional(),
serviceName: z.string().min(1, { message: "Service name is required" }),
})
.superRefine((input, ctx) => {
if (input.https && !input.certificateType) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["certificateType"],
message: "Required",
});
}
});

View File

@@ -0,0 +1,37 @@
import { z } from "zod";
export const sshKeyCreate = z.object({
name: z.string().min(1),
description: z.string().optional(),
publicKey: z.string().refine(
(key) => {
const rsaPubPattern = /^ssh-rsa\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/;
const ed25519PubPattern = /^ssh-ed25519\s+([A-Za-z0-9+/=]+)\s*(.*)?\s*$/;
return rsaPubPattern.test(key) || ed25519PubPattern.test(key);
},
{
message: "Invalid public key format",
},
),
privateKey: z.string().refine(
(key) => {
const rsaPrivPattern =
/^-----BEGIN RSA PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END RSA PRIVATE KEY-----\s*$/;
const ed25519PrivPattern =
/^-----BEGIN OPENSSH PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END OPENSSH PRIVATE KEY-----\s*$/;
return rsaPrivPattern.test(key) || ed25519PrivPattern.test(key);
},
{
message: "Invalid private key format",
},
),
});
export const sshKeyUpdate = sshKeyCreate.pick({
name: true,
description: true,
});
export const sshKeyType = z.object({
type: z.enum(["rsa", "ed25519"]).optional(),
});

View File

@@ -0,0 +1,2 @@
/node_modules
/dist

View File

@@ -0,0 +1,113 @@
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;
applicationName: string;
applicationType: string;
errorMessage: string;
buildLink: string;
date: string;
};
export const BuildFailedEmail = ({
projectName = "dokploy",
applicationName = "frontend",
applicationType = "application",
errorMessage = "Error array.length is not a function",
buildLink = "https://dokploy.com/projects/dokploy-test/applications/dokploy-test",
date = "2023-05-01T00:00:00.000Z",
}: TemplateProps) => {
const previewText = `Build failed for ${applicationName}`;
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#007291",
},
},
},
}}
>
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={
"https://raw.githubusercontent.com/Dokploy/dokploy/canary/logo.png"
}
width="100"
height="50"
alt="Dokploy"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Build failed for <strong>{applicationName}</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
Your build for <strong>{applicationName}</strong> failed. Please
check the error message below.
</Text>
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Details: </Text>
<Text className="!leading-3">
Project Name: <strong>{projectName}</strong>
</Text>
<Text className="!leading-3">
Application Name: <strong>{applicationName}</strong>
</Text>
<Text className="!leading-3">
Application Type: <strong>{applicationType}</strong>
</Text>
<Text className="!leading-3">
Date: <strong>{date}</strong>
</Text>
</Section>
<Section className="flex text-black text-[14px] mt-4 leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Reason: </Text>
<Text className="text-[12px] leading-[24px]">{errorMessage}</Text>
</Section>
<Section className="text-center mt-[32px] mb-[32px]">
<Button
href={buildLink}
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
>
View build
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
or copy and paste this URL into your browser:{" "}
<Link href={buildLink} className="text-blue-600 no-underline">
{buildLink}
</Link>
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export default BuildFailedEmail;

View File

@@ -0,0 +1,106 @@
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;
applicationName: string;
applicationType: string;
buildLink: string;
date: string;
};
export const BuildSuccessEmail = ({
projectName = "dokploy",
applicationName = "frontend",
applicationType = "application",
buildLink = "https://dokploy.com/projects/dokploy-test/applications/dokploy-test",
date = "2023-05-01T00:00:00.000Z",
}: TemplateProps) => {
const previewText = `Build success for ${applicationName}`;
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#007291",
},
},
},
}}
>
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={
"https://raw.githubusercontent.com/Dokploy/dokploy/canary/logo.png"
}
width="100"
height="50"
alt="Dokploy"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Build success for <strong>{applicationName}</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
Your build for <strong>{applicationName}</strong> was successful
</Text>
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Details: </Text>
<Text className="!leading-3">
Project Name: <strong>{projectName}</strong>
</Text>
<Text className="!leading-3">
Application Name: <strong>{applicationName}</strong>
</Text>
<Text className="!leading-3">
Application Type: <strong>{applicationType}</strong>
</Text>
<Text className="!leading-3">
Date: <strong>{date}</strong>
</Text>
</Section>
<Section className="text-center mt-[32px] mb-[32px]">
<Button
href={buildLink}
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
>
View build
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
or copy and paste this URL into your browser:{" "}
<Link href={buildLink} className="text-blue-600 no-underline">
{buildLink}
</Link>
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export default BuildSuccessEmail;

View File

@@ -0,0 +1,105 @@
import {
Body,
Container,
Head,
Heading,
Html,
Img,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
projectName: string;
applicationName: string;
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
type: "error" | "success";
errorMessage?: string;
date: string;
};
export const DatabaseBackupEmail = ({
projectName = "dokploy",
applicationName = "frontend",
databaseType = "postgres",
type = "success",
errorMessage,
date = "2023-05-01T00:00:00.000Z",
}: TemplateProps) => {
const previewText = `Database backup for ${applicationName} was ${type === "success" ? "successful ✅" : "failed ❌"}`;
return (
<Html>
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#007291",
},
},
},
}}
>
<Head />
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={
"https://raw.githubusercontent.com/Dokploy/dokploy/canary/logo.png"
}
width="100"
height="50"
alt="Dokploy"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Database backup for <strong>{applicationName}</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
Your database backup for <strong>{applicationName}</strong> was{" "}
{type === "success"
? "successful ✅"
: "failed Please check the error message below. ❌"}
.
</Text>
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Details: </Text>
<Text className="!leading-3">
Project Name: <strong>{projectName}</strong>
</Text>
<Text className="!leading-3">
Application Name: <strong>{applicationName}</strong>
</Text>
<Text className="!leading-3">
Database Type: <strong>{databaseType}</strong>
</Text>
<Text className="!leading-3">
Date: <strong>{date}</strong>
</Text>
</Section>
{type === "error" && errorMessage ? (
<Section className="flex text-black text-[14px] mt-4 leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Reason: </Text>
<Text className="text-[12px] leading-[24px]">
{errorMessage || "Error message not provided"}
</Text>
</Section>
) : null}
</Container>
</Body>
</Tailwind>
</Html>
);
};
export default DatabaseBackupEmail;

View File

@@ -0,0 +1,81 @@
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Img,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
message: string;
date: string;
};
export const DockerCleanupEmail = ({
message = "Docker cleanup for dokploy",
date = "2023-05-01T00:00:00.000Z",
}: TemplateProps) => {
const previewText = "Docker cleanup for dokploy";
return (
<Html>
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#007291",
},
},
},
}}
>
<Head />
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={
"https://raw.githubusercontent.com/Dokploy/dokploy/canary/logo.png"
}
width="100"
height="50"
alt="Dokploy"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Docker cleanup for <strong>dokploy</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
The docker cleanup for <strong>dokploy</strong> was successful
</Text>
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Details: </Text>
<Text className="!leading-3">
Message: <strong>{message}</strong>
</Text>
<Text className="!leading-3">
Date: <strong>{date}</strong>
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export default DockerCleanupEmail;

View File

@@ -0,0 +1,75 @@
import {
Body,
Container,
Head,
Heading,
Html,
Img,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
export type TemplateProps = {
date: string;
};
export const DokployRestartEmail = ({
date = "2023-05-01T00:00:00.000Z",
}: TemplateProps) => {
const previewText = "Your dokploy server was restarted";
return (
<Html>
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#007291",
},
},
},
}}
>
<Head />
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={
"https://raw.githubusercontent.com/Dokploy/dokploy/canary/logo.png"
}
width="100"
height="50"
alt="Dokploy"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Dokploy Server Restart
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
Your dokploy server was restarted
</Text>
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
<Text className="!leading-3 font-bold">Details: </Text>
<Text className="!leading-3">
Date: <strong>{date}</strong>
</Text>
</Section>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export default DokployRestartEmail;

View File

@@ -0,0 +1,98 @@
import {
Body,
Button,
Container,
Head,
Heading,
Hr,
Html,
Img,
Link,
Preview,
Section,
Tailwind,
Text,
} from "@react-email/components";
export type TemplateProps = {
email: string;
name: string;
};
interface VercelInviteUserEmailProps {
inviteLink: string;
toEmail: string;
}
export const InvitationEmail = ({
inviteLink,
toEmail,
}: VercelInviteUserEmailProps) => {
const previewText = "Join to Dokploy";
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#007291",
},
},
},
}}
>
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={
"https://raw.githubusercontent.com/Dokploy/dokploy/canary/logo.png"
}
width="100"
height="50"
alt="Dokploy"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Join to <strong>Dokploy</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
You have been invited to join <strong>Dokploy</strong>, a platform
that helps for deploying your apps to the cloud.
</Text>
<Section className="text-center mt-[32px] mb-[32px]">
<Button
href={inviteLink}
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
>
Join the team 🚀
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
or copy and paste this URL into your browser:{" "}
<Link href={inviteLink} className="text-blue-600 no-underline">
https://dokploy.com
</Link>
</Text>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text className="text-[#666666] text-[12px] leading-[24px]">
This invitation was intended for {toEmail}. This invite was sent
from <strong className="text-black">dokploy.com</strong>. If you
were not expecting this invitation, you can ignore this email. If
you are concerned about your account's safety, please reply to
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export default InvitationEmail;

View File

@@ -0,0 +1,150 @@
import {
Body,
Container,
Head,
Heading,
Html,
Img,
Link,
Preview,
Text,
} from "@react-email/components";
import * as React from "react";
interface NotionMagicLinkEmailProps {
loginCode?: string;
}
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "";
export const NotionMagicLinkEmail = ({
loginCode,
}: NotionMagicLinkEmailProps) => (
<Html>
<Head />
<Preview>Log in with this magic link</Preview>
<Body style={main}>
<Container style={container}>
<Heading style={h1}>Login</Heading>
<Link
href="https://notion.so"
target="_blank"
style={{
...link,
display: "block",
marginBottom: "16px",
}}
>
Click here to log in with this magic link
</Link>
<Text style={{ ...text, marginBottom: "14px" }}>
Or, copy and paste this temporary login code:
</Text>
<code style={code}>{loginCode}</code>
<Text
style={{
...text,
color: "#ababab",
marginTop: "14px",
marginBottom: "16px",
}}
>
If you didn&apos;t try to login, you can safely ignore this email.
</Text>
<Text
style={{
...text,
color: "#ababab",
marginTop: "12px",
marginBottom: "38px",
}}
>
Hint: You can set a permanent password in Settings & members My
account.
</Text>
<Img
src={`${baseUrl}/static/notion-logo.png`}
width="32"
height="32"
alt="Notion's Logo"
/>
<Text style={footer}>
<Link
href="https://notion.so"
target="_blank"
style={{ ...link, color: "#898989" }}
>
Notion.so
</Link>
, the all-in-one-workspace
<br />
for your notes, tasks, wikis, and databases.
</Text>
</Container>
</Body>
</Html>
);
NotionMagicLinkEmail.PreviewProps = {
loginCode: "sparo-ndigo-amurt-secan",
} as NotionMagicLinkEmailProps;
export default NotionMagicLinkEmail;
const main = {
backgroundColor: "#ffffff",
};
const container = {
paddingLeft: "12px",
paddingRight: "12px",
margin: "0 auto",
};
const h1 = {
color: "#333",
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
fontSize: "24px",
fontWeight: "bold",
margin: "40px 0",
padding: "0",
};
const link = {
color: "#2754C5",
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
fontSize: "14px",
textDecoration: "underline",
};
const text = {
color: "#333",
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
fontSize: "14px",
margin: "24px 0",
};
const footer = {
color: "#898989",
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
fontSize: "12px",
lineHeight: "22px",
marginTop: "12px",
marginBottom: "24px",
};
const code = {
display: "inline-block",
padding: "16px 4.5%",
width: "90.5%",
backgroundColor: "#f4f4f4",
borderRadius: "5px",
border: "1px solid #eee",
color: "#333",
};

View File

@@ -0,0 +1,158 @@
import {
Body,
Container,
Head,
Heading,
Html,
Img,
Link,
Section,
Text,
} from "@react-email/components";
import * as React from "react";
interface PlaidVerifyIdentityEmailProps {
validationCode?: string;
}
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "";
export const PlaidVerifyIdentityEmail = ({
validationCode,
}: PlaidVerifyIdentityEmailProps) => (
<Html>
<Head />
<Body style={main}>
<Container style={container}>
<Img
src={`${baseUrl}/static/plaid-logo.png`}
width="212"
height="88"
alt="Plaid"
style={logo}
/>
<Text style={tertiary}>Verify Your Identity</Text>
<Heading style={secondary}>
Enter the following code to finish linking Venmo.
</Heading>
<Section style={codeContainer}>
<Text style={code}>{validationCode}</Text>
</Section>
<Text style={paragraph}>Not expecting this email?</Text>
<Text style={paragraph}>
Contact{" "}
<Link href="mailto:login@plaid.com" style={link}>
login@plaid.com
</Link>{" "}
if you did not request this code.
</Text>
</Container>
<Text style={footer}>Securely powered by Plaid.</Text>
</Body>
</Html>
);
PlaidVerifyIdentityEmail.PreviewProps = {
validationCode: "144833",
} as PlaidVerifyIdentityEmailProps;
export default PlaidVerifyIdentityEmail;
const main = {
backgroundColor: "#ffffff",
fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
};
const container = {
backgroundColor: "#ffffff",
border: "1px solid #eee",
borderRadius: "5px",
boxShadow: "0 5px 10px rgba(20,50,70,.2)",
marginTop: "20px",
maxWidth: "360px",
margin: "0 auto",
padding: "68px 0 130px",
};
const logo = {
margin: "0 auto",
};
const tertiary = {
color: "#0a85ea",
fontSize: "11px",
fontWeight: 700,
fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
height: "16px",
letterSpacing: "0",
lineHeight: "16px",
margin: "16px 8px 8px 8px",
textTransform: "uppercase" as const,
textAlign: "center" as const,
};
const secondary = {
color: "#000",
display: "inline-block",
fontFamily: "HelveticaNeue-Medium,Helvetica,Arial,sans-serif",
fontSize: "20px",
fontWeight: 500,
lineHeight: "24px",
marginBottom: "0",
marginTop: "0",
textAlign: "center" as const,
};
const codeContainer = {
background: "rgba(0,0,0,.05)",
borderRadius: "4px",
margin: "16px auto 14px",
verticalAlign: "middle",
width: "280px",
};
const code = {
color: "#000",
display: "inline-block",
fontFamily: "HelveticaNeue-Bold",
fontSize: "32px",
fontWeight: 700,
letterSpacing: "6px",
lineHeight: "40px",
paddingBottom: "8px",
paddingTop: "8px",
margin: "0 auto",
width: "100%",
textAlign: "center" as const,
};
const paragraph = {
color: "#444",
fontSize: "15px",
fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
letterSpacing: "0",
lineHeight: "23px",
padding: "0 40px",
margin: "0",
textAlign: "center" as const,
};
const link = {
color: "#444",
textDecoration: "underline",
};
const footer = {
color: "#000",
fontSize: "12px",
fontWeight: 800,
letterSpacing: "0",
lineHeight: "23px",
margin: "0",
marginTop: "20px",
fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
textAlign: "center" as const,
textTransform: "uppercase" as const,
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -0,0 +1,152 @@
import {
Body,
Button,
Container,
Head,
Hr,
Html,
Img,
Link,
Preview,
Section,
Text,
} from "@react-email/components";
import * as React from "react";
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "";
export const StripeWelcomeEmail = () => (
<Html>
<Head />
<Preview>You're now ready to make live transactions with Stripe!</Preview>
<Body style={main}>
<Container style={container}>
<Section style={box}>
<Img
src={`${baseUrl}/static/stripe-logo.png`}
width="49"
height="21"
alt="Stripe"
/>
<Hr style={hr} />
<Text style={paragraph}>
Thanks for submitting your account information. You're now ready to
make live transactions with Stripe!
</Text>
<Text style={paragraph}>
You can view your payments and a variety of other information about
your account right from your dashboard.
</Text>
<Button style={button} href="https://dashboard.stripe.com/login">
View your Stripe Dashboard
</Button>
<Hr style={hr} />
<Text style={paragraph}>
If you haven't finished your integration, you might find our{" "}
<Link style={anchor} href="https://stripe.com/docs">
docs
</Link>{" "}
handy.
</Text>
<Text style={paragraph}>
Once you're ready to start accepting payments, you'll just need to
use your live{" "}
<Link
style={anchor}
href="https://dashboard.stripe.com/login?redirect=%2Fapikeys"
>
API keys
</Link>{" "}
instead of your test API keys. Your account can simultaneously be
used for both test and live requests, so you can continue testing
while accepting live payments. Check out our{" "}
<Link style={anchor} href="https://stripe.com/docs/dashboard">
tutorial about account basics
</Link>
.
</Text>
<Text style={paragraph}>
Finally, we've put together a{" "}
<Link
style={anchor}
href="https://stripe.com/docs/checklist/website"
>
quick checklist
</Link>{" "}
to ensure your website conforms to card network standards.
</Text>
<Text style={paragraph}>
We'll be here to help you with any step along the way. You can find
answers to most questions and get in touch with us on our{" "}
<Link style={anchor} href="https://support.stripe.com/">
support site
</Link>
.
</Text>
<Text style={paragraph}>— The Stripe team</Text>
<Hr style={hr} />
<Text style={footer}>
Stripe, 354 Oyster Point Blvd, South San Francisco, CA 94080
</Text>
</Section>
</Container>
</Body>
</Html>
);
export default StripeWelcomeEmail;
const main = {
backgroundColor: "#f6f9fc",
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
};
const container = {
backgroundColor: "#ffffff",
margin: "0 auto",
padding: "20px 0 48px",
marginBottom: "64px",
};
const box = {
padding: "0 48px",
};
const hr = {
borderColor: "#e6ebf1",
margin: "20px 0",
};
const paragraph = {
color: "#525f7f",
fontSize: "16px",
lineHeight: "24px",
textAlign: "left" as const,
};
const anchor = {
color: "#556cd6",
};
const button = {
backgroundColor: "#656ee8",
borderRadius: "5px",
color: "#fff",
fontSize: "16px",
fontWeight: "bold",
textDecoration: "none",
textAlign: "center" as const,
display: "block",
width: "100%",
padding: "10px",
};
const footer = {
color: "#8898aa",
fontSize: "12px",
lineHeight: "16px",
};

View File

@@ -0,0 +1,154 @@
import {
Body,
Button,
Column,
Container,
Head,
Heading,
Hr,
Html,
Img,
Link,
Preview,
Row,
Section,
Tailwind,
Text,
} from "@react-email/components";
import * as React from "react";
interface VercelInviteUserEmailProps {
username?: string;
userImage?: string;
invitedByUsername?: string;
invitedByEmail?: string;
teamName?: string;
teamImage?: string;
inviteLink?: string;
inviteFromIp?: string;
inviteFromLocation?: string;
}
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: "";
export const VercelInviteUserEmail = ({
username,
userImage,
invitedByUsername,
invitedByEmail,
teamName,
teamImage,
inviteLink,
inviteFromIp,
inviteFromLocation,
}: VercelInviteUserEmailProps) => {
const previewText = `Join ${invitedByUsername} on Vercel`;
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind>
<Body className="bg-white my-auto mx-auto font-sans px-2">
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]">
<Section className="mt-[32px]">
<Img
src={`${baseUrl}/static/vercel-logo.png`}
width="40"
height="37"
alt="Vercel"
className="my-0 mx-auto"
/>
</Section>
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Join <strong>{teamName}</strong> on <strong>Vercel</strong>
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello {username},
</Text>
<Text className="text-black text-[14px] leading-[24px]">
<strong>{invitedByUsername}</strong> (
<Link
href={`mailto:${invitedByEmail}`}
className="text-blue-600 no-underline"
>
{invitedByEmail}
</Link>
) has invited you to the <strong>{teamName}</strong> team on{" "}
<strong>Vercel</strong>.
</Text>
<Section>
<Row>
<Column align="right">
<Img
className="rounded-full"
src={userImage}
width="64"
height="64"
/>
</Column>
<Column align="center">
<Img
src={`${baseUrl}/static/vercel-arrow.png`}
width="12"
height="9"
alt="invited you to"
/>
</Column>
<Column align="left">
<Img
className="rounded-full"
src={teamImage}
width="64"
height="64"
/>
</Column>
</Row>
</Section>
<Section className="text-center mt-[32px] mb-[32px]">
<Button
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
href={inviteLink}
>
Join the team
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
or copy and paste this URL into your browser:{" "}
<Link href={inviteLink} className="text-blue-600 no-underline">
{inviteLink}
</Link>
</Text>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text className="text-[#666666] text-[12px] leading-[24px]">
This invitation was intended for{" "}
<span className="text-black">{username}</span>. This invite was
sent from <span className="text-black">{inviteFromIp}</span>{" "}
located in{" "}
<span className="text-black">{inviteFromLocation}</span>. If you
were not expecting this invitation, you can ignore this email. If
you are concerned about your account's safety, please reply to
this email to get in touch with us.
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};
VercelInviteUserEmail.PreviewProps = {
username: "alanturing",
userImage: `${baseUrl}/static/vercel-user.png`,
invitedByUsername: "Alan",
invitedByEmail: "alan.turing@example.com",
teamName: "Enigma",
teamImage: `${baseUrl}/static/vercel-team.png`,
inviteLink: "https://vercel.com/teams/invite/foo",
inviteFromIp: "204.13.186.218",
inviteFromLocation: "São Paulo, Brazil",
} as VercelInviteUserEmailProps;
export default VercelInviteUserEmail;

View File

@@ -0,0 +1,20 @@
{
"name": "emails",
"version": "0.0.19",
"private": true,
"type": "module",
"scripts": {
"build": "email build",
"dev": "email dev",
"export": "email export"
},
"dependencies": {
"@react-email/components": "0.0.21",
"react-email": "2.1.5",
"react": "^18.2.0"
},
"devDependencies": {
"@types/react": "18.2.33",
"@types/react-dom": "18.2.14"
}
}

4209
packages/builders/src/emails/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
# React Email Starter
A live preview right in your browser so you don't need to keep sending real emails during development.
## Getting Started
First, install the dependencies:
```sh
npm install
# or
yarn
```
Then, run the development server:
```sh
npm run dev
# or
yarn dev
```
Open [localhost:3000](http://localhost:3000) with your browser to see the result.
## License
MIT License

View File

@@ -0,0 +1,137 @@
export * from "./auth/auth";
// export * from "./db";
export * from "./services/admin";
export * from "./services/user";
export * from "./services/project";
export * from "./services/domain";
export * from "./services/mariadb";
export * from "./services/mongo";
export * from "./services/mysql";
export * from "./services/backup";
export * from "./services/destination";
export * from "./services/deployment";
export * from "./services/mount";
export * from "./services/certificate";
export * from "./services/redirect";
export * from "./services/security";
export * from "./services/port";
export * from "./services/redis";
export * from "./services/compose";
export * from "./services/registry";
export * from "./services/notification";
export * from "./services/ssh-key";
export * from "./services/git-provider";
export * from "./services/bitbucket";
export * from "./services/github";
export * from "./services/gitlab";
export * from "./services/server";
export * from "./services/application";
export * from "./db/schema/application";
export * from "./db/schema/postgres";
export * from "./db/schema/user";
export * from "./db/schema/admin";
export * from "./db/schema/auth";
export * from "./db/schema/project";
export * from "./db/schema/domain";
export * from "./db/schema/mariadb";
export * from "./db/schema/mongo";
export * from "./db/schema/mysql";
export * from "./db/schema/backups";
export * from "./db/schema/destination";
export * from "./db/schema/deployment";
export * from "./db/schema/mount";
export * from "./db/schema/certificate";
export * from "./db/schema/session";
export * from "./db/schema/redirects";
export * from "./db/schema/security";
export * from "./db/schema/port";
export * from "./db/schema/redis";
export * from "./db/schema/shared";
export * from "./db/schema/compose";
export * from "./db/schema/registry";
export * from "./db/schema/notification";
export * from "./db/schema/ssh-key";
export * from "./db/schema/git-provider";
export * from "./db/schema/bitbucket";
export * from "./db/schema/github";
export * from "./db/schema/gitlab";
export * from "./db/schema/server";
export * from "./db/schema/utils";
export * from "./setup/config-paths";
export * from "./setup/postgres-setup";
export * from "./setup/redis-setup";
export * from "./setup/registry-setup";
export * from "./setup/server-setup";
export * from "./setup/setup";
export * from "./setup/traefik-setup";
export * from "./utils/backups/index";
export * from "./utils/backups/mariadb";
export * from "./utils/backups/mongo";
export * from "./utils/backups/mysql";
export * from "./utils/backups/postgres";
export * from "./utils/backups/utils";
export * from "./utils/notifications/build-error";
export * from "./utils/notifications/build-success";
export * from "./utils/notifications/database-backup";
export * from "./utils/notifications/dokploy-restart";
export * from "./utils/notifications/utils";
export * from "./utils/notifications/docker-cleanup";
export * from "./utils/builders/index";
export * from "./utils/builders/compose";
export * from "./utils/builders/docker-file";
export * from "./utils/builders/drop";
export * from "./utils/builders/heroku";
export * from "./utils/builders/nixpacks";
export * from "./utils/builders/paketo";
export * from "./utils/builders/static";
export * from "./utils/builders/utils";
export * from "./utils/cluster/upload";
export * from "./utils/docker/compose";
export * from "./utils/docker/domain";
export * from "./utils/docker/utils";
export * from "./utils/docker/compose/configs";
export * from "./utils/docker/compose/network";
export * from "./utils/docker/compose/secrets";
export * from "./utils/docker/compose/service";
export * from "./utils/docker/compose/volume";
export * from "./utils/filesystem/directory";
export * from "./utils/filesystem/ssh";
export * from "./utils/process/execAsync";
export * from "./utils/process/spawnAsync";
export * from "./utils/providers/bitbucket";
export * from "./utils/providers/docker";
export * from "./utils/providers/git";
export * from "./utils/providers/github";
export * from "./utils/providers/gitlab";
export * from "./utils/providers/raw";
export * from "./utils/servers/remote-docker";
export * from "./utils/traefik/application";
export * from "./utils/traefik/domain";
export * from "./utils/traefik/file-types";
export * from "./utils/traefik/middleware";
export * from "./utils/traefik/redirect";
export * from "./utils/traefik/registry";
export * from "./utils/traefik/security";
export * from "./utils/traefik/types";
export * from "./utils/traefik/web-server";
export * from "./wss/docker-container-logs";
export * from "./wss/docker-container-terminal";
export * from "./wss/docker-stats";
export * from "./wss/listen-deployment";
export * from "./wss/terminal";
export * from "./wss/utils";
export * from "./utils/access-log/handler";
export * from "./utils/access-log/types";
export * from "./utils/access-log/utils";

Some files were not shown because too many files have changed in this diff Show More