mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
50 Commits
wip
...
481-when-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a6bf06929 | ||
|
|
ecc98b0944 | ||
|
|
950ee77a49 | ||
|
|
38a72806a7 | ||
|
|
fa70696c71 | ||
|
|
b123baafa4 | ||
|
|
d6fa416a3f | ||
|
|
d2c0f19353 | ||
|
|
3ef6c711e6 | ||
|
|
64a4d51f9c | ||
|
|
1a20e4f813 | ||
|
|
250c14738c | ||
|
|
28221a4e7a | ||
|
|
680c22a41e | ||
|
|
428b9cf0ec | ||
|
|
78659b2ad9 | ||
|
|
4b5408c050 | ||
|
|
5417f6376b | ||
|
|
27c33c7661 | ||
|
|
0873618c63 | ||
|
|
e3b20268d5 | ||
|
|
742e5a244d | ||
|
|
1f208f5f5b | ||
|
|
a53ade88e7 | ||
|
|
77e9617770 | ||
|
|
21e97b0175 | ||
|
|
a6618a14d5 | ||
|
|
59308ab013 | ||
|
|
e19c8d7a7a | ||
|
|
421c93795b | ||
|
|
182f908c31 | ||
|
|
20616363e9 | ||
|
|
d85073b26d | ||
|
|
303d1b1b87 | ||
|
|
60d4e1ba63 | ||
|
|
83d52b68f0 | ||
|
|
af3b1a27f4 | ||
|
|
7f94593c07 | ||
|
|
5df7654873 | ||
|
|
054836fd4c | ||
|
|
484ead1f1f | ||
|
|
fbada4c5de | ||
|
|
491113416b | ||
|
|
c42f5cb799 | ||
|
|
47aa223f87 | ||
|
|
554ac59b97 | ||
|
|
0247898876 | ||
|
|
fc2778db35 | ||
|
|
85d6ff9012 | ||
|
|
522f8baec7 |
10
.github/workflows/deploy.yml
vendored
10
.github/workflows/deploy.yml
vendored
@@ -70,8 +70,12 @@ jobs:
|
|||||||
file: ./Dockerfile.cloud
|
file: ./Dockerfile.cloud
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
siumauricio/cloud:${{ github.ref_name == 'main' && 'main' || 'canary' }}
|
siumauricio/cloud:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
|
build-args: |
|
||||||
|
NEXT_PUBLIC_UMAMI_HOST=${{ secrets.NEXT_PUBLIC_UMAMI_HOST }}
|
||||||
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${{ secrets.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY }}
|
||||||
|
|
||||||
build-and-push-schedule-image:
|
build-and-push-schedule-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -93,7 +97,7 @@ jobs:
|
|||||||
file: ./Dockerfile.schedule
|
file: ./Dockerfile.schedule
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
siumauricio/schedule:${{ github.ref_name == 'main' && 'main' || 'canary' }}
|
siumauricio/schedule:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
|
||||||
@@ -117,5 +121,5 @@ jobs:
|
|||||||
file: ./Dockerfile.server
|
file: ./Dockerfile.server
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
siumauricio/server:${{ github.ref_name == 'main' && 'main' || 'canary' }}
|
siumauricio/server:${{ github.ref_name == 'main' && 'latest' || 'canary' }}
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
@@ -71,10 +71,9 @@ Run the command that will spin up all the required services and files.
|
|||||||
pnpm run dokploy:setup
|
pnpm run dokploy:setup
|
||||||
```
|
```
|
||||||
|
|
||||||
Build the server package (If you make any changes after in the packages/server folder, you need to rebuild and run this command)
|
Run this script
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run server:build
|
pnpm run server:script
|
||||||
```
|
```
|
||||||
|
|
||||||
Now run the development server.
|
Now run the development server.
|
||||||
|
|||||||
@@ -12,7 +12,16 @@ RUN apt-get update && apt-get install -y python3 make g++ git && rm -rf /var/lib
|
|||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/dokploy install --frozen-lockfile
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm --filter=@dokploy/server --filter=./apps/dokploy install --frozen-lockfile
|
||||||
|
|
||||||
|
|
||||||
# Deploy only the dokploy app
|
# Deploy only the dokploy app
|
||||||
|
ARG NEXT_PUBLIC_UMAMI_HOST
|
||||||
|
ENV NEXT_PUBLIC_UMAMI_HOST=$NEXT_PUBLIC_UMAMI_HOST
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
|
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
|
|
||||||
|
ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||||
|
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=$NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
RUN pnpm --filter=@dokploy/server build
|
RUN pnpm --filter=@dokploy/server build
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -15,29 +15,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
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 include multiples features to make your life easier.
|
Dokploy includes multiple features to make your life easier.
|
||||||
|
|
||||||
- **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
- **Applications**: Deploy any type of application (Node.js, PHP, Python, Go, Ruby, etc.).
|
||||||
- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, Redis.
|
- **Databases**: Create and manage databases with support for MySQL, PostgreSQL, MongoDB, MariaDB, and Redis.
|
||||||
- **Backups**: Automate backups for databases to a external storage destination.
|
- **Backups**: Automate backups for databases to an external storage destination.
|
||||||
- **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
- **Docker Compose**: Native support for Docker Compose to manage complex applications.
|
||||||
- **Multi Node**: Scale applications to multiples nodes using docker swarm to manage the cluster.
|
- **Multi Node**: Scale applications to multiple nodes using Docker Swarm to manage the cluster.
|
||||||
- **Templates**: Deploy in a single click open source templates (Plausible, Pocketbase, Calcom, etc.).
|
- **Templates**: Deploy open-source templates (Plausible, Pocketbase, Calcom, etc.) with a single click.
|
||||||
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||||
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage for every resource.
|
||||||
- **Docker Management**: Easily deploy and manage Docker containers.
|
- **Docker Management**: Easily deploy and manage Docker containers.
|
||||||
- **CLI/API**: Manage your applications and databases using the command line or through the API.
|
- **CLI/API**: Manage your applications and databases using the command line or through the API.
|
||||||
- **Notifications**: Get notified when your deployments are successful or failed (Slack, Discord, Telegram, Email, etc.)
|
- **Notifications**: Get notified when your deployments succeed or fail (via Slack, Discord, Telegram, Email, etc.).
|
||||||
- **Multi Server**: Deploy and manager your applications remotely to external servers.
|
- **Multi Server**: Deploy and manage your applications remotely to external servers.
|
||||||
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|
||||||
To get started run the following command in a VPS:
|
To get started, run the following command on a VPS:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sSL https://dokploy.com/install.sh | sh
|
curl -sSL https://dokploy.com/install.sh | sh
|
||||||
@@ -90,6 +90,8 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
|
||||||
<a href="https://steamsets.com/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
|
<a href="https://steamsets.com/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
|
||||||
<a href="https://rivo.gg/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/126797452?s=200&v=4" width="60px" alt="Rivo.gg"/></a>
|
<a href="https://rivo.gg/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/126797452?s=200&v=4" width="60px" alt="Rivo.gg"/></a>
|
||||||
|
<a href="https://photoquest.wedding/?ref=dokploy"><img src="https://photoquest.wedding/favicon/android-chrome-512x512.png" width="60px" alt="Rivo.gg"/></a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
#### Organizations:
|
#### Organizations:
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
export interface LemonSqueezyLicenseResponse {
|
|
||||||
valid: boolean;
|
|
||||||
error?: string;
|
|
||||||
meta?: {
|
|
||||||
store_id: string;
|
|
||||||
order_id: number;
|
|
||||||
order_item_id: number;
|
|
||||||
product_id: number;
|
|
||||||
product_name: string;
|
|
||||||
variant_id: number;
|
|
||||||
variant_name: string;
|
|
||||||
customer_id: number;
|
|
||||||
customer_name: string;
|
|
||||||
customer_email: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,45 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
deployApplication,
|
|
||||||
deployCompose,
|
|
||||||
deployRemoteApplication,
|
deployRemoteApplication,
|
||||||
deployRemoteCompose,
|
deployRemoteCompose,
|
||||||
rebuildApplication,
|
|
||||||
rebuildCompose,
|
|
||||||
rebuildRemoteApplication,
|
rebuildRemoteApplication,
|
||||||
rebuildRemoteCompose,
|
rebuildRemoteCompose,
|
||||||
updateApplicationStatus,
|
updateApplicationStatus,
|
||||||
updateCompose,
|
updateCompose,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import type { DeployJob } from "./schema";
|
import type { DeployJob } from "./schema";
|
||||||
import type { LemonSqueezyLicenseResponse } from "./types";
|
|
||||||
|
|
||||||
// const LEMON_SQUEEZY_API_KEY = process.env.LEMON_SQUEEZY_API_KEY;
|
|
||||||
// const LEMON_SQUEEZY_STORE_ID = process.env.LEMON_SQUEEZY_STORE_ID;
|
|
||||||
// export const validateLemonSqueezyLicense = async (
|
|
||||||
// licenseKey: string,
|
|
||||||
// ): Promise<LemonSqueezyLicenseResponse> => {
|
|
||||||
// try {
|
|
||||||
// const response = await fetch(
|
|
||||||
// "https://api.lemonsqueezy.com/v1/licenses/validate",
|
|
||||||
// {
|
|
||||||
// method: "POST",
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// "x-api-key": LEMON_SQUEEZY_API_KEY as string,
|
|
||||||
// },
|
|
||||||
// body: JSON.stringify({
|
|
||||||
// license_key: licenseKey,
|
|
||||||
// store_id: LEMON_SQUEEZY_STORE_ID as string,
|
|
||||||
// }),
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return response.json();
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error("Error validating license:", error);
|
|
||||||
// return { valid: false, error: "Error validating license" };
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const deploy = async (job: DeployJob) => {
|
export const deploy = async (job: DeployJob) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
PlugZapIcon,
|
PlugZapIcon,
|
||||||
TerminalIcon,
|
TerminalIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import Script from "next/script";
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
@@ -63,6 +64,10 @@ export default function Layout({
|
|||||||
className={inter.className}
|
className={inter.className}
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
|
<Script
|
||||||
|
src="https://umami.dokploy.com/script.js"
|
||||||
|
data-website-id="6ad2aa56-6d38-4f39-97a8-1a8fcdda8d51"
|
||||||
|
/>
|
||||||
<GoogleAnalytics />
|
<GoogleAnalytics />
|
||||||
<body>
|
<body>
|
||||||
<I18nProvider
|
<I18nProvider
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ description: '学习如何在服务器上手动安装 Dokploy。'
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# 确保以根用户身份运行脚本
|
# Ensure the script is run as root
|
||||||
if [ "$(id -u)" != "0" ]; then
|
if [ "$(id -u)" != "0" ]; then
|
||||||
echo "This script must be run as root" >&2
|
echo "This script must be run as root" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查 Linux 操作系统(非 macOS 或 Docker 容器内的操作系统)
|
# Check for Linux OS (not macOS or inside a Docker container)
|
||||||
if [ "$(uname)" = "Darwin" ]; then
|
if [ "$(uname)" = "Darwin" ]; then
|
||||||
echo "This script must be run on Linux" >&2
|
echo "This script must be run on Linux" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -32,7 +32,7 @@ if [ -f /.dockerenv ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查端口是否被占用
|
# Check for occupied ports
|
||||||
if ss -tulnp | grep ':80 ' >/dev/null; then
|
if ss -tulnp | grep ':80 ' >/dev/null; then
|
||||||
echo "Error: Port 80 is already in use" >&2
|
echo "Error: Port 80 is already in use" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -43,32 +43,53 @@ if ss -tulnp | grep ':443 ' >/dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查命令是否存在
|
# Function to check if a command exists
|
||||||
command_exists() {
|
command_exists() {
|
||||||
command -v "$@" > /dev/null 2>&1
|
command -v "$@" > /dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
# 如果未安装 Docker 则安装
|
# Install Docker if it is not installed
|
||||||
if command_exists docker; then
|
if command_exists docker; then
|
||||||
echo "Docker already installed"
|
echo "Docker already installed"
|
||||||
else
|
else
|
||||||
curl -sSL https://get.docker.com | sh
|
curl -sSL https://get.docker.com | sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 初始化 Docker Swarm
|
# Initialize Docker Swarm
|
||||||
docker swarm leave --force 2>/dev/null
|
docker swarm leave --force 2>/dev/null
|
||||||
advertise_addr=$(curl -s ifconfig.me)
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
|
||||||
echo "Swarm initialized"
|
|
||||||
|
|
||||||
# 创建网络
|
get_ip() {
|
||||||
docker network rm -f dokploy-network 2>/dev/null
|
# Try to get IPv4
|
||||||
docker network create --driver overlay --attachable dokploy-network
|
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||||
echo "Network created"
|
|
||||||
|
|
||||||
# 准备配置目录
|
if [ -n "$ipv4" ]; then
|
||||||
mkdir -p /etc/dokploy
|
echo "$ipv4"
|
||||||
chmod -R 777 /etc/dokploy
|
else
|
||||||
|
# Try to get IPv6
|
||||||
|
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||||
|
if [ -n "$ipv6" ]; then
|
||||||
|
echo "$ipv6"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||||
|
|
||||||
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker network rm -f dokploy-network 2>/dev/null
|
||||||
|
docker network create --driver overlay --attachable dokploy-network
|
||||||
|
|
||||||
|
echo "Network created"
|
||||||
|
|
||||||
|
mkdir -p /etc/dokploy
|
||||||
|
|
||||||
|
chmod 777 /etc/dokploy
|
||||||
|
|
||||||
# Pull and deploy Dokploy
|
# Pull and deploy Dokploy
|
||||||
docker pull dokploy/dokploy:latest
|
docker pull dokploy/dokploy:latest
|
||||||
@@ -84,9 +105,10 @@ docker service create \
|
|||||||
-e PORT=<Value For PORT eg(3000)> \
|
-e PORT=<Value For PORT eg(3000)> \
|
||||||
-e TRAEFIK_SSL_PORT=<Value For SSL PORT eg(444)> \
|
-e TRAEFIK_SSL_PORT=<Value For SSL PORT eg(444)> \
|
||||||
-e TRAEFIK_PORT=<VALUE FOR TRAEFIK HTTP PORT eg(81)> \
|
-e TRAEFIK_PORT=<VALUE FOR TRAEFIK HTTP PORT eg(81)> \
|
||||||
|
-e ADVERTISE_ADDR=$advertise_addr \
|
||||||
dokploy/dokploy:latest
|
dokploy/dokploy:latest
|
||||||
|
|
||||||
# 输出成功消息
|
# Output success message
|
||||||
GREEN="\033[0;32m"
|
GREEN="\033[0;32m"
|
||||||
YELLOW="\033[1;33m"
|
YELLOW="\033[1;33m"
|
||||||
BLUE="\033[0;34m"
|
BLUE="\033[0;34m"
|
||||||
|
|||||||
@@ -57,18 +57,39 @@ fi
|
|||||||
|
|
||||||
# Initialize Docker Swarm
|
# Initialize Docker Swarm
|
||||||
docker swarm leave --force 2>/dev/null
|
docker swarm leave --force 2>/dev/null
|
||||||
advertise_addr=$(curl -s ifconfig.me)
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
|
||||||
echo "Swarm initialized"
|
|
||||||
|
|
||||||
# Create network
|
get_ip() {
|
||||||
docker network rm -f dokploy-network 2>/dev/null
|
# Try to get IPv4
|
||||||
docker network create --driver overlay --attachable dokploy-network
|
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||||
echo "Network created"
|
|
||||||
|
|
||||||
# Prepare configuration directory
|
if [ -n "$ipv4" ]; then
|
||||||
mkdir -p /etc/dokploy
|
echo "$ipv4"
|
||||||
chmod -R 777 /etc/dokploy
|
else
|
||||||
|
# Try to get IPv6
|
||||||
|
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||||
|
if [ -n "$ipv6" ]; then
|
||||||
|
echo "$ipv6"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||||
|
|
||||||
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker network rm -f dokploy-network 2>/dev/null
|
||||||
|
docker network create --driver overlay --attachable dokploy-network
|
||||||
|
|
||||||
|
echo "Network created"
|
||||||
|
|
||||||
|
mkdir -p /etc/dokploy
|
||||||
|
|
||||||
|
chmod 777 /etc/dokploy
|
||||||
|
|
||||||
# Pull and deploy Dokploy
|
# Pull and deploy Dokploy
|
||||||
docker pull dokploy/dokploy:latest
|
docker pull dokploy/dokploy:latest
|
||||||
@@ -84,6 +105,7 @@ docker service create \
|
|||||||
-e PORT=<Value For PORT eg(3000)> \
|
-e PORT=<Value For PORT eg(3000)> \
|
||||||
-e TRAEFIK_SSL_PORT=<Value For SSL PORT eg(444)> \
|
-e TRAEFIK_SSL_PORT=<Value For SSL PORT eg(444)> \
|
||||||
-e TRAEFIK_PORT=<VALUE FOR TRAEFIK HTTP PORT eg(81)> \
|
-e TRAEFIK_PORT=<VALUE FOR TRAEFIK HTTP PORT eg(81)> \
|
||||||
|
-e ADVERTISE_ADDR=$advertise_addr \
|
||||||
dokploy/dokploy:latest
|
dokploy/dokploy:latest
|
||||||
|
|
||||||
# Output success message
|
# Output success message
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { paths } from "@dokploy/server/dist/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
const { APPLICATIONS_PATH } = paths();
|
const { APPLICATIONS_PATH } = paths();
|
||||||
import type { ApplicationNested } from "@dokploy/server";
|
import type { ApplicationNested } from "@dokploy/server";
|
||||||
import { unzipDrop } from "@dokploy/server";
|
import { unzipDrop } from "@dokploy/server";
|
||||||
import AdmZip from "adm-zip";
|
import AdmZip from "adm-zip";
|
||||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("@dokploy/server/constants", async (importOriginal) => {
|
||||||
|
const actual = await importOriginal();
|
||||||
|
return {
|
||||||
|
// @ts-ignore
|
||||||
|
...actual,
|
||||||
|
paths: () => ({
|
||||||
|
APPLICATIONS_PATH: "./__test__/drop/zips/output",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
const undici = require("undici");
|
const undici = require("undici");
|
||||||
globalThis.File = undici.File as any;
|
globalThis.File = undici.File as any;
|
||||||
@@ -82,16 +93,6 @@ const baseApp: ApplicationNested = {
|
|||||||
dockerContextPath: null,
|
dockerContextPath: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.mock("@dokploy/server/dist/constants", async (importOriginal) => {
|
|
||||||
const actual = await importOriginal();
|
|
||||||
return {
|
|
||||||
// @ts-ignore
|
|
||||||
...actual,
|
|
||||||
paths: () => ({
|
|
||||||
APPLICATIONS_PATH: "./__test__/drop/zips/output",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
describe("unzipDrop using real zip files", () => {
|
describe("unzipDrop using real zip files", () => {
|
||||||
// const { APPLICATIONS_PATH } = paths();
|
// const { APPLICATIONS_PATH } = paths();
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
|
import path from "node:path";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
import tsconfigPaths from "vite-tsconfig-paths";
|
||||||
import { defineConfig } from "vitest/config";
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
|
||||||
tsconfigPaths({
|
|
||||||
root: "./",
|
|
||||||
projects: ["tsconfig.json"],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
test: {
|
test: {
|
||||||
include: ["__test__/**/*.test.ts"], // Incluir solo los archivos de test en el directorio __test__
|
include: ["__test__/**/*.test.ts"], // Incluir solo los archivos de test en el directorio __test__
|
||||||
exclude: ["**/node_modules/**", "**/dist/**", "**/.docker/**"],
|
exclude: ["**/node_modules/**", "**/dist/**", "**/.docker/**"],
|
||||||
@@ -18,4 +13,12 @@ export default defineConfig({
|
|||||||
NODE: "test",
|
NODE: "test",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@dokploy/server": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"../../../packages/server/src",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,11 +6,18 @@ import { cn } from "@/lib/utils";
|
|||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { loadStripe } from "@stripe/stripe-js";
|
import { loadStripe } from "@stripe/stripe-js";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { AlertTriangle, CheckIcon, MinusIcon, PlusIcon } from "lucide-react";
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
CheckIcon,
|
||||||
|
Loader2,
|
||||||
|
MinusIcon,
|
||||||
|
PlusIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
const stripePromise = loadStripe(
|
const stripePromise = loadStripe(
|
||||||
"pk_test_51QAm7bF3cxQuHeOz0xg04o9teeyTbbNHQPJ5Tr98MlTEan9MzewT3gwh0jSWBNvrRWZ5vASoBgxUSF4gPWsJwATk00Ir2JZ0S1",
|
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || "",
|
||||||
);
|
);
|
||||||
|
|
||||||
export const calculatePrice = (count: number, isAnnual = false) => {
|
export const calculatePrice = (count: number, isAnnual = false) => {
|
||||||
@@ -21,11 +28,10 @@ export const calculatePrice = (count: number, isAnnual = false) => {
|
|||||||
if (count <= 1) return 4.5;
|
if (count <= 1) return 4.5;
|
||||||
return count * 3.5;
|
return count * 3.5;
|
||||||
};
|
};
|
||||||
// 178.156.147.118
|
|
||||||
export const ShowBilling = () => {
|
export const ShowBilling = () => {
|
||||||
const { data: servers } = api.server.all.useQuery(undefined);
|
const { data: servers } = api.server.all.useQuery(undefined);
|
||||||
const { data: admin } = api.admin.one.useQuery();
|
const { data: admin } = api.admin.one.useQuery();
|
||||||
const { data } = api.stripe.getProducts.useQuery();
|
const { data, isLoading } = api.stripe.getProducts.useQuery();
|
||||||
const { mutateAsync: createCheckoutSession } =
|
const { mutateAsync: createCheckoutSession } =
|
||||||
api.stripe.createCheckoutSession.useMutation();
|
api.stripe.createCheckoutSession.useMutation();
|
||||||
|
|
||||||
@@ -97,6 +103,39 @@ export const ShowBilling = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="flex flex-col gap-1.5 mt-4">
|
||||||
|
<span className="text-base text-primary">
|
||||||
|
Need Help? We are here to help you.
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
Join to our Discord server and we will help you.
|
||||||
|
</span>
|
||||||
|
<Button className="rounded-full bg-[#5965F2] hover:bg-[#4A55E0] w-fit">
|
||||||
|
<Link
|
||||||
|
href="https://discord.gg/2tBnJ3jDJc"
|
||||||
|
aria-label="Dokploy on GitHub"
|
||||||
|
target="_blank"
|
||||||
|
className="flex flex-row items-center gap-2 text-white"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
className="h-6 w-6 fill-white"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||||
|
</svg>
|
||||||
|
Join Discord
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{isLoading ? (
|
||||||
|
<span className="text-base text-muted-foreground flex flex-row gap-3 items-center justify-center min-h-[10vh]">
|
||||||
|
Loading...
|
||||||
|
<Loader2 className="animate-spin" />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{products?.map((product) => {
|
{products?.map((product) => {
|
||||||
const featured = true;
|
const featured = true;
|
||||||
return (
|
return (
|
||||||
@@ -112,18 +151,22 @@ export const ShowBilling = () => {
|
|||||||
{isAnnual ? (
|
{isAnnual ? (
|
||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<p className=" text-2xl font-semibold tracking-tight text-primary ">
|
<p className=" text-2xl font-semibold tracking-tight text-primary ">
|
||||||
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
|
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)}{" "}
|
||||||
|
USD
|
||||||
</p>
|
</p>
|
||||||
|
|
|
|
||||||
<p className=" text-base font-semibold tracking-tight text-muted-foreground">
|
<p className=" text-base font-semibold tracking-tight text-muted-foreground">
|
||||||
${" "}
|
${" "}
|
||||||
{(calculatePrice(serverQuantity, isAnnual) / 12).toFixed(2)}{" "}
|
{(
|
||||||
|
calculatePrice(serverQuantity, isAnnual) / 12
|
||||||
|
).toFixed(2)}{" "}
|
||||||
/ Month USD
|
/ Month USD
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className=" text-2xl font-semibold tracking-tight text-primary ">
|
<p className=" text-2xl font-semibold tracking-tight text-primary ">
|
||||||
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)} USD
|
$ {calculatePrice(serverQuantity, isAnnual).toFixed(2)}{" "}
|
||||||
|
USD
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<h3 className="mt-5 font-medium text-lg text-white">
|
<h3 className="mt-5 font-medium text-lg text-white">
|
||||||
@@ -182,7 +225,9 @@ export const ShowBilling = () => {
|
|||||||
<NumberInput
|
<NumberInput
|
||||||
value={serverQuantity}
|
value={serverQuantity}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setServerQuantity(e.target.value as unknown as number);
|
setServerQuantity(
|
||||||
|
e.target.value as unknown as number,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -236,6 +281,8 @@ export const ShowBilling = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ export const ProfileForm = () => {
|
|||||||
const { data, refetch } = api.auth.get.useQuery();
|
const { data, refetch } = api.auth.get.useQuery();
|
||||||
const { mutateAsync, isLoading } = api.auth.update.useMutation();
|
const { mutateAsync, isLoading } = api.auth.update.useMutation();
|
||||||
|
|
||||||
const { mutateAsync: generateToken, isLoading: isLoadingToken } =
|
|
||||||
api.auth.generateToken.useMutation();
|
|
||||||
const form = useForm<Profile>({
|
const form = useForm<Profile>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: data?.email || "",
|
email: data?.email || "",
|
||||||
@@ -76,7 +74,7 @@ export const ProfileForm = () => {
|
|||||||
|
|
||||||
const onSubmit = async (values: Profile) => {
|
const onSubmit = async (values: Profile) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
email: values.email,
|
email: values.email.toLowerCase(),
|
||||||
password: values.password,
|
password: values.password,
|
||||||
image: values.image,
|
image: values.image,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const AddUser = () => {
|
|||||||
|
|
||||||
const onSubmit = async (data: AddUser) => {
|
const onSubmit = async (data: AddUser) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
email: data.email,
|
email: data.email.toLowerCase(),
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Invitation created");
|
toast.success("Invitation created");
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { api } from "@/utils/api";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { SquarePen } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const updateUserSchema = z.object({
|
|
||||||
email: z
|
|
||||||
.string()
|
|
||||||
.min(1, "Email is required")
|
|
||||||
.email({ message: "Invalid email" }),
|
|
||||||
password: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
type UpdateUser = z.infer<typeof updateUserSchema>;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
authId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UpdateUser = ({ authId }: Props) => {
|
|
||||||
const utils = api.useUtils();
|
|
||||||
const { mutateAsync, error, isError, isLoading } =
|
|
||||||
api.auth.updateByAdmin.useMutation();
|
|
||||||
const { data } = api.auth.one.useQuery(
|
|
||||||
{
|
|
||||||
id: authId,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: !!authId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const form = useForm<UpdateUser>({
|
|
||||||
defaultValues: {
|
|
||||||
email: "",
|
|
||||||
password: "",
|
|
||||||
},
|
|
||||||
resolver: zodResolver(updateUserSchema),
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
form.reset({
|
|
||||||
email: data.email || "",
|
|
||||||
password: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [data, form, form.reset]);
|
|
||||||
|
|
||||||
const onSubmit = async (formData: UpdateUser) => {
|
|
||||||
await mutateAsync({
|
|
||||||
email: formData.email === data?.email ? null : formData.email,
|
|
||||||
password: formData.password,
|
|
||||||
id: authId,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
toast.success("User updated succesfully");
|
|
||||||
utils.user.all.invalidate();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
toast.error("Error to update the user");
|
|
||||||
})
|
|
||||||
.finally(() => {});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild className="w-fit">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className=" cursor-pointer space-x-3 w-fit"
|
|
||||||
onSelect={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<SquarePen className="size-4 text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Update User</DialogTitle>
|
|
||||||
<DialogDescription>Update the user</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
|
||||||
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<div className="grid items-center gap-4">
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
id="hook-form-update-user"
|
|
||||||
className="grid w-full gap-4 "
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Email</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="XNl5C@example.com" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Password</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
type="password"
|
|
||||||
placeholder="*******"
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
form="hook-form-update-user"
|
|
||||||
type="submit"
|
|
||||||
isLoading={isLoading}
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
2
apps/dokploy/drizzle/0042_fancy_havok.sql
Normal file
2
apps/dokploy/drizzle/0042_fancy_havok.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "auth" ADD COLUMN "confirmationToken" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "auth" ADD COLUMN "confirmationExpiresAt" text;
|
||||||
3968
apps/dokploy/drizzle/meta/0042_snapshot.json
Normal file
3968
apps/dokploy/drizzle/meta/0042_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -295,6 +295,13 @@
|
|||||||
"when": 1729667438853,
|
"when": 1729667438853,
|
||||||
"tag": "0041_huge_bruce_banner",
|
"tag": "0041_huge_bruce_banner",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 42,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1729984439862,
|
||||||
|
"tag": "0042_fancy_havok",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.10.2",
|
"version": "v0.10.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"build-next": "next build",
|
"build-next": "next build",
|
||||||
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
||||||
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
||||||
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
"dev": "TURBOPACK=1 tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
||||||
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
||||||
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
||||||
"migration:run": "tsx -r dotenv/config migration.ts",
|
"migration:run": "tsx -r dotenv/config migration.ts",
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
"lucia": "^3.0.1",
|
"lucia": "^3.0.1",
|
||||||
"lucide-react": "^0.312.0",
|
"lucide-react": "^0.312.0",
|
||||||
"nanoid": "3",
|
"nanoid": "3",
|
||||||
"next": "^14.1.3",
|
"next": "^15.0.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"node-pty": "1.0.0",
|
"node-pty": "1.0.0",
|
||||||
"node-schedule": "2.1.1",
|
"node-schedule": "2.1.1",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ThemeProvider } from "next-themes";
|
|||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
import Script from "next/script";
|
||||||
import type { ReactElement, ReactNode } from "react";
|
import type { ReactElement, ReactNode } from "react";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
@@ -36,6 +37,14 @@ const MyApp = ({
|
|||||||
<Head>
|
<Head>
|
||||||
<title>Dokploy</title>
|
<title>Dokploy</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
{process.env.NEXT_PUBLIC_UMAMI_HOST &&
|
||||||
|
process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && (
|
||||||
|
<Script
|
||||||
|
src={process.env.NEXT_PUBLIC_UMAMI_HOST}
|
||||||
|
data-website-id={process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="system"
|
defaultTheme="system"
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { Head, Html, Main, NextScript } from "next/document";
|
|||||||
export default function Document() {
|
export default function Document() {
|
||||||
return (
|
return (
|
||||||
<Html lang="en" className="font-sans">
|
<Html lang="en" className="font-sans">
|
||||||
<Head />
|
<Head>
|
||||||
|
<link rel="icon" href="/icon.svg" />
|
||||||
|
</Head>
|
||||||
<body className="flex h-full flex-col font-sans">
|
<body className="flex h-full flex-col font-sans">
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import { applications } from "@/server/db/schema";
|
import { applications } from "@/server/db/schema";
|
||||||
import type { DeploymentJob } from "@/server/queues/deployments-queue";
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
import { myQueue } from "@/server/queues/queueSetup";
|
||||||
import { deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import { IS_CLOUD } from "@dokploy/server";
|
import { IS_CLOUD } from "@dokploy/server";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import { compose } from "@/server/db/schema";
|
import { compose } from "@/server/db/schema";
|
||||||
import type { DeploymentJob } from "@/server/queues/deployments-queue";
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
import { myQueue } from "@/server/queues/queueSetup";
|
||||||
import { deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import { IS_CLOUD } from "@dokploy/server";
|
import { IS_CLOUD } from "@dokploy/server";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import { applications, compose, github } from "@/server/db/schema";
|
import { applications, compose, github } from "@/server/db/schema";
|
||||||
import type { DeploymentJob } from "@/server/queues/deployments-queue";
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
import { myQueue } from "@/server/queues/queueSetup";
|
||||||
import { deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import { IS_CLOUD } from "@dokploy/server";
|
import { IS_CLOUD } from "@dokploy/server";
|
||||||
|
|||||||
96
apps/dokploy/pages/confirm-email.tsx
Normal file
96
apps/dokploy/pages/confirm-email.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
|
||||||
|
import { Logo } from "@/components/shared/logo";
|
||||||
|
import { CardDescription, CardTitle } from "@/components/ui/card";
|
||||||
|
import { db } from "@/server/db";
|
||||||
|
import { auth } from "@/server/db/schema";
|
||||||
|
import { IS_CLOUD, updateAuthById } from "@dokploy/server";
|
||||||
|
import { isBefore } from "date-fns";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen w-full items-center justify-center ">
|
||||||
|
<div className="flex flex-col items-center gap-4 w-full">
|
||||||
|
<Link href="/" className="flex flex-row items-center gap-2">
|
||||||
|
<Logo />
|
||||||
|
<span className="font-medium text-sm">Dokploy</span>
|
||||||
|
</Link>
|
||||||
|
<CardTitle className="text-2xl font-bold">Email Confirmed</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Congratulations, your email is confirmed.
|
||||||
|
</CardDescription>
|
||||||
|
<div>
|
||||||
|
<Link href="/" className="w-full text-primary">
|
||||||
|
Click here to login
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Home.getLayout = (page: ReactElement) => {
|
||||||
|
return <OnboardingLayout>{page}</OnboardingLayout>;
|
||||||
|
};
|
||||||
|
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||||
|
if (!IS_CLOUD) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { token } = context.query;
|
||||||
|
|
||||||
|
if (typeof token !== "string") {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const authR = await db.query.auth.findFirst({
|
||||||
|
where: eq(auth.confirmationToken, token),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
!authR ||
|
||||||
|
authR?.confirmationToken === null ||
|
||||||
|
authR?.confirmationExpiresAt === null
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExpired = isBefore(new Date(authR.confirmationExpiresAt), new Date());
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateAuthById(authR.id, {
|
||||||
|
confirmationToken: null,
|
||||||
|
confirmationExpiresAt: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
token: authR.confirmationToken,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -110,7 +110,9 @@ const Service = (
|
|||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
data?.server?.serverStatus === "active"
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
? "default"
|
? "default"
|
||||||
: "destructive"
|
: "destructive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,9 @@ const Service = (
|
|||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
data?.server?.serverStatus === "active"
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
? "default"
|
? "default"
|
||||||
: "destructive"
|
: "destructive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ const Mariadb = (
|
|||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
data?.server?.serverStatus === "active"
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
? "default"
|
? "default"
|
||||||
: "destructive"
|
: "destructive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,9 @@ const Mongo = (
|
|||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
data?.server?.serverStatus === "active"
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
? "default"
|
? "default"
|
||||||
: "destructive"
|
: "destructive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ const MySql = (
|
|||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
data?.server?.serverStatus === "active"
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
? "default"
|
? "default"
|
||||||
: "destructive"
|
: "destructive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,9 @@ const Postgresql = (
|
|||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
data?.server?.serverStatus === "active"
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
? "default"
|
? "default"
|
||||||
: "destructive"
|
: "destructive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ const Redis = (
|
|||||||
<div className="flex flex-row h-fit w-fit gap-2">
|
<div className="flex flex-row h-fit w-fit gap-2">
|
||||||
<Badge
|
<Badge
|
||||||
variant={
|
variant={
|
||||||
data?.server?.serverStatus === "active"
|
!data?.serverId
|
||||||
|
? "default"
|
||||||
|
: data?.server?.serverStatus === "active"
|
||||||
? "default"
|
? "default"
|
||||||
: "destructive"
|
: "destructive"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ export async function getServerSideProps(
|
|||||||
|
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
|
if (user?.rol === "user") {
|
||||||
|
await helpers.user.byAuthId.prefetch({
|
||||||
|
authId: user.authId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ export async function getServerSideProps(
|
|||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
|
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { ShowNodes } from "@/components/dashboard/settings/cluster/nodes/show-nodes";
|
import { ShowNodes } from "@/components/dashboard/settings/cluster/nodes/show-nodes";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||||
|
import { appRouter } from "@/server/api/root";
|
||||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||||
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import React, { type ReactElement } from "react";
|
import React, { type ReactElement } from "react";
|
||||||
|
import superjson from "superjson";
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return (
|
return (
|
||||||
@@ -25,6 +28,7 @@ Page.getLayout = (page: ReactElement) => {
|
|||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||||
) {
|
) {
|
||||||
|
const { req, res } = ctx;
|
||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
@@ -42,8 +46,22 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const helpers = createServerSideHelpers({
|
||||||
|
router: appRouter,
|
||||||
|
ctx: {
|
||||||
|
req: req as any,
|
||||||
|
res: res as any,
|
||||||
|
db: null as any,
|
||||||
|
session: session,
|
||||||
|
user: user,
|
||||||
|
},
|
||||||
|
transformer: superjson,
|
||||||
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {
|
||||||
|
trpcState: helpers.dehydrate(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
try {
|
try {
|
||||||
await helpers.project.all.prefetch();
|
await helpers.project.all.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ export async function getServerSideProps(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
|
if (user?.rol === "user") {
|
||||||
|
await helpers.user.byAuthId.prefetch({
|
||||||
|
authId: user.authId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import { WebDomain } from "@/components/dashboard/settings/web-domain";
|
|||||||
import { WebServer } from "@/components/dashboard/settings/web-server";
|
import { WebServer } from "@/components/dashboard/settings/web-server";
|
||||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||||
|
import { appRouter } from "@/server/api/root";
|
||||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||||
|
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import React, { type ReactElement } from "react";
|
import React, { type ReactElement } from "react";
|
||||||
|
import superjson from "superjson";
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return (
|
return (
|
||||||
@@ -27,6 +30,7 @@ Page.getLayout = (page: ReactElement) => {
|
|||||||
export async function getServerSideProps(
|
export async function getServerSideProps(
|
||||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||||
) {
|
) {
|
||||||
|
const { req, res } = ctx;
|
||||||
if (IS_CLOUD) {
|
if (IS_CLOUD) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
@@ -35,7 +39,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { user } = await validateRequest(ctx.req, ctx.res);
|
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
@@ -53,7 +57,22 @@ export async function getServerSideProps(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const helpers = createServerSideHelpers({
|
||||||
|
router: appRouter,
|
||||||
|
ctx: {
|
||||||
|
req: req as any,
|
||||||
|
res: res as any,
|
||||||
|
db: null as any,
|
||||||
|
session: session,
|
||||||
|
user: user,
|
||||||
|
},
|
||||||
|
transformer: superjson,
|
||||||
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {
|
||||||
|
trpcState: helpers.dehydrate(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export async function getServerSideProps(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await helpers.project.all.prefetch();
|
await helpers.project.all.prefetch();
|
||||||
|
|
||||||
const auth = await helpers.auth.get.fetch();
|
const auth = await helpers.auth.get.fetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export async function getServerSideProps(
|
|||||||
},
|
},
|
||||||
transformer: superjson,
|
transformer: superjson,
|
||||||
});
|
});
|
||||||
|
await helpers.auth.get.prefetch();
|
||||||
await helpers.settings.isCloud.prefetch();
|
await helpers.settings.isCloud.prefetch();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Login2FA } from "@/components/auth/login-2fa";
|
import { Login2FA } from "@/components/auth/login-2fa";
|
||||||
import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
|
import { OnboardingLayout } from "@/components/layouts/onboarding-layout";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Logo } from "@/components/shared/logo";
|
import { Logo } from "@/components/shared/logo";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -63,7 +64,8 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
is2FAEnabled: false,
|
is2FAEnabled: false,
|
||||||
authId: "",
|
authId: "",
|
||||||
});
|
});
|
||||||
const { mutateAsync, isLoading } = api.auth.login.useMutation();
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
|
api.auth.login.useMutation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const form = useForm<Login>({
|
const form = useForm<Login>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -115,6 +117,12 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
<Card className="mx-auto w-full max-w-lg bg-transparent ">
|
<Card className="mx-auto w-full max-w-lg bg-transparent ">
|
||||||
<div className="p-3.5" />
|
<div className="p-3.5" />
|
||||||
|
{isError && (
|
||||||
|
<AlertBlock type="error" className="mx-4 my-2">
|
||||||
|
<span>{error?.message}</span>
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{!temp.is2FAEnabled ? (
|
{!temp.is2FAEnabled ? (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
@@ -170,12 +178,14 @@ export default function Home({ IS_CLOUD }: Props) {
|
|||||||
|
|
||||||
<div className="flex flex-row justify-between flex-wrap">
|
<div className="flex flex-row justify-between flex-wrap">
|
||||||
<div className="mt-4 text-center text-sm flex flex-row justify-center gap-2">
|
<div className="mt-4 text-center text-sm flex flex-row justify-center gap-2">
|
||||||
|
{IS_CLOUD && (
|
||||||
<Link
|
<Link
|
||||||
className="hover:underline text-muted-foreground"
|
className="hover:underline text-muted-foreground"
|
||||||
href="/register"
|
href="/register"
|
||||||
>
|
>
|
||||||
Create an account
|
Create an account
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 text-sm flex flex-row justify-center gap-2">
|
<div className="mt-4 text-sm flex flex-row justify-center gap-2">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { getUserByToken } from "@dokploy/server";
|
import { IS_CLOUD, getUserByToken } from "@dokploy/server";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle } from "lucide-react";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
@@ -69,9 +69,10 @@ type Register = z.infer<typeof registerSchema>;
|
|||||||
interface Props {
|
interface Props {
|
||||||
token: string;
|
token: string;
|
||||||
invitation: Awaited<ReturnType<typeof getUserByToken>>;
|
invitation: Awaited<ReturnType<typeof getUserByToken>>;
|
||||||
|
isCloud: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Invitation = ({ token, invitation }: Props) => {
|
const Invitation = ({ token, invitation, isCloud }: Props) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { data } = api.admin.getUserByToken.useQuery(
|
const { data } = api.admin.getUserByToken.useQuery(
|
||||||
{
|
{
|
||||||
@@ -83,7 +84,8 @@ const Invitation = ({ token, invitation }: Props) => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutateAsync, error, isError } = api.auth.createUser.useMutation();
|
const { mutateAsync, error, isError, isSuccess } =
|
||||||
|
api.auth.createUser.useMutation();
|
||||||
|
|
||||||
const form = useForm<Register>({
|
const form = useForm<Register>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -112,7 +114,9 @@ const Invitation = ({ token, invitation }: Props) => {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("User registration succesfuly", {
|
toast.success("User registration succesfuly", {
|
||||||
duration: 2000,
|
description:
|
||||||
|
"Please check your inbox or spam folder to confirm your account.",
|
||||||
|
duration: 100000,
|
||||||
});
|
});
|
||||||
router.push("/dashboard/projects");
|
router.push("/dashboard/projects");
|
||||||
})
|
})
|
||||||
@@ -146,6 +150,7 @@ const Invitation = ({ token, invitation }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
@@ -210,6 +215,25 @@ const Invitation = ({ token, invitation }: Props) => {
|
|||||||
Register
|
Register
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4 text-sm flex flex-row justify-between gap-2 w-full">
|
||||||
|
{isCloud && (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
className="hover:underline text-muted-foreground"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
className="hover:underline text-muted-foreground"
|
||||||
|
href="/send-reset-password"
|
||||||
|
>
|
||||||
|
Lost your password?
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -250,6 +274,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
isCloud: IS_CLOUD,
|
||||||
token: token,
|
token: token,
|
||||||
invitation: invitation,
|
invitation: invitation,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Logo } from "@/components/shared/logo";
|
import { Logo } from "@/components/shared/logo";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -72,7 +73,8 @@ interface Props {
|
|||||||
|
|
||||||
const Register = ({ isCloud }: Props) => {
|
const Register = ({ isCloud }: Props) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutateAsync, error, isError } = api.auth.createAdmin.useMutation();
|
const { mutateAsync, error, isError, data } =
|
||||||
|
api.auth.createAdmin.useMutation();
|
||||||
|
|
||||||
const form = useForm<Register>({
|
const form = useForm<Register>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -89,14 +91,16 @@ const Register = ({ isCloud }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async (values: Register) => {
|
const onSubmit = async (values: Register) => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
email: values.email,
|
email: values.email.toLowerCase(),
|
||||||
password: values.password,
|
password: values.password,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("User registration succesfuly", {
|
toast.success("User registration succesfuly", {
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
});
|
});
|
||||||
|
if (!isCloud) {
|
||||||
router.push("/");
|
router.push("/");
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => e);
|
.catch((e) => e);
|
||||||
};
|
};
|
||||||
@@ -130,6 +134,14 @@ const Register = ({ isCloud }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{data && (
|
||||||
|
<AlertBlock type="success" className="mx-4 my-2">
|
||||||
|
<span>
|
||||||
|
Registration succesfuly, Please check your inbox or spam
|
||||||
|
folder to confirm your account.
|
||||||
|
</span>
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { db } from "@/server/db";
|
||||||
import { auth } from "@/server/db/schema";
|
import { auth } from "@/server/db/schema";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { IS_CLOUD } from "@dokploy/server";
|
import { IS_CLOUD } from "@dokploy/server";
|
||||||
@@ -194,7 +195,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const authR = await db?.query.auth.findFirst({
|
const authR = await db.query.auth.findFirst({
|
||||||
where: eq(auth.resetPasswordToken, token),
|
where: eq(auth.resetPasswordToken, token),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const Home: NextPage = () => {
|
|||||||
const [spec, setSpec] = useState({});
|
const [spec, setSpec] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Esto solo se ejecutará en el cliente
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const protocolAndHost = `${window.location.protocol}//${window.location.host}/api`;
|
const protocolAndHost = `${window.location.protocol}//${window.location.host}/api`;
|
||||||
const newSpec = {
|
const newSpec = {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
@@ -6,7 +6,6 @@ import {
|
|||||||
apiRemoveUser,
|
apiRemoveUser,
|
||||||
users,
|
users,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createInvitation,
|
createInvitation,
|
||||||
findAdminById,
|
findAdminById,
|
||||||
|
|||||||
@@ -19,11 +19,8 @@ import {
|
|||||||
apiUpdateApplication,
|
apiUpdateApplication,
|
||||||
applications,
|
applications,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import {
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
type DeploymentJob,
|
import { cleanQueuesByApplication, myQueue } from "@/server/queues/queueSetup";
|
||||||
cleanQueuesByApplication,
|
|
||||||
} from "@/server/queues/deployments-queue";
|
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
|
||||||
import { deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import { uploadFileSchema } from "@/utils/schema";
|
import { uploadFileSchema } from "@/utils/schema";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import {
|
|||||||
apiFindOneAuth,
|
apiFindOneAuth,
|
||||||
apiLogin,
|
apiLogin,
|
||||||
apiUpdateAuth,
|
apiUpdateAuth,
|
||||||
apiUpdateAuthByAdmin,
|
|
||||||
apiVerify2FA,
|
apiVerify2FA,
|
||||||
apiVerifyLogin2FA,
|
apiVerifyLogin2FA,
|
||||||
auth,
|
auth,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
|
import { WEBSITE_URL } from "@/server/utils/stripe";
|
||||||
import {
|
import {
|
||||||
|
type Auth,
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
createAdmin,
|
createAdmin,
|
||||||
createUser,
|
createUser,
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
getUserByToken,
|
getUserByToken,
|
||||||
lucia,
|
lucia,
|
||||||
luciaToken,
|
luciaToken,
|
||||||
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
updateAuthById,
|
updateAuthById,
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -53,6 +55,12 @@ export const authRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newAdmin = await createAdmin(input);
|
const newAdmin = await createAdmin(input);
|
||||||
|
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
await sendDiscordNotificationWelcome(newAdmin);
|
||||||
|
await sendVerificationEmail(newAdmin.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const session = await lucia.createSession(newAdmin.id || "", {});
|
const session = await lucia.createSession(newAdmin.id || "", {});
|
||||||
ctx.res.appendHeader(
|
ctx.res.appendHeader(
|
||||||
"Set-Cookie",
|
"Set-Cookie",
|
||||||
@@ -60,7 +68,12 @@ export const authRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
// @ts-ignore
|
||||||
|
message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error to create admin"}`,
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
createUser: publicProcedure
|
createUser: publicProcedure
|
||||||
@@ -74,7 +87,13 @@ export const authRouter = createTRPCRouter({
|
|||||||
message: "Invalid token",
|
message: "Invalid token",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUser = await createUser(input);
|
const newUser = await createUser(input);
|
||||||
|
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
await sendVerificationEmail(token.authId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const session = await lucia.createSession(newUser?.authId || "", {});
|
const session = await lucia.createSession(newUser?.authId || "", {});
|
||||||
ctx.res.appendHeader(
|
ctx.res.appendHeader(
|
||||||
"Set-Cookie",
|
"Set-Cookie",
|
||||||
@@ -106,6 +125,15 @@ export const authRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auth?.confirmationToken && IS_CLOUD) {
|
||||||
|
await sendVerificationEmail(auth.id);
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message:
|
||||||
|
"Email not confirmed, we have sent you a confirmation email please check your inbox.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (auth?.is2FAEnabled) {
|
if (auth?.is2FAEnabled) {
|
||||||
return {
|
return {
|
||||||
is2FAEnabled: true,
|
is2FAEnabled: true,
|
||||||
@@ -126,7 +154,7 @@ export const authRouter = createTRPCRouter({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "Credentials do not match",
|
message: `Error: ${error instanceof Error ? error.message : "Error to login"}`,
|
||||||
cause: error,
|
cause: error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -151,7 +179,7 @@ export const authRouter = createTRPCRouter({
|
|||||||
.input(apiUpdateAuth)
|
.input(apiUpdateAuth)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const auth = await updateAuthById(ctx.user.authId, {
|
const auth = await updateAuthById(ctx.user.authId, {
|
||||||
...(input.email && { email: input.email }),
|
...(input.email && { email: input.email.toLowerCase() }),
|
||||||
...(input.password && {
|
...(input.password && {
|
||||||
password: bcrypt.hashSync(input.password, 10),
|
password: bcrypt.hashSync(input.password, 10),
|
||||||
}),
|
}),
|
||||||
@@ -183,19 +211,6 @@ export const authRouter = createTRPCRouter({
|
|||||||
return auth;
|
return auth;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateByAdmin: protectedProcedure
|
|
||||||
.input(apiUpdateAuthByAdmin)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
const auth = await updateAuthById(input.id, {
|
|
||||||
...(input.email && { email: input.email }),
|
|
||||||
...(input.password && {
|
|
||||||
password: bcrypt.hashSync(input.password, 10),
|
|
||||||
}),
|
|
||||||
...(input.image && { image: input.image }),
|
|
||||||
});
|
|
||||||
|
|
||||||
return auth;
|
|
||||||
}),
|
|
||||||
generate2FASecret: protectedProcedure.query(async ({ ctx }) => {
|
generate2FASecret: protectedProcedure.query(async ({ ctx }) => {
|
||||||
return await generate2FASecret(ctx.user.authId);
|
return await generate2FASecret(ctx.user.authId);
|
||||||
}),
|
}),
|
||||||
@@ -236,9 +251,6 @@ export const authRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
return auth;
|
return auth;
|
||||||
}),
|
}),
|
||||||
verifyToken: protectedProcedure.mutation(async () => {
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
sendResetPasswordEmail: publicProcedure
|
sendResetPasswordEmail: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -270,7 +282,7 @@ export const authRouter = createTRPCRouter({
|
|||||||
).toISOString(),
|
).toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const email = await sendEmailNotification(
|
await sendEmailNotification(
|
||||||
{
|
{
|
||||||
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
||||||
toAddresses: [authR.email],
|
toAddresses: [authR.email],
|
||||||
@@ -283,7 +295,7 @@ export const authRouter = createTRPCRouter({
|
|||||||
`
|
`
|
||||||
Reset your password by clicking the link below:
|
Reset your password by clicking the link below:
|
||||||
The link will expire in 24 hours.
|
The link will expire in 24 hours.
|
||||||
<a href="http://localhost:3000/reset-password?token=${token}">
|
<a href="${WEBSITE_URL}/reset-password?token=${token}">
|
||||||
Reset Password
|
Reset Password
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -334,6 +346,113 @@ export const authRouter = createTRPCRouter({
|
|||||||
password: bcrypt.hashSync(input.password, 10),
|
password: bcrypt.hashSync(input.password, 10),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
confirmEmail: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
confirmationToken: z.string().min(1),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
if (!IS_CLOUD) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Functionality not available in cloud version",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const authR = await db.query.auth.findFirst({
|
||||||
|
where: eq(auth.confirmationToken, input.confirmationToken),
|
||||||
|
});
|
||||||
|
if (!authR || authR.confirmationExpiresAt === null) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Token not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (authR.confirmationToken !== input.confirmationToken) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Confirmation Token not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExpired = isBefore(
|
||||||
|
new Date(authR.confirmationExpiresAt),
|
||||||
|
new Date(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Confirmation Token expired",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
1;
|
||||||
|
await updateAuthById(authR.id, {
|
||||||
|
confirmationToken: null,
|
||||||
|
confirmationExpiresAt: null,
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const sendVerificationEmail = async (authId: string) => {
|
||||||
|
const token = nanoid();
|
||||||
|
const result = await updateAuthById(authId, {
|
||||||
|
confirmationToken: token,
|
||||||
|
confirmationExpiresAt: new Date(
|
||||||
|
new Date().getTime() + 24 * 60 * 60 * 1000,
|
||||||
|
).toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "User not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await sendEmailNotification(
|
||||||
|
{
|
||||||
|
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
||||||
|
toAddresses: [result?.email],
|
||||||
|
smtpServer: process.env.SMTP_SERVER || "",
|
||||||
|
smtpPort: Number(process.env.SMTP_PORT),
|
||||||
|
username: process.env.SMTP_USERNAME || "",
|
||||||
|
password: process.env.SMTP_PASSWORD || "",
|
||||||
|
},
|
||||||
|
"Confirm your email | Dokploy",
|
||||||
|
`
|
||||||
|
Welcome to Dokploy!
|
||||||
|
Please confirm your email by clicking the link below:
|
||||||
|
<a href="${WEBSITE_URL}/confirm-email?token=${result?.confirmationToken}">
|
||||||
|
Confirm Email
|
||||||
|
</a>
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
|
||||||
|
await sendDiscordNotification(
|
||||||
|
{
|
||||||
|
webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "✅ New User Registered",
|
||||||
|
color: 0x00ff00,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Email",
|
||||||
|
value: newAdmin.email,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: newAdmin.createdAt,
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy User Registration Notification",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ import {
|
|||||||
apiUpdateCompose,
|
apiUpdateCompose,
|
||||||
compose,
|
compose,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import {
|
import { cleanQueuesByCompose, myQueue } from "@/server/queues/queueSetup";
|
||||||
type DeploymentJob,
|
|
||||||
cleanQueuesByCompose,
|
|
||||||
} from "@/server/queues/deployments-queue";
|
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
|
||||||
import { templates } from "@/templates/templates";
|
import { templates } from "@/templates/templates";
|
||||||
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
|
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +24,7 @@ import _ from "lodash";
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
|
||||||
|
import type { DeploymentJob } from "@/server/queues/queue-types";
|
||||||
import { deploy } from "@/server/utils/deploy";
|
import { deploy } from "@/server/utils/deploy";
|
||||||
import {
|
import {
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
@@ -41,7 +38,6 @@ import {
|
|||||||
createComposeByTemplate,
|
createComposeByTemplate,
|
||||||
createDomain,
|
createDomain,
|
||||||
createMount,
|
createMount,
|
||||||
findAdmin,
|
|
||||||
findAdminById,
|
findAdminById,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
findDomainsByComposeId,
|
findDomainsByComposeId,
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ import { and, desc, eq, sql } from "drizzle-orm";
|
|||||||
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
import type { AnyPgColumn } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
IS_CLOUD,
|
||||||
addNewProject,
|
addNewProject,
|
||||||
checkProjectAccess,
|
checkProjectAccess,
|
||||||
createProject,
|
createProject,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
|
findAdminById,
|
||||||
findProjectById,
|
findProjectById,
|
||||||
findUserByAuthId,
|
findUserByAuthId,
|
||||||
updateProjectById,
|
updateProjectById,
|
||||||
@@ -38,6 +40,15 @@ export const projectRouter = createTRPCRouter({
|
|||||||
await checkProjectAccess(ctx.user.authId, "create");
|
await checkProjectAccess(ctx.user.authId, "create");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const admin = await findAdminById(ctx.user.adminId);
|
||||||
|
|
||||||
|
if (admin.serversQuantity === 0 && IS_CLOUD) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "No servers available, Please subscribe to a plan",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const project = await createProject(input, ctx.user.adminId);
|
const project = await createProject(input, ctx.user.adminId);
|
||||||
if (ctx.user.rol === "user") {
|
if (ctx.user.rol === "user") {
|
||||||
await addNewProject(ctx.user.authId, project.projectId);
|
await addNewProject(ctx.user.authId, project.projectId);
|
||||||
@@ -47,7 +58,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "Error to create the project",
|
message: `Error to create the project: ${error instanceof Error ? error.message : error}`,
|
||||||
cause: error,
|
cause: error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,9 +139,9 @@ export const projectRouter = createTRPCRouter({
|
|||||||
)})`,
|
)})`,
|
||||||
with: {
|
with: {
|
||||||
applications: {
|
applications: {
|
||||||
where: buildServiceFilter(
|
where: and(
|
||||||
applications.applicationId,
|
buildServiceFilter(applications.applicationId, accesedServices),
|
||||||
accesedServices,
|
eq(projects.adminId, ctx.user.adminId),
|
||||||
),
|
),
|
||||||
with: { domains: true },
|
with: { domains: true },
|
||||||
},
|
},
|
||||||
@@ -230,5 +241,5 @@ function buildServiceFilter(fieldName: AnyPgColumn, accesedServices: string[]) {
|
|||||||
accesedServices.map((serviceId) => sql`${serviceId}`),
|
accesedServices.map((serviceId) => sql`${serviceId}`),
|
||||||
sql`, `,
|
sql`, `,
|
||||||
)})`
|
)})`
|
||||||
: sql`1 = 0`; // Always false condition
|
: sql`1 = 0`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
export * from "@dokploy/server/dist/db/schema";
|
export * from "@dokploy/server/db/schema";
|
||||||
|
|||||||
@@ -11,29 +11,8 @@ import {
|
|||||||
updateCompose,
|
updateCompose,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { type Job, Worker } from "bullmq";
|
import { type Job, Worker } from "bullmq";
|
||||||
import { myQueue, redisConfig } from "./queueSetup";
|
import type { DeploymentJob } from "./queue-types";
|
||||||
|
import { redisConfig } from "./redis-connection";
|
||||||
type DeployJob =
|
|
||||||
| {
|
|
||||||
applicationId: string;
|
|
||||||
titleLog: string;
|
|
||||||
descriptionLog: string;
|
|
||||||
server?: boolean;
|
|
||||||
type: "deploy" | "redeploy";
|
|
||||||
applicationType: "application";
|
|
||||||
serverId?: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
composeId: string;
|
|
||||||
titleLog: string;
|
|
||||||
descriptionLog: string;
|
|
||||||
server?: boolean;
|
|
||||||
type: "deploy" | "redeploy";
|
|
||||||
applicationType: "compose";
|
|
||||||
serverId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DeploymentJob = DeployJob;
|
|
||||||
|
|
||||||
export const deploymentWorker = new Worker(
|
export const deploymentWorker = new Worker(
|
||||||
"deployments",
|
"deployments",
|
||||||
@@ -114,25 +93,3 @@ export const deploymentWorker = new Worker(
|
|||||||
connection: redisConfig,
|
connection: redisConfig,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const cleanQueuesByApplication = async (applicationId: string) => {
|
|
||||||
const jobs = await myQueue.getJobs(["waiting", "delayed"]);
|
|
||||||
|
|
||||||
for (const job of jobs) {
|
|
||||||
if (job?.data?.applicationId === applicationId) {
|
|
||||||
await job.remove();
|
|
||||||
console.log(`Removed job ${job.id} for application ${applicationId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const cleanQueuesByCompose = async (composeId: string) => {
|
|
||||||
const jobs = await myQueue.getJobs(["waiting", "delayed"]);
|
|
||||||
|
|
||||||
for (const job of jobs) {
|
|
||||||
if (job?.data?.composeId === composeId) {
|
|
||||||
await job.remove();
|
|
||||||
console.log(`Removed job ${job.id} for compose ${composeId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
21
apps/dokploy/server/queues/queue-types.ts
Normal file
21
apps/dokploy/server/queues/queue-types.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
type DeployJob =
|
||||||
|
| {
|
||||||
|
applicationId: string;
|
||||||
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
|
server?: boolean;
|
||||||
|
type: "deploy" | "redeploy";
|
||||||
|
applicationType: "application";
|
||||||
|
serverId?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
composeId: string;
|
||||||
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
|
server?: boolean;
|
||||||
|
type: "deploy" | "redeploy";
|
||||||
|
applicationType: "compose";
|
||||||
|
serverId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeploymentJob = DeployJob;
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import { type ConnectionOptions, Queue } from "bullmq";
|
import { Queue } from "bullmq";
|
||||||
|
import { redisConfig } from "./redis-connection";
|
||||||
|
|
||||||
export const redisConfig: ConnectionOptions = {
|
|
||||||
host: process.env.NODE_ENV === "production" ? "dokploy-redis" : "127.0.0.1",
|
|
||||||
};
|
|
||||||
const myQueue = new Queue("deployments", {
|
const myQueue = new Queue("deployments", {
|
||||||
connection: redisConfig,
|
connection: redisConfig,
|
||||||
});
|
});
|
||||||
@@ -21,4 +19,26 @@ myQueue.on("error", (error) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const cleanQueuesByApplication = async (applicationId: string) => {
|
||||||
|
const jobs = await myQueue.getJobs(["waiting", "delayed"]);
|
||||||
|
|
||||||
|
for (const job of jobs) {
|
||||||
|
if (job?.data?.applicationId === applicationId) {
|
||||||
|
await job.remove();
|
||||||
|
console.log(`Removed job ${job.id} for application ${applicationId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cleanQueuesByCompose = async (composeId: string) => {
|
||||||
|
const jobs = await myQueue.getJobs(["waiting", "delayed"]);
|
||||||
|
|
||||||
|
for (const job of jobs) {
|
||||||
|
if (job?.data?.composeId === composeId) {
|
||||||
|
await job.remove();
|
||||||
|
console.log(`Removed job ${job.id} for compose ${composeId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export { myQueue };
|
export { myQueue };
|
||||||
|
|||||||
5
apps/dokploy/server/queues/redis-connection.ts
Normal file
5
apps/dokploy/server/queues/redis-connection.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { ConnectionOptions } from "bullmq";
|
||||||
|
|
||||||
|
export const redisConfig: ConnectionOptions = {
|
||||||
|
host: process.env.NODE_ENV === "production" ? "dokploy-redis" : "127.0.0.1",
|
||||||
|
};
|
||||||
@@ -19,15 +19,12 @@ import { setupDockerContainerLogsWebSocketServer } from "./wss/docker-container-
|
|||||||
import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal";
|
import { setupDockerContainerTerminalWebSocketServer } from "./wss/docker-container-terminal";
|
||||||
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";
|
import { setupDockerStatsMonitoringSocketServer } from "./wss/docker-stats";
|
||||||
import { setupDeploymentLogsWebSocketServer } from "./wss/listen-deployment";
|
import { setupDeploymentLogsWebSocketServer } from "./wss/listen-deployment";
|
||||||
import {
|
import { setupTerminalWebSocketServer } from "./wss/terminal";
|
||||||
getPublicIpWithFallback,
|
|
||||||
setupTerminalWebSocketServer,
|
|
||||||
} from "./wss/terminal";
|
|
||||||
|
|
||||||
config({ path: ".env" });
|
config({ path: ".env" });
|
||||||
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
|
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
|
||||||
const dev = process.env.NODE_ENV !== "production";
|
const dev = process.env.NODE_ENV !== "production";
|
||||||
const app = next({ dev });
|
const app = next({ dev, turbopack: dev });
|
||||||
const handle = app.getRequestHandler();
|
const handle = app.getRequestHandler();
|
||||||
void app.prepare().then(async () => {
|
void app.prepare().then(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -55,7 +52,6 @@ void app.prepare().then(async () => {
|
|||||||
await initializeRedis();
|
await initializeRedis();
|
||||||
|
|
||||||
initCronJobs();
|
initCronJobs();
|
||||||
welcomeServer();
|
|
||||||
|
|
||||||
// Timeout to wait for the database to be ready
|
// Timeout to wait for the database to be ready
|
||||||
await new Promise((resolve) => setTimeout(resolve, 7000));
|
await new Promise((resolve) => setTimeout(resolve, 7000));
|
||||||
@@ -78,18 +74,3 @@ void app.prepare().then(async () => {
|
|||||||
console.error("Main Server Error", e);
|
console.error("Main Server Error", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function welcomeServer() {
|
|
||||||
const ip = await getPublicIpWithFallback();
|
|
||||||
console.log(
|
|
||||||
[
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"Dokploy server is up and running!",
|
|
||||||
"Please wait for 15 seconds before opening the browser.",
|
|
||||||
` http://${ip}:${PORT}`,
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { findServerById } from "@dokploy/server";
|
import { findServerById } from "@dokploy/server";
|
||||||
import type { DeploymentJob } from "../queues/deployments-queue";
|
import type { DeploymentJob } from "../queues/queue-types";
|
||||||
|
|
||||||
export const deploy = async (jobData: DeploymentJob) => {
|
export const deploy = async (jobData: DeploymentJob) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const WEBSITE_URL =
|
export const WEBSITE_URL =
|
||||||
process.env.NODE_ENV === "development"
|
process.env.NODE_ENV === "development"
|
||||||
? "http://localhost:3000"
|
? "http://localhost:3000"
|
||||||
: "https://app.dokploy.com";
|
: process.env.SITE_URL;
|
||||||
|
|
||||||
const BASE_PRICE_MONTHLY_ID = process.env.BASE_PRICE_MONTHLY_ID || ""; // $4.00
|
const BASE_PRICE_MONTHLY_ID = process.env.BASE_PRICE_MONTHLY_ID || ""; // $4.00
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import {
|
|||||||
createDefaultServerTraefikConfig,
|
createDefaultServerTraefikConfig,
|
||||||
createDefaultTraefikConfig,
|
createDefaultTraefikConfig,
|
||||||
initializeTraefik,
|
initializeTraefik,
|
||||||
} from "@dokploy/server/dist/setup/traefik-setup";
|
} from "@dokploy/server/setup/traefik-setup";
|
||||||
|
|
||||||
import { setupDirectories } from "@dokploy/server/dist/setup/config-paths";
|
import { setupDirectories } from "@dokploy/server/setup/config-paths";
|
||||||
import { initializePostgres } from "@dokploy/server/dist/setup/postgres-setup";
|
import { initializePostgres } from "@dokploy/server/setup/postgres-setup";
|
||||||
import { initializeRedis } from "@dokploy/server/dist/setup/redis-setup";
|
import { initializeRedis } from "@dokploy/server/setup/redis-setup";
|
||||||
import {
|
import {
|
||||||
initializeNetwork,
|
initializeNetwork,
|
||||||
initializeSwarm,
|
initializeSwarm,
|
||||||
} from "@dokploy/server/dist/setup/setup";
|
} from "@dokploy/server/setup/setup";
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
setupDirectories();
|
setupDirectories();
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
/* Path Aliases */
|
/* Path Aliases */
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"],
|
||||||
|
"@dokploy/server/*": ["../../packages/server/src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"],
|
||||||
|
"@dokploy/server/*": ["../../packages/server/src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "./server/**/*"]
|
"include": ["next-env.d.ts", "./server/**/*"]
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Inter, Lexend } from "next/font/google";
|
import { Inter, Lexend } from "next/font/google";
|
||||||
import "@/styles/tailwind.css";
|
import "@/styles/tailwind.css";
|
||||||
import GoogleAnalytics from "@/components/analitycs/google";
|
|
||||||
|
|
||||||
import { NextIntlClientProvider } from "next-intl";
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
import { getMessages } from "next-intl/server";
|
import { getMessages } from "next-intl/server";
|
||||||
|
|
||||||
@@ -88,7 +86,14 @@ export default async function RootLayout({
|
|||||||
lang={locale}
|
lang={locale}
|
||||||
className={clsx("h-full scroll-smooth", inter.variable, lexend.variable)}
|
className={clsx("h-full scroll-smooth", inter.variable, lexend.variable)}
|
||||||
>
|
>
|
||||||
<GoogleAnalytics />
|
<head>
|
||||||
|
<script
|
||||||
|
defer
|
||||||
|
src="https://umami.dokploy.com/script.js"
|
||||||
|
data-website-id="7d1422e4-3776-4870-8145-7d7b2075d470"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
{/* <GoogleAnalytics /> */}
|
||||||
<body className="flex h-full flex-col">
|
<body className="flex h-full flex-col">
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<Header />
|
<Header />
|
||||||
|
|||||||
@@ -1,11 +1,40 @@
|
|||||||
import Link from "next/link";
|
"use client";
|
||||||
|
|
||||||
import { useTranslations } from "next-intl";
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
import { useLocale, useTranslations } from "next-intl";
|
||||||
|
import type { SVGProps } from "react";
|
||||||
import { Container } from "./Container";
|
import { Container } from "./Container";
|
||||||
import { NavLink } from "./NavLink";
|
import { NavLink } from "./NavLink";
|
||||||
import { Logo } from "./shared/Logo";
|
import { Logo } from "./shared/Logo";
|
||||||
|
import { buttonVariants } from "./ui/button";
|
||||||
|
|
||||||
|
const I18nIcon = (props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
fill="currentColor"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth={0}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="none"
|
||||||
|
d="m478.33 433.6-90-218a22 22 0 0 0-40.67 0l-90 218a22 22 0 1 0 40.67 16.79L316.66 406h102.67l18.33 44.39A22 22 0 0 0 458 464a22 22 0 0 0 20.32-30.4zM334.83 362 368 281.65 401.17 362zm-66.99-19.08a22 22 0 0 0-4.89-30.7c-.2-.15-15-11.13-36.49-34.73 39.65-53.68 62.11-114.75 71.27-143.49H330a22 22 0 0 0 0-44H214V70a22 22 0 0 0-44 0v20H54a22 22 0 0 0 0 44h197.25c-9.52 26.95-27.05 69.5-53.79 108.36-31.41-41.68-43.08-68.65-43.17-68.87a22 22 0 0 0-40.58 17c.58 1.38 14.55 34.23 52.86 83.93.92 1.19 1.83 2.35 2.74 3.51-39.24 44.35-77.74 71.86-93.85 80.74a22 22 0 1 0 21.07 38.63c2.16-1.18 48.6-26.89 101.63-85.59 22.52 24.08 38 35.44 38.93 36.1a22 22 0 0 0 30.75-4.9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
|
const router = useRouter();
|
||||||
|
const locale = useLocale();
|
||||||
const t = useTranslations("HomePage");
|
const t = useTranslations("HomePage");
|
||||||
const linkT = useTranslations("Link");
|
const linkT = useTranslations("Link");
|
||||||
|
|
||||||
@@ -31,7 +60,7 @@ export function Footer() {
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
|
<div className="flex flex-col items-center border-t border-slate-400/10 py-10 sm:flex-row-reverse sm:justify-between">
|
||||||
<div className="flex gap-x-6">
|
<div className="flex gap-x-6 items-center">
|
||||||
<Link
|
<Link
|
||||||
href="https://x.com/getdokploy"
|
href="https://x.com/getdokploy"
|
||||||
className="group"
|
className="group"
|
||||||
@@ -56,6 +85,30 @@ export function Footer() {
|
|||||||
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
|
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Select
|
||||||
|
onValueChange={(locale) => {
|
||||||
|
router.replace("/", {
|
||||||
|
locale: locale as "en" | "zh-Hans",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
value={locale}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className={buttonVariants({
|
||||||
|
variant: "outline",
|
||||||
|
className:
|
||||||
|
" flex items-center gap-2 !rounded-full visited:outline-none focus-within:outline-none focus:outline-none",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<I18nIcon width={20} height={20} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="en">{t("navigation.i18nEn")}</SelectItem>
|
||||||
|
<SelectItem value="zh-Hans">
|
||||||
|
{t("navigation.i18nZh-Hans")}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-6 text-sm text-muted-foreground sm:mt-0">
|
<p className="mt-6 text-sm text-muted-foreground sm:mt-0">
|
||||||
{t("footer.copyright", {
|
{t("footer.copyright", {
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { Link } from "@/i18n/routing";
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Popover, Transition } from "@headlessui/react";
|
import { Popover, Transition } from "@headlessui/react";
|
||||||
import { HeartIcon } from "lucide-react";
|
import { HeartIcon } from "lucide-react";
|
||||||
import { useLocale, useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Fragment, type JSX, type SVGProps } from "react";
|
import { Fragment, type JSX, type SVGProps } from "react";
|
||||||
import { Container } from "./Container";
|
import { Container } from "./Container";
|
||||||
import { NavLink } from "./NavLink";
|
import { NavLink } from "./NavLink";
|
||||||
@@ -125,10 +119,7 @@ function MobileNavigation() {
|
|||||||
as="div"
|
as="div"
|
||||||
className="absolute inset-x-0 top-full mt-4 flex origin-top flex-col rounded-2xl border border-border bg-background p-4 text-lg tracking-tight text-primary shadow-xl ring-1 ring-border/5"
|
className="absolute inset-x-0 top-full mt-4 flex origin-top flex-col rounded-2xl border border-border bg-background p-4 text-lg tracking-tight text-primary shadow-xl ring-1 ring-border/5"
|
||||||
>
|
>
|
||||||
<MobileNavLink href="/#features">
|
<MobileNavLink href="/pricing">Pricing</MobileNavLink>
|
||||||
{t("navigation.features")}
|
|
||||||
</MobileNavLink>
|
|
||||||
{/* <MobileNavLink href="/#testimonials">Testimonials</MobileNavLink> */}
|
|
||||||
<MobileNavLink href="/#faqs">{t("navigation.faqs")}</MobileNavLink>
|
<MobileNavLink href="/#faqs">{t("navigation.faqs")}</MobileNavLink>
|
||||||
<MobileNavLink href={linkT("docs.intro")} target="_blank">
|
<MobileNavLink href={linkT("docs.intro")} target="_blank">
|
||||||
{t("navigation.docs")}
|
{t("navigation.docs")}
|
||||||
@@ -141,8 +132,6 @@ function MobileNavigation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const router = useRouter();
|
|
||||||
const locale = useLocale();
|
|
||||||
const t = useTranslations("HomePage");
|
const t = useTranslations("HomePage");
|
||||||
const linkT = useTranslations("Link");
|
const linkT = useTranslations("Link");
|
||||||
|
|
||||||
@@ -155,8 +144,7 @@ export function Header() {
|
|||||||
<Logo className="h-10 w-auto" />
|
<Logo className="h-10 w-auto" />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="hidden md:flex md:gap-x-6">
|
<div className="hidden md:flex md:gap-x-6">
|
||||||
<NavLink href="/#features">{t("navigation.features")}</NavLink>
|
<NavLink href="/pricing">{t("navigation.pricing")}</NavLink>
|
||||||
{/* <NavLink href="/#testimonials">Testimonials</NavLink> */}
|
|
||||||
<NavLink href="/#faqs">{t("navigation.faqs")}</NavLink>
|
<NavLink href="/#faqs">{t("navigation.faqs")}</NavLink>
|
||||||
<NavLink href={linkT("docs.intro")} target="_blank">
|
<NavLink href={linkT("docs.intro")} target="_blank">
|
||||||
{t("navigation.docs")}
|
{t("navigation.docs")}
|
||||||
@@ -164,31 +152,6 @@ export function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-x-2 md:gap-x-5">
|
<div className="flex items-center gap-x-2 md:gap-x-5">
|
||||||
<Select
|
|
||||||
onValueChange={(locale) => {
|
|
||||||
router.replace("/", {
|
|
||||||
locale: locale as "en" | "zh-Hans",
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
value={locale}
|
|
||||||
>
|
|
||||||
<SelectTrigger
|
|
||||||
className={buttonVariants({
|
|
||||||
variant: "outline",
|
|
||||||
className:
|
|
||||||
" flex items-center gap-2 !rounded-full visited:outline-none focus-within:outline-none focus:outline-none",
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<I18nIcon width={20} height={20} />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="en">{t("navigation.i18nEn")}</SelectItem>
|
|
||||||
<SelectItem value="zh-Hans">
|
|
||||||
{t("navigation.i18nZh-Hans")}
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
className={buttonVariants({
|
className={buttonVariants({
|
||||||
variant: "outline",
|
variant: "outline",
|
||||||
@@ -202,25 +165,14 @@ export function Header() {
|
|||||||
</span>
|
</span>
|
||||||
<HeartIcon className="animate-heartbeat size-4 fill-red-600 text-red-500 " />
|
<HeartIcon className="animate-heartbeat size-4 fill-red-600 text-red-500 " />
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button className="rounded-xl" asChild>
|
||||||
className="rounded-full bg-[#5965F2] hover:bg-[#4A55E0]"
|
|
||||||
asChild
|
|
||||||
>
|
|
||||||
<Link
|
<Link
|
||||||
href="https://discord.gg/2tBnJ3jDJc"
|
href="https://app.dokploy.com"
|
||||||
aria-label="Dokploy on GitHub"
|
aria-label="Dokploy on GitHub"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="flex flex-row items-center gap-2 text-white"
|
// className="flex flex-row items-center gap-2 text-white"
|
||||||
>
|
>
|
||||||
<svg
|
{t("navigation.dashboard")}
|
||||||
role="img"
|
|
||||||
className="h-6 w-6 fill-white"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
|
||||||
</svg>
|
|
||||||
{t("navigation.discord")}
|
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<div className="-mr-1 md:hidden">
|
<div className="-mr-1 md:hidden">
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Check, Copy } from "lucide-react";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { ArrowRight, ArrowRightIcon, Check, Copy } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Container } from "./Container";
|
import { Container } from "./Container";
|
||||||
|
import AnimatedShinyText from "./ui/animated-shiny-text";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
|
import { HoverBorderGradient } from "./ui/hover-border-gradient";
|
||||||
|
|
||||||
const ProductHunt = () => {
|
const ProductHunt = () => {
|
||||||
return (
|
return (
|
||||||
@@ -69,6 +72,21 @@ export function Hero() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
<Link href="/pricing" className="relative z-10 mb-4 inline-block">
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"group rounded-full border border-black/5 bg-neutral-100 text-sm font-medium text-white transition-all ease-in hover:cursor-pointer hover:bg-neutral-200 dark:border-white/5 dark:bg-neutral-900 dark:hover:bg-neutral-800",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AnimatedShinyText className="inline-flex items-center justify-center px-4 py-1 text-neutral-800 transition ease-out hover:text-neutral-900 hover:duration-300 hover:dark:text-neutral-400">
|
||||||
|
<span>🚀 {t("hero.cloud")} </span>
|
||||||
|
<ArrowRightIcon className="ml-1 size-3 transition-transform duration-300 ease-in-out group-hover:translate-x-0.5" />
|
||||||
|
</AnimatedShinyText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<h1 className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl">
|
<h1 className="mx-auto max-w-4xl font-display text-5xl font-medium tracking-tight text-muted-foreground sm:text-7xl">
|
||||||
{t("hero.deploy")}{" "}
|
{t("hero.deploy")}{" "}
|
||||||
<span className="relative whitespace-nowrap text-primary">
|
<span className="relative whitespace-nowrap text-primary">
|
||||||
@@ -125,7 +143,9 @@ export function Hero() {
|
|||||||
Discord
|
Discord
|
||||||
</Link>
|
</Link>
|
||||||
</Button> */}
|
</Button> */}
|
||||||
<Button className="rounded-xl" asChild>
|
</div>
|
||||||
|
<div className="mx-auto flex w-full max-w-sm flex-wrap items-center justify-center gap-3 md:flex-nowrap">
|
||||||
|
<Button className="w-full rounded-xl" asChild>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/dokploy/dokploy"
|
href="https://github.com/dokploy/dokploy"
|
||||||
aria-label="Dokploy on GitHub"
|
aria-label="Dokploy on GitHub"
|
||||||
@@ -138,6 +158,27 @@ export function Hero() {
|
|||||||
Github
|
Github
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="w-full rounded-xl bg-[#5965F2] hover:bg-[#4A55E0]"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href="https://discord.gg/2tBnJ3jDJc"
|
||||||
|
aria-label="Dokploy on GitHub"
|
||||||
|
target="_blank"
|
||||||
|
className="flex flex-row items-center gap-2 text-white"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
className="h-6 w-6 fill-white"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||||
|
</svg>
|
||||||
|
{t("navigation.discord")}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-16 flex flex-row justify-center gap-x-8 rounded-lg sm:gap-x-0 sm:gap-y-10 xl:gap-x-12 xl:gap-y-0">
|
<div className="mt-16 flex flex-row justify-center gap-x-8 rounded-lg sm:gap-x-0 sm:gap-y-10 xl:gap-x-12 xl:gap-y-0">
|
||||||
@@ -182,22 +223,20 @@ export function Hero() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ShowSponsors = () => {
|
export const ShowSponsors = () => {
|
||||||
|
const t = useTranslations("HomePage");
|
||||||
return (
|
return (
|
||||||
<div className="mt-20 flex flex-col justify-center gap-y-10">
|
<div className="mt-20 flex flex-col justify-center gap-y-10">
|
||||||
<div className="flex flex-col gap-4 justify-start">
|
<div className="flex flex-col justify-start gap-4">
|
||||||
<h1 className="mx-auto max-w-2xl font-display text-3xl font-medium tracking-tight text-primary sm:text-5xl">
|
<h1 className="mx-auto max-w-2xl font-display text-3xl font-medium tracking-tight text-primary sm:text-5xl">
|
||||||
Sponsors
|
{t("hero.sponsors.title")}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mx-auto max-w-2xl text-lg tracking-tight text-muted-foreground">
|
<p className="mx-auto max-w-2xl text-lg tracking-tight text-muted-foreground">
|
||||||
Dokploy is an open source project that is maintained by a community of
|
{t("hero.sponsors.description")}
|
||||||
volunteers. We would like to thank our sponsors for their support and
|
|
||||||
contributions to the project, which help us to continue to develop and
|
|
||||||
improve Dokploy.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 md:gap-6 justify-start">
|
<div className="flex flex-col items-center justify-start gap-4 md:gap-6">
|
||||||
<h2 className="font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl text-left">
|
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||||
Hero Sponsors 🎖
|
{t("hero.sponsors.level.hero")} 🎖
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-wrap items-center gap-4">
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
<a
|
<a
|
||||||
@@ -209,7 +248,7 @@ export const ShowSponsors = () => {
|
|||||||
<img
|
<img
|
||||||
src="https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/hostinger.jpg"
|
src="https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/hostinger.jpg"
|
||||||
alt="hostinger.com"
|
alt="hostinger.com"
|
||||||
className="rounded-xl w-[190px] h-auto"
|
className="h-auto w-[190px] rounded-xl"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
@@ -221,16 +260,16 @@ export const ShowSponsors = () => {
|
|||||||
<img
|
<img
|
||||||
src="https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/lxaer.png"
|
src="https://raw.githubusercontent.com/Dokploy/dokploy/canary/.github/sponsors/lxaer.png"
|
||||||
alt="lxaer.com"
|
alt="lxaer.com"
|
||||||
className="rounded-xl w-[70px] h-auto"
|
className="h-auto w-[70px] rounded-xl"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 md:gap-8 justify-start">
|
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||||
<h2 className="font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl text-left">
|
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||||
Premium Supporters 🥇
|
{t("hero.sponsors.level.premium")} 🥇
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
<div className="flex flex-col items-center justify-start gap-4 md:gap-6">
|
||||||
<a
|
<a
|
||||||
href="https://supafort.com/?ref=dokploy"
|
href="https://supafort.com/?ref=dokploy"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -245,9 +284,9 @@ export const ShowSponsors = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 md:gap-8 justify-start">
|
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||||
<h2 className="font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl text-left">
|
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||||
Supporting Members 🥉
|
{t("hero.sponsors.level.supporting")} 🥉
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-row gap-10">
|
<div className="flex flex-row gap-10">
|
||||||
<a
|
<a
|
||||||
@@ -276,9 +315,9 @@ export const ShowSponsors = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 md:gap-8 justify-start">
|
<div className="justify-star flex flex-col items-center gap-4 md:gap-8">
|
||||||
<h2 className="font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl text-left">
|
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||||
Community Backers 🤝
|
{t("hero.sponsors.level.community")} 🤝
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-row gap-10">
|
<div className="flex flex-row gap-10">
|
||||||
<a
|
<a
|
||||||
@@ -305,11 +344,24 @@ export const ShowSponsors = () => {
|
|||||||
alt="Rivo.gg"
|
alt="Rivo.gg"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://photoquest.wedding/?ref=dokploy"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://photoquest.wedding/favicon/android-chrome-512x512.png"
|
||||||
|
className="rounded-xl"
|
||||||
|
width="60px"
|
||||||
|
alt="Rivo.gg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 md:gap-8 justify-start">
|
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||||
<h2 className="font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl text-left">
|
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||||
Organizations:
|
{t("hero.sponsors.level.organizations")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-row gap-10">
|
<div className="flex flex-row gap-10">
|
||||||
<a
|
<a
|
||||||
@@ -324,9 +376,9 @@ export const ShowSponsors = () => {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 md:gap-8 justify-start">
|
<div className="flex flex-col items-center justify-start gap-4 md:gap-8">
|
||||||
<h2 className="font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl text-left">
|
<h2 className="text-left font-display text-2xl font-medium tracking-tight text-primary sm:text-2xl">
|
||||||
Individuals:
|
{t("hero.sponsors.level.individuals")}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex flex-row gap-10">
|
<div className="flex flex-row gap-10">
|
||||||
<a
|
<a
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
40
apps/website/components/ui/animated-shiny-text.tsx
Normal file
40
apps/website/components/ui/animated-shiny-text.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import type { CSSProperties, FC, ReactNode } from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface AnimatedShinyTextProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
shimmerWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
shimmerWidth = 100,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--shiny-width": `${shimmerWidth}px`,
|
||||||
|
} as CSSProperties
|
||||||
|
}
|
||||||
|
className={cn(
|
||||||
|
"mx-auto max-w-md text-neutral-600/70 dark:text-neutral-400/70",
|
||||||
|
|
||||||
|
// Shine effect
|
||||||
|
"animate-shiny-text bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shiny-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
|
||||||
|
|
||||||
|
// Shine gradient
|
||||||
|
"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
|
||||||
|
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnimatedShinyText;
|
||||||
100
apps/website/components/ui/hover-border-gradient.tsx
Normal file
100
apps/website/components/ui/hover-border-gradient.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"use client";
|
||||||
|
import type React from "react";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
type Direction = "TOP" | "LEFT" | "BOTTOM" | "RIGHT";
|
||||||
|
|
||||||
|
export function HoverBorderGradient({
|
||||||
|
children,
|
||||||
|
containerClassName,
|
||||||
|
className,
|
||||||
|
as: Tag = "button",
|
||||||
|
duration = 1,
|
||||||
|
clockwise = true,
|
||||||
|
...props
|
||||||
|
}: React.PropsWithChildren<
|
||||||
|
{
|
||||||
|
as?: React.ElementType;
|
||||||
|
containerClassName?: string;
|
||||||
|
className?: string;
|
||||||
|
duration?: number;
|
||||||
|
clockwise?: boolean;
|
||||||
|
} & React.HTMLAttributes<HTMLElement>
|
||||||
|
>) {
|
||||||
|
const [hovered, setHovered] = useState<boolean>(false);
|
||||||
|
const [direction, setDirection] = useState<Direction>("TOP");
|
||||||
|
|
||||||
|
const rotateDirection = (currentDirection: Direction): Direction => {
|
||||||
|
const directions: Direction[] = ["TOP", "LEFT", "BOTTOM", "RIGHT"];
|
||||||
|
const currentIndex = directions.indexOf(currentDirection);
|
||||||
|
const nextIndex = clockwise
|
||||||
|
? (currentIndex - 1 + directions.length) % directions.length
|
||||||
|
: (currentIndex + 1) % directions.length;
|
||||||
|
return directions[nextIndex];
|
||||||
|
};
|
||||||
|
|
||||||
|
const movingMap: Record<Direction, string> = {
|
||||||
|
TOP: "radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||||
|
LEFT: "radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||||
|
BOTTOM:
|
||||||
|
"radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||||
|
RIGHT:
|
||||||
|
"radial-gradient(16.2% 41.199999999999996% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)",
|
||||||
|
};
|
||||||
|
|
||||||
|
const highlight =
|
||||||
|
"radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)";
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hovered) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setDirection((prevState) => rotateDirection(prevState));
|
||||||
|
}, duration * 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, [hovered]);
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
onMouseEnter={(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
setHovered(true);
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
className={cn(
|
||||||
|
"relative flex rounded-full border content-center bg-black/20 hover:bg-black/10 transition duration-500 dark:bg-white/20 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit",
|
||||||
|
containerClassName,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-auto text-white z-10 bg-black px-4 py-2 rounded-[inherit]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<motion.div
|
||||||
|
className={cn(
|
||||||
|
"flex-none inset-0 overflow-hidden absolute z-0 rounded-[inherit]",
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
filter: "blur(2px)",
|
||||||
|
position: "absolute",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
initial={{ background: movingMap[direction] }}
|
||||||
|
animate={{
|
||||||
|
background: hovered
|
||||||
|
? [movingMap[direction], highlight]
|
||||||
|
: movingMap[direction],
|
||||||
|
}}
|
||||||
|
transition={{ ease: "linear", duration: duration ?? 1 }}
|
||||||
|
/>
|
||||||
|
<div className="bg-black absolute z-1 flex-none inset-[2px] rounded-[100px]" />
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,18 +4,33 @@
|
|||||||
"features": "Features",
|
"features": "Features",
|
||||||
"faqs": "FAQ",
|
"faqs": "FAQ",
|
||||||
"docs": "Docs",
|
"docs": "Docs",
|
||||||
|
"pricing": "Pricing",
|
||||||
"support": "Support",
|
"support": "Support",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"i18nButtonPlaceholder": "Language",
|
"i18nButtonPlaceholder": "Language",
|
||||||
"i18nEn": "English",
|
"i18nEn": "English",
|
||||||
"i18nZh-Hans": "简体中文"
|
"i18nZh-Hans": "简体中文"
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
|
"cloud": "Introducing Dokploy Cloud",
|
||||||
"deploy": "Deploy",
|
"deploy": "Deploy",
|
||||||
"anywhere": "Anywhere",
|
"anywhere": "Anywhere",
|
||||||
"with": "with Total Freedom and Ease.",
|
"with": "with Total Freedom and Ease.",
|
||||||
"des": "Streamline your operations with our all-in-one platform—perfect for managing projects, data, and system health with simplicity and efficiency.",
|
"des": "Streamline your operations with our all-in-one platform—perfect for managing projects, data, and system health with simplicity and efficiency.",
|
||||||
"featuredIn": "Featured in"
|
"featuredIn": "Featured in",
|
||||||
|
"sponsors": {
|
||||||
|
"title": "Sponsors",
|
||||||
|
"description": "Dokploy is an open source project that is maintained by a community of volunteers. We would like to thank our sponsors for their support and contributions to the project, which help us to continue to develop and improve Dokploy.",
|
||||||
|
"level": {
|
||||||
|
"hero": "Hero Sponsors",
|
||||||
|
"premium": "Premium Supporters",
|
||||||
|
"supporting": "Supporting Members",
|
||||||
|
"community": "Community Backers",
|
||||||
|
"organizations": "Organizations",
|
||||||
|
"individuals": "Individuals"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"primaryFeatures": {
|
"primaryFeatures": {
|
||||||
"title": "Comprehensive Control for Your Digital Ecosystem",
|
"title": "Comprehensive Control for Your Digital Ecosystem",
|
||||||
@@ -92,5 +107,70 @@
|
|||||||
"intro": "https://docs.dokploy.com/get-started/introduction",
|
"intro": "https://docs.dokploy.com/get-started/introduction",
|
||||||
"install": "https://docs.dokploy.com/en/docs/core/get-started/introduction"
|
"install": "https://docs.dokploy.com/en/docs/core/get-started/introduction"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Pricing": {
|
||||||
|
"swirlyDoodleTitle": "Simple & Affordable,",
|
||||||
|
"restTitle": "Pricing.",
|
||||||
|
"description": "Deploy Smarter, Scale Faster – Without Breaking the Bank",
|
||||||
|
"billingCycle": {
|
||||||
|
"monthly": "Monthly",
|
||||||
|
"annual": "Annual"
|
||||||
|
},
|
||||||
|
"plan": {
|
||||||
|
"free": {
|
||||||
|
"title": "Free",
|
||||||
|
"subTitle": "Open Source",
|
||||||
|
"section": {
|
||||||
|
"title": "Dokploy Open Source",
|
||||||
|
"description": "Manager your own infrastructure installing dokploy ui in your own server."
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"f1": "Complete Flexibility: Install Dokploy UI on your own infrastructure",
|
||||||
|
"f2": "Unlimited Deployments",
|
||||||
|
"f3": "Self-hosted Infrastructure",
|
||||||
|
"f4": "Community Support",
|
||||||
|
"f5": "Access to Core Features",
|
||||||
|
"f6": "Dokploy Integration",
|
||||||
|
"f7": "Basic Backups",
|
||||||
|
"f8": "Access to All Updates",
|
||||||
|
"f9": "Unlimited Servers"
|
||||||
|
},
|
||||||
|
"go": "Installation"
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"title": "Recommended",
|
||||||
|
"section": {
|
||||||
|
"title": "Dokploy Plan",
|
||||||
|
"description": " to manage Dokploy UI infrastructure, we take care of it for you."
|
||||||
|
},
|
||||||
|
"servers": "{serverQuantity}Servers (You bring the servers)",
|
||||||
|
"features": {
|
||||||
|
"f1": "Managed Hosting: No need to manage your own servers",
|
||||||
|
"f2": "Priority Support",
|
||||||
|
"f3": "Future-Proof Features"
|
||||||
|
},
|
||||||
|
"go": "Subscribe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "Frequently asked questions",
|
||||||
|
"description": "If you can’t find what you’re looking for, please send us an email to",
|
||||||
|
"q1": "How does Dokploy's Open Source plan work?",
|
||||||
|
"a1": "You can host Dokploy UI on your own infrastructure and you will be responsible for the maintenance and updates.",
|
||||||
|
"q2": "Do I need to provide my own server for the managed plan?",
|
||||||
|
"a2": "Yes, in the managed plan, you provide your own server eg(Hetzner, Hostinger, AWS, ETC.) VPS, and we manage the Dokploy UI infrastructure for you.",
|
||||||
|
"q3": "What happens if I need more than one server?",
|
||||||
|
"a3": "The first server costs $4.50/month, if you buy more than one it will be $3.50/month per server.",
|
||||||
|
"q4": "Is there a limit on the number of deployments?",
|
||||||
|
"a4": "No, there is no limit on the number of deployments in any of the plans.",
|
||||||
|
"q5": "What happens if I exceed my purchased server limit?",
|
||||||
|
"a5": "The most recently added servers will be deactivated. You won't be able to create services on inactive servers until they are reactivated.",
|
||||||
|
"q6": "Do you offer a refunds?",
|
||||||
|
"a6": "We do not offer refunds. However, you can cancel your subscription at any time. Feel free to try our open-source version for free before making a purchase.",
|
||||||
|
"q7": "What kind of support do you offer?",
|
||||||
|
"a7": "We offer community support for the open source version and priority support for paid plans.",
|
||||||
|
"q8": "Is Dokploy open-source?",
|
||||||
|
"a8": "Yes, Dokploy is fully open-source. You can contribute or modify it as needed for your projects."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,33 @@
|
|||||||
"features": "特性",
|
"features": "特性",
|
||||||
"faqs": "FAQ",
|
"faqs": "FAQ",
|
||||||
"docs": "文档",
|
"docs": "文档",
|
||||||
|
"pricing": "价格",
|
||||||
"support": "赞助",
|
"support": "赞助",
|
||||||
|
"dashboard": "控制台",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"i18nButtonPlaceholder": "语言",
|
"i18nButtonPlaceholder": "语言",
|
||||||
"i18nEn": "English",
|
"i18nEn": "English",
|
||||||
"i18nZh-Hans": "简体中文"
|
"i18nZh-Hans": "简体中文"
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
|
"cloud": "隆重介绍 Dokploy 云",
|
||||||
"deploy": "部署在",
|
"deploy": "部署在",
|
||||||
"anywhere": "任何设施之上",
|
"anywhere": "任何设施之上",
|
||||||
"with": "",
|
"with": "",
|
||||||
"des": "以前所未有的简洁和高效提供一站式项目、数据的管理以及系统监控。",
|
"des": "以前所未有的简洁和高效提供一站式项目、数据的管理以及系统监控。",
|
||||||
"featuredIn": "发布于"
|
"featuredIn": "发布于",
|
||||||
|
"sponsors": {
|
||||||
|
"title": "赞助名单",
|
||||||
|
"description": "Dokploy 是由社区成员共同支持的完全免费的开源项目,您的慷慨解囊将帮助我们继续开发和改进 Dokploy。",
|
||||||
|
"level": {
|
||||||
|
"hero": "特别赞助",
|
||||||
|
"premium": "金牌赞助",
|
||||||
|
"supporting": "银牌赞助",
|
||||||
|
"community": "铜牌赞助",
|
||||||
|
"organizations": "组织赞助",
|
||||||
|
"individuals": "个人赞助"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"primaryFeatures": {
|
"primaryFeatures": {
|
||||||
"title": "全面掌控您的基础设施",
|
"title": "全面掌控您的基础设施",
|
||||||
@@ -92,5 +107,70 @@
|
|||||||
"intro": "https://docs.dokploy.com/cn/docs/core/get-started/introduction",
|
"intro": "https://docs.dokploy.com/cn/docs/core/get-started/introduction",
|
||||||
"install": "https://docs.dokploy.com/cn/docs/core/get-started/introduction"
|
"install": "https://docs.dokploy.com/cn/docs/core/get-started/introduction"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Pricing": {
|
||||||
|
"swirlyDoodleTitle": "简洁明了的,",
|
||||||
|
"restTitle": "定价",
|
||||||
|
"description": "更聪明的部署方式,更快的扩容速度,以及不会让你破产的账单。",
|
||||||
|
"billingCycle": {
|
||||||
|
"monthly": "按月",
|
||||||
|
"annual": "按年"
|
||||||
|
},
|
||||||
|
"plan": {
|
||||||
|
"free": {
|
||||||
|
"title": "免费",
|
||||||
|
"subTitle": "开源版本",
|
||||||
|
"section": {
|
||||||
|
"title": "部署Dokploy的开源版本",
|
||||||
|
"description": "自行管理您的基础设施,不收取任何费用"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"f1": "灵活的架构,您可以单独部署 Dokploy 管理端",
|
||||||
|
"f2": "无限数量的部署",
|
||||||
|
"f3": "自维护的基础设施",
|
||||||
|
"f4": "来自社区的有限支持",
|
||||||
|
"f5": "所有功能可用",
|
||||||
|
"f6": "Dokploy 集成",
|
||||||
|
"f7": "基础备份服务",
|
||||||
|
"f8": "跟随开源版本更新",
|
||||||
|
"f9": "无限服务器数量"
|
||||||
|
},
|
||||||
|
"go": "立即开始"
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"title": "推荐(年付更优惠)",
|
||||||
|
"section": {
|
||||||
|
"title": "Dokploy 云",
|
||||||
|
"description": "使用我们的云服务,一站式管理您所有的部署。"
|
||||||
|
},
|
||||||
|
"servers": "{serverQuantity}台受控服务器",
|
||||||
|
"features": {
|
||||||
|
"f1": "由 Dokploy 云提供支持的独立控制面板",
|
||||||
|
"f2": "优先技术支持",
|
||||||
|
"f3": "优先体验先行功能"
|
||||||
|
},
|
||||||
|
"go": "订阅"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"faq": {
|
||||||
|
"title": "常见问题",
|
||||||
|
"description": "如果您的问题不在以下列表,请随时向我们致信",
|
||||||
|
"q1": "Dokploy 的开源版本是什么?",
|
||||||
|
"a1": "您可以免费安装 Dokploy 开源版本,并自行负责 Dokploy 的日后维护工作",
|
||||||
|
"q2": "付费计划还需要自己的服务器吗?",
|
||||||
|
"a2": "是的,在付费计划中,您依然需要提供您用于实际运行服务的服务器(如阿里云、腾讯云、AWS 等),我们会为您管理 Dokploy 控制面板。",
|
||||||
|
"q3": "如果我需要管理更多的服务器怎么办?",
|
||||||
|
"a3": "第一台服务器的费用为 $4.5/月,之后每台的费用是 $3.5/月。",
|
||||||
|
"q4": "部署服务的数量有限制吗?",
|
||||||
|
"a4": "不管您如何使用 Dokploy,我们都不会限制您部署服务的数量。",
|
||||||
|
"q5": "如果我意外超过了最大服务器数量怎么办?",
|
||||||
|
"a5": "最新添加的服务器将被停用,您将不能向其创建服务,直到它们被重新激活。",
|
||||||
|
"q6": "关于退款服务的政策?",
|
||||||
|
"a6": "您可以随时取消您的订阅,但请原谅我们无法提供退款服务,您可以无限试用开源版本,然后再购买。",
|
||||||
|
"q7": "关于技术支持?",
|
||||||
|
"a7": "付费计划可以得到优先的技术支持,开源版本则是由社区提供技术支持。",
|
||||||
|
"q8": "Dokploy 开源吗?",
|
||||||
|
"a8": "是的,Dokploy 完全开源,您可以参与贡献或者是 Fork 后自行修改以用于您的私人需求。"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ install_dokploy() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
advertise_addr=$(get_ip)
|
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
@@ -92,6 +92,7 @@ install_dokploy() {
|
|||||||
--update-order stop-first \
|
--update-order stop-first \
|
||||||
--constraint 'node.role == manager' \
|
--constraint 'node.role == manager' \
|
||||||
-e RELEASE_TAG=canary \
|
-e RELEASE_TAG=canary \
|
||||||
|
-e ADVERTISE_ADDR=$advertise_addr \
|
||||||
dokploy/dokploy:canary
|
dokploy/dokploy:canary
|
||||||
|
|
||||||
GREEN="\033[0;32m"
|
GREEN="\033[0;32m"
|
||||||
|
|||||||
@@ -41,7 +41,23 @@ install_dokploy() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
docker swarm leave --force 2>/dev/null
|
docker swarm leave --force 2>/dev/null
|
||||||
advertise_addr=$(curl -s ifconfig.me)
|
|
||||||
|
get_ip() {
|
||||||
|
# Try to get IPv4
|
||||||
|
local ipv4=$(curl -4s https://ifconfig.io 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$ipv4" ]; then
|
||||||
|
echo "$ipv4"
|
||||||
|
else
|
||||||
|
# Try to get IPv6
|
||||||
|
local ipv6=$(curl -6s https://ifconfig.io 2>/dev/null)
|
||||||
|
if [ -n "$ipv6" ]; then
|
||||||
|
echo "$ipv6"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
@@ -76,6 +92,7 @@ install_dokploy() {
|
|||||||
--update-order stop-first \
|
--update-order stop-first \
|
||||||
--constraint 'node.role == manager' \
|
--constraint 'node.role == manager' \
|
||||||
-e RELEASE_TAG=feature \
|
-e RELEASE_TAG=feature \
|
||||||
|
-e ADVERTISE_ADDR=$advertise_addr \
|
||||||
dokploy/dokploy:feature
|
dokploy/dokploy:feature
|
||||||
|
|
||||||
GREEN="\033[0;32m"
|
GREEN="\033[0;32m"
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ install_dokploy() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
advertise_addr=$(get_ip)
|
advertise_addr="${ADVERTISE_ADDR:-$(get_ip)}"
|
||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
@@ -90,6 +90,7 @@ install_dokploy() {
|
|||||||
--update-parallelism 1 \
|
--update-parallelism 1 \
|
||||||
--update-order stop-first \
|
--update-order stop-first \
|
||||||
--constraint 'node.role == manager' \
|
--constraint 'node.role == manager' \
|
||||||
|
-e ADVERTISE_ADDR=$advertise_addr \
|
||||||
dokploy/dokploy:latest
|
dokploy/dokploy:latest
|
||||||
|
|
||||||
GREEN="\033[0;32m"
|
GREEN="\033[0;32m"
|
||||||
|
|||||||
@@ -10,9 +10,6 @@ const config = {
|
|||||||
],
|
],
|
||||||
prefix: "",
|
prefix: "",
|
||||||
theme: {
|
theme: {
|
||||||
// fontFamily: {
|
|
||||||
// sans: ["var(--font-sans)", ...fontFamily.sans],
|
|
||||||
// },
|
|
||||||
fontSize: {
|
fontSize: {
|
||||||
xs: ["0.75rem", { lineHeight: "1rem" }],
|
xs: ["0.75rem", { lineHeight: "1rem" }],
|
||||||
sm: ["0.875rem", { lineHeight: "1.5rem" }],
|
sm: ["0.875rem", { lineHeight: "1.5rem" }],
|
||||||
@@ -75,7 +72,6 @@ const config = {
|
|||||||
lg: "var(--radius)",
|
lg: "var(--radius)",
|
||||||
md: "calc(var(--radius) - 2px)",
|
md: "calc(var(--radius) - 2px)",
|
||||||
sm: "calc(var(--radius) - 4px)",
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
|
||||||
"4xl": "2rem",
|
"4xl": "2rem",
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
@@ -84,17 +80,34 @@ const config = {
|
|||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"accordion-down": {
|
"accordion-down": {
|
||||||
from: { height: "0" },
|
from: {
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
height: "0",
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
height: "var(--radix-accordion-content-height)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"accordion-up": {
|
"accordion-up": {
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
from: {
|
||||||
to: { height: "0" },
|
height: "var(--radix-accordion-content-height)",
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
height: "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"shiny-text": {
|
||||||
|
"0%, 90%, 100%": {
|
||||||
|
"background-position": "calc(-100% - var(--shiny-width)) 0",
|
||||||
|
},
|
||||||
|
"30%, 60%": {
|
||||||
|
"background-position": "calc(100% + var(--shiny-width)) 0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
|
"shiny-text": "shiny-text 8s infinite",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": ["node_modules/**", ".next/**", "drizzle/**", ".docker", "dist"]
|
"ignore": [
|
||||||
|
"node_modules/**",
|
||||||
|
".next/**",
|
||||||
|
"drizzle/**",
|
||||||
|
".docker",
|
||||||
|
"dist",
|
||||||
|
"packages/server/package.json"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"dokploy:build": "pnpm --filter=dokploy run build",
|
"dokploy:build": "pnpm --filter=dokploy run build",
|
||||||
"dokploy:start": "pnpm --filter=dokploy run start",
|
"dokploy:start": "pnpm --filter=dokploy run start",
|
||||||
"test": "pnpm --filter=dokploy run test",
|
"test": "pnpm --filter=dokploy run test",
|
||||||
|
"server:script": "pnpm --filter=server run switch:dev",
|
||||||
"server:dev": "pnpm --filter=server run dev",
|
"server:dev": "pnpm --filter=server run dev",
|
||||||
"server:build": "pnpm --filter=server run build",
|
"server:build": "pnpm --filter=server run build",
|
||||||
"docker:build:canary": "./apps/dokploy/docker/build.sh canary",
|
"docker:build:canary": "./apps/dokploy/docker/build.sh canary",
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
// import { build } from "esbuild";
|
import path from "node:path";
|
||||||
// import alias from "esbuild-plugin-alias";
|
import { fileURLToPath } from "node:url";
|
||||||
// import path from "node:path";
|
import { build } from "esbuild";
|
||||||
// import { fileURLToPath } from "node:url";
|
import alias from "esbuild-plugin-alias";
|
||||||
// const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
|
const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
|
||||||
// const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
// build({
|
build({
|
||||||
// entryPoints: ["./src/**/*.ts"],
|
entryPoints: ["./src/**/*.ts"],
|
||||||
// // outfile: "./dist/index.js",
|
// outfile: "./dist/index.js",
|
||||||
// outdir: "./dist",
|
outdir: "./dist",
|
||||||
// bundle: true,
|
bundle: true,
|
||||||
// minify: false,
|
minify: false,
|
||||||
// platform: "node",
|
platform: "node",
|
||||||
// target: "esnext",
|
target: "esnext",
|
||||||
// format: "esm",
|
format: "esm",
|
||||||
// plugins: [
|
plugins: [
|
||||||
// alias({
|
alias({
|
||||||
// "@/server": path.resolve(__dirname, "src"),
|
"@dokploy/server": path.resolve(__dirname, "src"),
|
||||||
// }),
|
}),
|
||||||
// ],
|
],
|
||||||
// packages: "external",
|
packages: "external",
|
||||||
// // Opcional: si deseas emitir declaraciones de tipos con esbuild-plugin-dts
|
// Opcional: si deseas emitir declaraciones de tipos con esbuild-plugin-dts
|
||||||
// })
|
})
|
||||||
// .then(() => {
|
.then(() => {
|
||||||
// console.log("Build successful");
|
console.log("Build successful");
|
||||||
// })
|
})
|
||||||
// .catch(() => process.exit(1));
|
.catch(() => process.exit(1));
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "@dokploy/server",
|
"name": "@dokploy/server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "dist/index.js",
|
"main": "./src/index.ts",
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./db": {
|
||||||
|
"import": "./src/db/index.ts",
|
||||||
|
"require": "./dist/db/index.cjs.js"
|
||||||
|
},
|
||||||
|
"./setup/*": {
|
||||||
|
"import": "./src/setup/*.ts",
|
||||||
|
"require": "./dist/setup/index.cjs.js"
|
||||||
|
},
|
||||||
|
"./constants": {
|
||||||
|
"import": "./src/constants/index.ts",
|
||||||
|
"require": "./dist/constants.cjs.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf ./dist && tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json",
|
"build": "npm run switch:prod && rm -rf ./dist && tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json",
|
||||||
"build:types": "tsc --emitDeclarationOnly --experimenta-dts",
|
"build:types": "tsc --emitDeclarationOnly --experimenta-dts",
|
||||||
|
"switch:dev": "node scripts/switchToSrc.js",
|
||||||
|
"switch:prod": "node scripts/switchToDist.js",
|
||||||
"dev": "rm -rf ./dist && pnpm esbuild && tsc --emitDeclarationOnly --outDir dist -p tsconfig.server.json",
|
"dev": "rm -rf ./dist && pnpm esbuild && tsc --emitDeclarationOnly --outDir dist -p tsconfig.server.json",
|
||||||
"esbuild": "tsx ./esbuild.config.ts",
|
"esbuild": "tsx ./esbuild.config.ts && tsc --project tsconfig.server.json --emitDeclarationOnly ",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -49,6 +65,7 @@
|
|||||||
"ssh2": "1.15.0"
|
"ssh2": "1.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"esbuild-plugin-alias": "0.2.1",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"tsx": "^4.7.1",
|
"tsx": "^4.7.1",
|
||||||
"tsc-alias": "1.8.10",
|
"tsc-alias": "1.8.10",
|
||||||
|
|||||||
41
packages/server/scripts/switchToDist.js
Normal file
41
packages/server/scripts/switchToDist.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const packagePath = path.resolve(__dirname, "../package.json");
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
||||||
|
|
||||||
|
pkg.main = "./dist/index.js";
|
||||||
|
|
||||||
|
pkg.exports = {
|
||||||
|
".": {
|
||||||
|
import: "./dist/index.js",
|
||||||
|
require: "./dist/index.cjs.js",
|
||||||
|
},
|
||||||
|
"./db": {
|
||||||
|
import: "./dist/db/index.js",
|
||||||
|
require: "./dist/db/index.cjs.js",
|
||||||
|
},
|
||||||
|
"./*": {
|
||||||
|
import: "./dist/*",
|
||||||
|
require: "./dist/*.cjs",
|
||||||
|
},
|
||||||
|
"./dist": {
|
||||||
|
import: "./dist/index.js",
|
||||||
|
require: "./dist/index.cjs.js",
|
||||||
|
},
|
||||||
|
"./dist/db": {
|
||||||
|
import: "./dist/db/index.js",
|
||||||
|
require: "./dist/db/index.cjs.js",
|
||||||
|
},
|
||||||
|
"./dist/db/schema": {
|
||||||
|
import: "./dist/db/schema/index.js",
|
||||||
|
require: "./dist/db/schema/index.cjs.js",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2));
|
||||||
|
console.log("Switched exports to use dist for production");
|
||||||
34
packages/server/scripts/switchToSrc.js
Normal file
34
packages/server/scripts/switchToSrc.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const packagePath = path.resolve(__dirname, "../package.json");
|
||||||
|
|
||||||
|
// Leer el archivo package.json
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
||||||
|
|
||||||
|
pkg.main = "./src/index.ts";
|
||||||
|
|
||||||
|
// Modificar los exports
|
||||||
|
pkg.exports = {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./db": {
|
||||||
|
import: "./src/db/index.ts",
|
||||||
|
require: "./dist/db/index.cjs.js",
|
||||||
|
},
|
||||||
|
"./setup/*": {
|
||||||
|
import: "./src/setup/*.ts",
|
||||||
|
require: "./dist/setup/index.cjs.js",
|
||||||
|
},
|
||||||
|
"./constants": {
|
||||||
|
import: "./src/constants/index.ts",
|
||||||
|
require: "./dist/constants.cjs.js",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Guardar los cambios en package.json
|
||||||
|
fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2));
|
||||||
|
console.log("Switched exports to use src for development");
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { webcrypto } from "node:crypto";
|
import { webcrypto } from "node:crypto";
|
||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
import { findAdminByAuthId } from "@/server/services/admin";
|
import { findAdminByAuthId } from "@dokploy/server/services/admin";
|
||||||
import { findUserByAuthId } from "@/server/services/user";
|
import { findUserByAuthId } from "@dokploy/server/services/user";
|
||||||
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
|
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
|
||||||
import { TimeSpan } from "lucia";
|
import { TimeSpan } from "lucia";
|
||||||
import { Lucia } from "lucia/dist/core.js";
|
import { Lucia } from "lucia/dist/core.js";
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export const auth = pgTable("auth", {
|
|||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
resetPasswordToken: text("resetPasswordToken"),
|
resetPasswordToken: text("resetPasswordToken"),
|
||||||
resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
|
resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
|
||||||
|
confirmationToken: text("confirmationToken"),
|
||||||
|
confirmationExpiresAt: text("confirmationExpiresAt"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const authRelations = relations(auth, ({ many }) => ({
|
export const authRelations = relations(auth, ({ many }) => ({
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
admins,
|
admins,
|
||||||
type apiCreateUserInvitation,
|
type apiCreateUserInvitation,
|
||||||
auth,
|
auth,
|
||||||
users,
|
users,
|
||||||
} from "@/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from "bcrypt";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -20,7 +20,7 @@ export const createInvitation = async (
|
|||||||
const result = await tx
|
const result = await tx
|
||||||
.insert(auth)
|
.insert(auth)
|
||||||
.values({
|
.values({
|
||||||
email: input.email,
|
email: input.email.toLowerCase(),
|
||||||
rol: "user",
|
rol: "user",
|
||||||
password: bcrypt.hashSync("01231203012312", 10),
|
password: bcrypt.hashSync("01231203012312", 10),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,38 +1,41 @@
|
|||||||
import { docker } from "@/server/constants";
|
import { docker } from "@dokploy/server/constants";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { type apiCreateApplication, applications } from "@/server/db/schema";
|
import {
|
||||||
import { generateAppName } from "@/server/db/schema";
|
type apiCreateApplication,
|
||||||
import { getAdvancedStats } from "@/server/monitoring/utilts";
|
applications,
|
||||||
import { generatePassword } from "@/server/templates/utils";
|
} from "@dokploy/server/db/schema";
|
||||||
|
import { generateAppName } from "@dokploy/server/db/schema";
|
||||||
|
import { getAdvancedStats } from "@dokploy/server/monitoring/utilts";
|
||||||
|
import { generatePassword } from "@dokploy/server/templates/utils";
|
||||||
import {
|
import {
|
||||||
buildApplication,
|
buildApplication,
|
||||||
getBuildCommand,
|
getBuildCommand,
|
||||||
mechanizeDockerContainer,
|
mechanizeDockerContainer,
|
||||||
} from "@/server/utils/builders";
|
} from "@dokploy/server/utils/builders";
|
||||||
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
|
import { sendBuildErrorNotifications } from "@dokploy/server/utils/notifications/build-error";
|
||||||
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
|
import { sendBuildSuccessNotifications } from "@dokploy/server/utils/notifications/build-success";
|
||||||
import { execAsyncRemote } from "@/server/utils/process/execAsync";
|
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||||
import {
|
import {
|
||||||
cloneBitbucketRepository,
|
cloneBitbucketRepository,
|
||||||
getBitbucketCloneCommand,
|
getBitbucketCloneCommand,
|
||||||
} from "@/server/utils/providers/bitbucket";
|
} from "@dokploy/server/utils/providers/bitbucket";
|
||||||
import {
|
import {
|
||||||
buildDocker,
|
buildDocker,
|
||||||
buildRemoteDocker,
|
buildRemoteDocker,
|
||||||
} from "@/server/utils/providers/docker";
|
} from "@dokploy/server/utils/providers/docker";
|
||||||
import {
|
import {
|
||||||
cloneGitRepository,
|
cloneGitRepository,
|
||||||
getCustomGitCloneCommand,
|
getCustomGitCloneCommand,
|
||||||
} from "@/server/utils/providers/git";
|
} from "@dokploy/server/utils/providers/git";
|
||||||
import {
|
import {
|
||||||
cloneGithubRepository,
|
cloneGithubRepository,
|
||||||
getGithubCloneCommand,
|
getGithubCloneCommand,
|
||||||
} from "@/server/utils/providers/github";
|
} from "@dokploy/server/utils/providers/github";
|
||||||
import {
|
import {
|
||||||
cloneGitlabRepository,
|
cloneGitlabRepository,
|
||||||
getGitlabCloneCommand,
|
getGitlabCloneCommand,
|
||||||
} from "@/server/utils/providers/gitlab";
|
} from "@dokploy/server/utils/providers/gitlab";
|
||||||
import { createTraefikConfig } from "@/server/utils/traefik/application";
|
import { createTraefikConfig } from "@dokploy/server/utils/traefik/application";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { encodeBase64 } from "../utils/docker/utils";
|
import { encodeBase64 } from "../utils/docker/utils";
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
admins,
|
admins,
|
||||||
type apiCreateAdmin,
|
type apiCreateAdmin,
|
||||||
type apiCreateUser,
|
type apiCreateUser,
|
||||||
auth,
|
auth,
|
||||||
users,
|
users,
|
||||||
} from "@/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import { getPublicIpWithFallback } from "@/server/wss/terminal";
|
import { getPublicIpWithFallback } from "@dokploy/server/wss/terminal";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from "bcrypt";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -24,7 +24,7 @@ export const createAdmin = async (input: typeof apiCreateAdmin._type) => {
|
|||||||
const newAuth = await tx
|
const newAuth = await tx
|
||||||
.insert(auth)
|
.insert(auth)
|
||||||
.values({
|
.values({
|
||||||
email: input.email,
|
email: input.email.toLowerCase(),
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
rol: "admin",
|
rol: "admin",
|
||||||
})
|
})
|
||||||
@@ -43,7 +43,8 @@ export const createAdmin = async (input: typeof apiCreateAdmin._type) => {
|
|||||||
.values({
|
.values({
|
||||||
authId: newAuth.id,
|
authId: newAuth.id,
|
||||||
...(!IS_CLOUD && {
|
...(!IS_CLOUD && {
|
||||||
serverIp: await getPublicIpWithFallback(),
|
serverIp:
|
||||||
|
process.env.ADVERTISE_ADDR || (await getPublicIpWithFallback()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
@@ -92,7 +93,7 @@ export const findAuthByEmail = async (email: string) => {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
message: "Auth not found",
|
message: "User not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { type apiCreateBackup, backups } from "@/server/db/schema";
|
import { type apiCreateBackup, backups } from "@dokploy/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
type apiCreateBitbucket,
|
type apiCreateBitbucket,
|
||||||
type apiUpdateBitbucket,
|
type apiUpdateBitbucket,
|
||||||
bitbucket,
|
bitbucket,
|
||||||
gitProvider,
|
gitProvider,
|
||||||
} from "@/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { paths } from "@/server/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { type apiCreateCertificate, certificates } from "@/server/db/schema";
|
import {
|
||||||
import { removeDirectoryIfExistsContent } from "@/server/utils/filesystem/directory";
|
type apiCreateCertificate,
|
||||||
|
certificates,
|
||||||
|
} from "@dokploy/server/db/schema";
|
||||||
|
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
|
|||||||
@@ -1,44 +1,47 @@
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { paths } from "@/server/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { type apiCreateCompose, compose } from "@/server/db/schema";
|
import { type apiCreateCompose, compose } from "@dokploy/server/db/schema";
|
||||||
import { generateAppName } from "@/server/db/schema";
|
import { generateAppName } from "@dokploy/server/db/schema";
|
||||||
import { generatePassword } from "@/server/templates/utils";
|
import { generatePassword } from "@dokploy/server/templates/utils";
|
||||||
import {
|
import {
|
||||||
buildCompose,
|
buildCompose,
|
||||||
getBuildComposeCommand,
|
getBuildComposeCommand,
|
||||||
} from "@/server/utils/builders/compose";
|
} from "@dokploy/server/utils/builders/compose";
|
||||||
import { randomizeSpecificationFile } from "@/server/utils/docker/compose";
|
import { randomizeSpecificationFile } from "@dokploy/server/utils/docker/compose";
|
||||||
import {
|
import {
|
||||||
cloneCompose,
|
cloneCompose,
|
||||||
cloneComposeRemote,
|
cloneComposeRemote,
|
||||||
loadDockerCompose,
|
loadDockerCompose,
|
||||||
loadDockerComposeRemote,
|
loadDockerComposeRemote,
|
||||||
} from "@/server/utils/docker/domain";
|
} from "@dokploy/server/utils/docker/domain";
|
||||||
import type { ComposeSpecification } from "@/server/utils/docker/types";
|
import type { ComposeSpecification } from "@dokploy/server/utils/docker/types";
|
||||||
import { sendBuildErrorNotifications } from "@/server/utils/notifications/build-error";
|
import { sendBuildErrorNotifications } from "@dokploy/server/utils/notifications/build-error";
|
||||||
import { sendBuildSuccessNotifications } from "@/server/utils/notifications/build-success";
|
import { sendBuildSuccessNotifications } from "@dokploy/server/utils/notifications/build-success";
|
||||||
import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
|
import {
|
||||||
|
execAsync,
|
||||||
|
execAsyncRemote,
|
||||||
|
} from "@dokploy/server/utils/process/execAsync";
|
||||||
import {
|
import {
|
||||||
cloneBitbucketRepository,
|
cloneBitbucketRepository,
|
||||||
getBitbucketCloneCommand,
|
getBitbucketCloneCommand,
|
||||||
} from "@/server/utils/providers/bitbucket";
|
} from "@dokploy/server/utils/providers/bitbucket";
|
||||||
import {
|
import {
|
||||||
cloneGitRepository,
|
cloneGitRepository,
|
||||||
getCustomGitCloneCommand,
|
getCustomGitCloneCommand,
|
||||||
} from "@/server/utils/providers/git";
|
} from "@dokploy/server/utils/providers/git";
|
||||||
import {
|
import {
|
||||||
cloneGithubRepository,
|
cloneGithubRepository,
|
||||||
getGithubCloneCommand,
|
getGithubCloneCommand,
|
||||||
} from "@/server/utils/providers/github";
|
} from "@dokploy/server/utils/providers/github";
|
||||||
import {
|
import {
|
||||||
cloneGitlabRepository,
|
cloneGitlabRepository,
|
||||||
getGitlabCloneCommand,
|
getGitlabCloneCommand,
|
||||||
} from "@/server/utils/providers/gitlab";
|
} from "@dokploy/server/utils/providers/gitlab";
|
||||||
import {
|
import {
|
||||||
createComposeFile,
|
createComposeFile,
|
||||||
getCreateComposeFileCommand,
|
getCreateComposeFileCommand,
|
||||||
} from "@/server/utils/providers/raw";
|
} from "@dokploy/server/utils/providers/raw";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { encodeBase64 } from "../utils/docker/utils";
|
import { encodeBase64 } from "../utils/docker/utils";
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { existsSync, promises as fsPromises } from "node:fs";
|
import { existsSync, promises as fsPromises } from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { paths } from "@/server/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import {
|
import {
|
||||||
type apiCreateDeployment,
|
type apiCreateDeployment,
|
||||||
type apiCreateDeploymentCompose,
|
type apiCreateDeploymentCompose,
|
||||||
type apiCreateDeploymentServer,
|
type apiCreateDeploymentServer,
|
||||||
deployments,
|
deployments,
|
||||||
} from "@/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import { removeDirectoryIfExistsContent } from "@/server/utils/filesystem/directory";
|
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { desc, eq } from "drizzle-orm";
|
import { desc, eq } from "drizzle-orm";
|
||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { type Compose, findComposeById, updateCompose } from "./compose";
|
import { type Compose, findComposeById, updateCompose } from "./compose";
|
||||||
import { type Server, findServerById } from "./server";
|
import { type Server, findServerById } from "./server";
|
||||||
|
|
||||||
import { execAsyncRemote } from "@/server/utils/process/execAsync";
|
import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync";
|
||||||
|
|
||||||
export type Deployment = typeof deployments.$inferSelect;
|
export type Deployment = typeof deployments.$inferSelect;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { type apiCreateDestination, destinations } from "@/server/db/schema";
|
import {
|
||||||
|
type apiCreateDestination,
|
||||||
|
destinations,
|
||||||
|
} from "@dokploy/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync";
|
import {
|
||||||
|
execAsync,
|
||||||
|
execAsyncRemote,
|
||||||
|
} from "@dokploy/server/utils/process/execAsync";
|
||||||
|
|
||||||
export const getContainers = async (serverId?: string | null) => {
|
export const getContainers = async (serverId?: string | null) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
import { generateRandomDomain } from "@/server/templates/utils";
|
import { generateRandomDomain } from "@dokploy/server/templates/utils";
|
||||||
import { manageDomain } from "@/server/utils/traefik/domain";
|
import { manageDomain } from "@dokploy/server/utils/traefik/domain";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { type apiCreateDomain, domains } from "../db/schema";
|
import { type apiCreateDomain, domains } from "../db/schema";
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user