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() {
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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 rm -f dokploy-network 2>/dev/null
|
||||||
docker network create --driver overlay --attachable dokploy-network
|
docker network create --driver overlay --attachable dokploy-network
|
||||||
|
|
||||||
echo "Network created"
|
echo "Network created"
|
||||||
|
|
||||||
# 准备配置目录
|
|
||||||
mkdir -p /etc/dokploy
|
mkdir -p /etc/dokploy
|
||||||
chmod -R 777 /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() {
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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 rm -f dokploy-network 2>/dev/null
|
||||||
docker network create --driver overlay --attachable dokploy-network
|
docker network create --driver overlay --attachable dokploy-network
|
||||||
|
|
||||||
echo "Network created"
|
echo "Network created"
|
||||||
|
|
||||||
# Prepare configuration directory
|
|
||||||
mkdir -p /etc/dokploy
|
mkdir -p /etc/dokploy
|
||||||
chmod -R 777 /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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
@@ -336,4 +348,111 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return true;
|
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;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
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`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,16 +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";
|
|
||||||
// Holman Oniel Guevara Ayala
|
|
||||||
// GET /dashboard/settings/server 200 in 6640ms
|
|
||||||
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, turbopack: true });
|
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 {
|
||||||
@@ -56,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));
|
||||||
@@ -79,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,12 +3,15 @@ import {
|
|||||||
createDefaultServerTraefikConfig,
|
createDefaultServerTraefikConfig,
|
||||||
createDefaultTraefikConfig,
|
createDefaultTraefikConfig,
|
||||||
initializeTraefik,
|
initializeTraefik,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server/setup/traefik-setup";
|
||||||
|
|
||||||
import { setupDirectories } from "@dokploy/server";
|
import { setupDirectories } from "@dokploy/server/setup/config-paths";
|
||||||
import { initializePostgres } from "@dokploy/server";
|
import { initializePostgres } from "@dokploy/server/setup/postgres-setup";
|
||||||
import { initializeRedis } from "@dokploy/server";
|
import { initializeRedis } from "@dokploy/server/setup/redis-setup";
|
||||||
import { initializeNetwork, initializeSwarm } from "@dokploy/server";
|
import {
|
||||||
|
initializeNetwork,
|
||||||
|
initializeSwarm,
|
||||||
|
} from "@dokploy/server/setup/setup";
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
setupDirectories();
|
setupDirectories();
|
||||||
|
|||||||
@@ -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/**/*"]
|
||||||
|
|||||||
5
apps/mig/.gitignore
vendored
5
apps/mig/.gitignore
vendored
@@ -1,5 +0,0 @@
|
|||||||
node_modules
|
|
||||||
|
|
||||||
/.cache
|
|
||||||
/build
|
|
||||||
.env
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Welcome to Remix!
|
|
||||||
|
|
||||||
- 📖 [Remix docs](https://remix.run/docs)
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Run the dev server:
|
|
||||||
|
|
||||||
```shellscript
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
First, build your app for production:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run the app in production mode:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you'll need to pick a host to deploy it to.
|
|
||||||
|
|
||||||
### DIY
|
|
||||||
|
|
||||||
If you're familiar with deploying Node applications, the built-in Remix app server is production-ready.
|
|
||||||
|
|
||||||
Make sure to deploy the output of `npm run build`
|
|
||||||
|
|
||||||
- `build/server`
|
|
||||||
- `build/client`
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* By default, Remix will handle hydrating your app on the client for you.
|
|
||||||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
|
|
||||||
* For more information, see https://remix.run/file-conventions/entry.client
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { RemixBrowser } from "@remix-run/react";
|
|
||||||
import { StrictMode, startTransition } from "react";
|
|
||||||
import { hydrateRoot } from "react-dom/client";
|
|
||||||
|
|
||||||
startTransition(() => {
|
|
||||||
hydrateRoot(
|
|
||||||
document,
|
|
||||||
<StrictMode>
|
|
||||||
<RemixBrowser />
|
|
||||||
</StrictMode>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
/**
|
|
||||||
* By default, Remix will handle generating the HTTP Response for you.
|
|
||||||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
|
|
||||||
* For more information, see https://remix.run/file-conventions/entry.server
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { PassThrough } from "node:stream";
|
|
||||||
|
|
||||||
import type { AppLoadContext, EntryContext } from "@remix-run/node";
|
|
||||||
import { createReadableStreamFromReadable } from "@remix-run/node";
|
|
||||||
import { RemixServer } from "@remix-run/react";
|
|
||||||
import { isbot } from "isbot";
|
|
||||||
import { renderToPipeableStream } from "react-dom/server";
|
|
||||||
|
|
||||||
const ABORT_DELAY = 5_000;
|
|
||||||
|
|
||||||
export default function handleRequest(
|
|
||||||
request: Request,
|
|
||||||
responseStatusCode: number,
|
|
||||||
responseHeaders: Headers,
|
|
||||||
remixContext: EntryContext,
|
|
||||||
// This is ignored so we can keep it in the template for visibility. Feel
|
|
||||||
// free to delete this parameter in your app if you're not using it!
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
loadContext: AppLoadContext,
|
|
||||||
) {
|
|
||||||
return isbot(request.headers.get("user-agent") || "")
|
|
||||||
? handleBotRequest(
|
|
||||||
request,
|
|
||||||
responseStatusCode,
|
|
||||||
responseHeaders,
|
|
||||||
remixContext,
|
|
||||||
)
|
|
||||||
: handleBrowserRequest(
|
|
||||||
request,
|
|
||||||
responseStatusCode,
|
|
||||||
responseHeaders,
|
|
||||||
remixContext,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBotRequest(
|
|
||||||
request: Request,
|
|
||||||
responseStatusCode: number,
|
|
||||||
responseHeaders: Headers,
|
|
||||||
remixContext: EntryContext,
|
|
||||||
) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let shellRendered = false;
|
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
|
||||||
<RemixServer
|
|
||||||
context={remixContext}
|
|
||||||
url={request.url}
|
|
||||||
abortDelay={ABORT_DELAY}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
onAllReady() {
|
|
||||||
shellRendered = true;
|
|
||||||
const body = new PassThrough();
|
|
||||||
const stream = createReadableStreamFromReadable(body);
|
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
new Response(stream, {
|
|
||||||
headers: responseHeaders,
|
|
||||||
status: responseStatusCode,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
pipe(body);
|
|
||||||
},
|
|
||||||
onShellError(error: unknown) {
|
|
||||||
reject(error);
|
|
||||||
},
|
|
||||||
onError(error: unknown) {
|
|
||||||
// responseStatusCode = 500;
|
|
||||||
|
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
|
||||||
// errors encountered during initial shell rendering since they'll
|
|
||||||
// reject and get logged in handleDocumentRequest.
|
|
||||||
if (shellRendered) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBrowserRequest(
|
|
||||||
request: Request,
|
|
||||||
responseStatusCode: number,
|
|
||||||
responseHeaders: Headers,
|
|
||||||
remixContext: EntryContext,
|
|
||||||
) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let shellRendered = false;
|
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
|
||||||
<RemixServer
|
|
||||||
context={remixContext}
|
|
||||||
url={request.url}
|
|
||||||
abortDelay={ABORT_DELAY}
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
onShellReady() {
|
|
||||||
shellRendered = true;
|
|
||||||
const body = new PassThrough();
|
|
||||||
const stream = createReadableStreamFromReadable(body);
|
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
|
||||||
|
|
||||||
resolve(
|
|
||||||
new Response(stream, {
|
|
||||||
headers: responseHeaders,
|
|
||||||
status: responseStatusCode,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
pipe(body);
|
|
||||||
},
|
|
||||||
onShellError(error: unknown) {
|
|
||||||
reject(error);
|
|
||||||
},
|
|
||||||
onError(error: unknown) {
|
|
||||||
// responseStatusCode = 500;
|
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
|
||||||
// errors encountered during initial shell rendering since they'll
|
|
||||||
// reject and get logged in handleDocumentRequest.
|
|
||||||
if (shellRendered) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import type { LinksFunction } from "@remix-run/node";
|
|
||||||
import {
|
|
||||||
Links,
|
|
||||||
Meta,
|
|
||||||
Outlet,
|
|
||||||
Scripts,
|
|
||||||
ScrollRestoration,
|
|
||||||
} from "@remix-run/react";
|
|
||||||
|
|
||||||
import "./tailwind.css";
|
|
||||||
|
|
||||||
export const links: LinksFunction = () => [
|
|
||||||
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
|
|
||||||
{
|
|
||||||
rel: "preconnect",
|
|
||||||
href: "https://fonts.gstatic.com",
|
|
||||||
crossOrigin: "anonymous",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rel: "stylesheet",
|
|
||||||
href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charSet="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<Meta />
|
|
||||||
<Links />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{children}
|
|
||||||
<ScrollRestoration />
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return <Outlet />;
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import type { MetaFunction } from "@remix-run/node";
|
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
|
||||||
return [
|
|
||||||
{ title: "New Remix App" },
|
|
||||||
{ name: "description", content: "Welcome to Remix!" },
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Index() {
|
|
||||||
return (
|
|
||||||
<div className="flex h-screen items-center justify-center">
|
|
||||||
<div className="flex flex-col items-center gap-16">
|
|
||||||
<header className="flex flex-col items-center gap-9">
|
|
||||||
<h1 className="leading text-2xl font-bold text-gray-800 dark:text-gray-100">
|
|
||||||
Welcome to <span className="sr-only">Dokploy</span>
|
|
||||||
</h1>
|
|
||||||
<div className="h-[144px] w-[434px]">
|
|
||||||
<img
|
|
||||||
src="/logo-light.png"
|
|
||||||
alt="Remix"
|
|
||||||
className="block w-full dark:hidden"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src="/logo-dark.png"
|
|
||||||
alt="Remix"
|
|
||||||
className="hidden w-full dark:block"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<nav className="flex flex-col items-center justify-center gap-4 rounded-3xl border border-gray-200 p-6 dark:border-gray-700">
|
|
||||||
<p className="leading-6 text-gray-700 dark:text-gray-200">
|
|
||||||
What's
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{resources.map(({ href, text, icon }) => (
|
|
||||||
<li key={href}>
|
|
||||||
<a
|
|
||||||
className="group flex items-center gap-3 self-stretch p-3 leading-normal text-blue-700 hover:underline dark:text-blue-500"
|
|
||||||
href={href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
{text}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resources = [
|
|
||||||
{
|
|
||||||
href: "https://remix.run/start/quickstart",
|
|
||||||
text: "Quick Start (5 min)",
|
|
||||||
icon: (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8.51851 12.0741L7.92592 18L15.6296 9.7037L11.4815 7.33333L12.0741 2L4.37036 10.2963L8.51851 12.0741Z"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "https://remix.run/start/tutorial",
|
|
||||||
text: "Tutorial (30 min)",
|
|
||||||
icon: (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M4.561 12.749L3.15503 14.1549M3.00811 8.99944H1.01978M3.15503 3.84489L4.561 5.2508M8.3107 1.70923L8.3107 3.69749M13.4655 3.84489L12.0595 5.2508M18.1868 17.0974L16.635 18.6491C16.4636 18.8205 16.1858 18.8205 16.0144 18.6491L13.568 16.2028C13.383 16.0178 13.0784 16.0347 12.915 16.239L11.2697 18.2956C11.047 18.5739 10.6029 18.4847 10.505 18.142L7.85215 8.85711C7.75756 8.52603 8.06365 8.21994 8.39472 8.31453L17.6796 10.9673C18.0223 11.0653 18.1115 11.5094 17.8332 11.7321L15.7766 13.3773C15.5723 13.5408 15.5554 13.8454 15.7404 14.0304L18.1868 16.4767C18.3582 16.6481 18.3582 16.926 18.1868 17.0974Z"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "https://remix.run/docs",
|
|
||||||
text: "Remix Docs",
|
|
||||||
icon: (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M9.99981 10.0751V9.99992M17.4688 17.4688C15.889 19.0485 11.2645 16.9853 7.13958 12.8604C3.01467 8.73546 0.951405 4.11091 2.53116 2.53116C4.11091 0.951405 8.73546 3.01467 12.8604 7.13958C16.9853 11.2645 19.0485 15.889 17.4688 17.4688ZM2.53132 17.4688C0.951566 15.8891 3.01483 11.2645 7.13974 7.13963C11.2647 3.01471 15.8892 0.951453 17.469 2.53121C19.0487 4.11096 16.9854 8.73551 12.8605 12.8604C8.73562 16.9853 4.11107 19.0486 2.53132 17.4688Z"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeLinecap="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: "https://rmx.as/discord",
|
|
||||||
text: "Join Discord",
|
|
||||||
icon: (
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 20"
|
|
||||||
fill="none"
|
|
||||||
className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M15.0686 1.25995L14.5477 1.17423L14.2913 1.63578C14.1754 1.84439 14.0545 2.08275 13.9422 2.31963C12.6461 2.16488 11.3406 2.16505 10.0445 2.32014C9.92822 2.08178 9.80478 1.84975 9.67412 1.62413L9.41449 1.17584L8.90333 1.25995C7.33547 1.51794 5.80717 1.99419 4.37748 2.66939L4.19 2.75793L4.07461 2.93019C1.23864 7.16437 0.46302 11.3053 0.838165 15.3924L0.868838 15.7266L1.13844 15.9264C2.81818 17.1714 4.68053 18.1233 6.68582 18.719L7.18892 18.8684L7.50166 18.4469C7.96179 17.8268 8.36504 17.1824 8.709 16.4944L8.71099 16.4904C10.8645 17.0471 13.128 17.0485 15.2821 16.4947C15.6261 17.1826 16.0293 17.8269 16.4892 18.4469L16.805 18.8725L17.3116 18.717C19.3056 18.105 21.1876 17.1751 22.8559 15.9238L23.1224 15.724L23.1528 15.3923C23.5873 10.6524 22.3579 6.53306 19.8947 2.90714L19.7759 2.73227L19.5833 2.64518C18.1437 1.99439 16.6386 1.51826 15.0686 1.25995ZM16.6074 10.7755L16.6074 10.7756C16.5934 11.6409 16.0212 12.1444 15.4783 12.1444C14.9297 12.1444 14.3493 11.6173 14.3493 10.7877C14.3493 9.94885 14.9378 9.41192 15.4783 9.41192C16.0471 9.41192 16.6209 9.93851 16.6074 10.7755ZM8.49373 12.1444C7.94513 12.1444 7.36471 11.6173 7.36471 10.7877C7.36471 9.94885 7.95323 9.41192 8.49373 9.41192C9.06038 9.41192 9.63892 9.93712 9.6417 10.7815C9.62517 11.6239 9.05462 12.1444 8.49373 12.1444Z"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
@apply bg-white dark:bg-gray-950;
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
|
||||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
|
||||||
import postgres from "postgres";
|
|
||||||
|
|
||||||
const connectionString = process.env.DATABASE_URL || "";
|
|
||||||
|
|
||||||
const sql = postgres(connectionString, { max: 1 });
|
|
||||||
const db = drizzle(sql);
|
|
||||||
|
|
||||||
await migrate(db, { migrationsFolder: "drizzle" })
|
|
||||||
.then(() => {
|
|
||||||
console.log("Migration complete");
|
|
||||||
sql.end();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("Migration failed", error);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
sql.end();
|
|
||||||
});
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "my-remix-app",
|
|
||||||
"private": true,
|
|
||||||
"sideEffects": false,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"build": "remix vite:build",
|
|
||||||
"dev2": "tsx --watch -r dotenv/config server/server.ts",
|
|
||||||
"start3": "cross-env NODE_ENV=production node -r dotenv/config ./build/server/index.js",
|
|
||||||
"start2": "cross-env NODE_ENV=production node ./server.js",
|
|
||||||
"dev": "remix vite:dev",
|
|
||||||
"start": "remix-serve ./build/server/index.js",
|
|
||||||
"typecheck": "tsc"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@dokploy/server": "workspace:*",
|
|
||||||
"@remix-run/express": "2.13.1",
|
|
||||||
"@remix-run/node": "^2.13.1",
|
|
||||||
"@remix-run/react": "^2.13.1",
|
|
||||||
"cross-env": "7.0.3",
|
|
||||||
"isbot": "^4.1.0",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"bullmq": "5.4.2",
|
|
||||||
"rotating-file-stream": "3.2.3",
|
|
||||||
"@faker-js/faker": "^8.4.1",
|
|
||||||
"@lucia-auth/adapter-drizzle": "1.0.7",
|
|
||||||
"@octokit/auth-app": "^6.0.4",
|
|
||||||
"@react-email/components": "^0.0.21",
|
|
||||||
"@trpc/server": "^10.43.6",
|
|
||||||
"adm-zip": "^0.5.14",
|
|
||||||
"bcrypt": "5.1.1",
|
|
||||||
"bl": "6.0.11",
|
|
||||||
"boxen": "^7.1.1",
|
|
||||||
"date-fns": "3.6.0",
|
|
||||||
"dockerode": "4.0.2",
|
|
||||||
"dotenv": "16.4.5",
|
|
||||||
"drizzle-orm": "^0.30.8",
|
|
||||||
"drizzle-zod": "0.5.1",
|
|
||||||
"hi-base32": "^0.5.1",
|
|
||||||
"js-yaml": "4.1.0",
|
|
||||||
"lodash": "4.17.21",
|
|
||||||
"lucia": "^3.0.1",
|
|
||||||
"nanoid": "3",
|
|
||||||
"node-os-utils": "1.3.7",
|
|
||||||
"node-pty": "1.0.0",
|
|
||||||
"node-schedule": "2.1.1",
|
|
||||||
"nodemailer": "6.9.14",
|
|
||||||
"octokit": "3.1.2",
|
|
||||||
"otpauth": "^9.2.3",
|
|
||||||
"postgres": "3.4.4",
|
|
||||||
"public-ip": "6.0.2",
|
|
||||||
"qrcode": "^1.5.3",
|
|
||||||
"slugify": "^1.6.6",
|
|
||||||
"ws": "8.16.0",
|
|
||||||
"zod": "^3.23.4",
|
|
||||||
"ssh2": "1.15.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"esbuild": "0.24.0",
|
|
||||||
"@remix-run/dev": "^2.13.1",
|
|
||||||
"@types/react": "^18.2.20",
|
|
||||||
"@types/react-dom": "^18.2.7",
|
|
||||||
"autoprefixer": "^10.4.19",
|
|
||||||
"postcss": "^8.4.38",
|
|
||||||
"tailwindcss": "^3.4.4",
|
|
||||||
"typescript": "^5.1.6",
|
|
||||||
"vite": "^5.1.0",
|
|
||||||
"vite-tsconfig-paths": "^4.2.1",
|
|
||||||
|
|
||||||
"tsx": "^4.7.1",
|
|
||||||
"tsc-alias": "1.8.10",
|
|
||||||
"@types/adm-zip": "^0.5.5",
|
|
||||||
"@types/bcrypt": "5.0.2",
|
|
||||||
"@types/dockerode": "3.3.23",
|
|
||||||
"@types/js-yaml": "4.0.9",
|
|
||||||
"@types/lodash": "4.17.4",
|
|
||||||
"@types/node": "^18.17.0",
|
|
||||||
"@types/node-os-utils": "1.3.4",
|
|
||||||
"@types/node-schedule": "2.1.6",
|
|
||||||
"@types/nodemailer": "^6.4.15",
|
|
||||||
"@types/qrcode": "^1.5.5",
|
|
||||||
"@types/ws": "8.5.10",
|
|
||||||
"drizzle-kit": "^0.21.1",
|
|
||||||
"@types/ssh2": "1.15.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.18.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.8 KiB |
@@ -1,77 +0,0 @@
|
|||||||
import { authRouter } from "@/server/api/routers/auth";
|
|
||||||
import { createTRPCRouter } from "../api/trpc";
|
|
||||||
import { adminRouter } from "./routers/admin";
|
|
||||||
import { applicationRouter } from "./routers/application";
|
|
||||||
import { backupRouter } from "./routers/backup";
|
|
||||||
import { bitbucketRouter } from "./routers/bitbucket";
|
|
||||||
import { certificateRouter } from "./routers/certificate";
|
|
||||||
import { clusterRouter } from "./routers/cluster";
|
|
||||||
import { composeRouter } from "./routers/compose";
|
|
||||||
import { deploymentRouter } from "./routers/deployment";
|
|
||||||
import { destinationRouter } from "./routers/destination";
|
|
||||||
import { dockerRouter } from "./routers/docker";
|
|
||||||
import { domainRouter } from "./routers/domain";
|
|
||||||
import { gitProviderRouter } from "./routers/git-provider";
|
|
||||||
import { githubRouter } from "./routers/github";
|
|
||||||
import { gitlabRouter } from "./routers/gitlab";
|
|
||||||
import { mariadbRouter } from "./routers/mariadb";
|
|
||||||
import { mongoRouter } from "./routers/mongo";
|
|
||||||
import { mountRouter } from "./routers/mount";
|
|
||||||
import { mysqlRouter } from "./routers/mysql";
|
|
||||||
import { notificationRouter } from "./routers/notification";
|
|
||||||
import { portRouter } from "./routers/port";
|
|
||||||
import { postgresRouter } from "./routers/postgres";
|
|
||||||
import { projectRouter } from "./routers/project";
|
|
||||||
import { redirectsRouter } from "./routers/redirects";
|
|
||||||
import { redisRouter } from "./routers/redis";
|
|
||||||
import { registryRouter } from "./routers/registry";
|
|
||||||
import { securityRouter } from "./routers/security";
|
|
||||||
import { serverRouter } from "./routers/server";
|
|
||||||
import { settingsRouter } from "./routers/settings";
|
|
||||||
import { sshRouter } from "./routers/ssh-key";
|
|
||||||
import { stripeRouter } from "./routers/stripe";
|
|
||||||
import { userRouter } from "./routers/user";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the primary router for your server.
|
|
||||||
*
|
|
||||||
* All routers added in /api/routers should be manually added here.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const appRouter = createTRPCRouter({
|
|
||||||
admin: adminRouter,
|
|
||||||
docker: dockerRouter,
|
|
||||||
auth: authRouter,
|
|
||||||
project: projectRouter,
|
|
||||||
application: applicationRouter,
|
|
||||||
mysql: mysqlRouter,
|
|
||||||
postgres: postgresRouter,
|
|
||||||
redis: redisRouter,
|
|
||||||
mongo: mongoRouter,
|
|
||||||
mariadb: mariadbRouter,
|
|
||||||
compose: composeRouter,
|
|
||||||
user: userRouter,
|
|
||||||
domain: domainRouter,
|
|
||||||
destination: destinationRouter,
|
|
||||||
backup: backupRouter,
|
|
||||||
deployment: deploymentRouter,
|
|
||||||
mounts: mountRouter,
|
|
||||||
certificates: certificateRouter,
|
|
||||||
settings: settingsRouter,
|
|
||||||
security: securityRouter,
|
|
||||||
redirects: redirectsRouter,
|
|
||||||
port: portRouter,
|
|
||||||
registry: registryRouter,
|
|
||||||
cluster: clusterRouter,
|
|
||||||
notification: notificationRouter,
|
|
||||||
sshKey: sshRouter,
|
|
||||||
gitProvider: gitProviderRouter,
|
|
||||||
bitbucket: bitbucketRouter,
|
|
||||||
gitlab: gitlabRouter,
|
|
||||||
github: githubRouter,
|
|
||||||
server: serverRouter,
|
|
||||||
stripe: stripeRouter,
|
|
||||||
});
|
|
||||||
|
|
||||||
// export type definition of API
|
|
||||||
export type AppRouter = typeof appRouter;
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
apiAssignPermissions,
|
|
||||||
apiCreateUserInvitation,
|
|
||||||
apiFindOneToken,
|
|
||||||
apiRemoveUser,
|
|
||||||
users,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
|
|
||||||
import {
|
|
||||||
createInvitation,
|
|
||||||
findAdminById,
|
|
||||||
findUserByAuthId,
|
|
||||||
findUserById,
|
|
||||||
getUserByToken,
|
|
||||||
removeUserByAuthId,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { adminProcedure, createTRPCRouter, publicProcedure } from "../trpc";
|
|
||||||
|
|
||||||
export const adminRouter = createTRPCRouter({
|
|
||||||
one: adminProcedure.query(async ({ ctx }) => {
|
|
||||||
const { sshPrivateKey, ...rest } = await findAdminById(ctx.user.adminId);
|
|
||||||
return {
|
|
||||||
haveSSH: !!sshPrivateKey,
|
|
||||||
...rest,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
createUserInvitation: adminProcedure
|
|
||||||
.input(apiCreateUserInvitation)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
await createInvitation(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message:
|
|
||||||
"Error to create this user\ncheck if the email is not registered",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
removeUser: adminProcedure
|
|
||||||
.input(apiRemoveUser)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const user = await findUserByAuthId(input.authId);
|
|
||||||
|
|
||||||
if (user.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to delete this user",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await removeUserByAuthId(input.authId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to delete this user",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
getUserByToken: publicProcedure
|
|
||||||
.input(apiFindOneToken)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getUserByToken(input.token);
|
|
||||||
}),
|
|
||||||
assignPermissions: adminProcedure
|
|
||||||
.input(apiAssignPermissions)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const user = await findUserById(input.userId);
|
|
||||||
|
|
||||||
if (user.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to assign permissions",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await db
|
|
||||||
.update(users)
|
|
||||||
.set({
|
|
||||||
...input,
|
|
||||||
})
|
|
||||||
.where(eq(users.userId, input.userId));
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,625 +0,0 @@
|
|||||||
import {
|
|
||||||
createTRPCRouter,
|
|
||||||
protectedProcedure,
|
|
||||||
uploadProcedure,
|
|
||||||
} from "@/server/api/trpc";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
apiCreateApplication,
|
|
||||||
apiFindMonitoringStats,
|
|
||||||
apiFindOneApplication,
|
|
||||||
apiReloadApplication,
|
|
||||||
apiSaveBitbucketProvider,
|
|
||||||
apiSaveBuildType,
|
|
||||||
apiSaveDockerProvider,
|
|
||||||
apiSaveEnvironmentVariables,
|
|
||||||
apiSaveGitProvider,
|
|
||||||
apiSaveGithubProvider,
|
|
||||||
apiSaveGitlabProvider,
|
|
||||||
apiUpdateApplication,
|
|
||||||
applications,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
type DeploymentJob,
|
|
||||||
cleanQueuesByApplication,
|
|
||||||
} from "@/server/queues/deployments-queue";
|
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
|
||||||
import { deploy } from "@/server/utils/deploy";
|
|
||||||
import { uploadFileSchema } from "@/utils/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
addNewService,
|
|
||||||
checkServiceAccess,
|
|
||||||
createApplication,
|
|
||||||
deleteAllMiddlewares,
|
|
||||||
findApplicationById,
|
|
||||||
findProjectById,
|
|
||||||
getApplicationStats,
|
|
||||||
readConfig,
|
|
||||||
readRemoteConfig,
|
|
||||||
removeDeployments,
|
|
||||||
removeDirectoryCode,
|
|
||||||
removeMonitoringDirectory,
|
|
||||||
removeService,
|
|
||||||
removeTraefikConfig,
|
|
||||||
startService,
|
|
||||||
startServiceRemote,
|
|
||||||
stopService,
|
|
||||||
stopServiceRemote,
|
|
||||||
unzipDrop,
|
|
||||||
updateApplication,
|
|
||||||
updateApplicationStatus,
|
|
||||||
writeConfig,
|
|
||||||
writeConfigRemote,
|
|
||||||
// uploadFileSchema
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const applicationRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You need to use a server to create an application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const project = await findProjectById(input.projectId);
|
|
||||||
if (project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this project",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const newApplication = await createApplication(input);
|
|
||||||
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await addNewService(ctx.user.authId, newApplication.applicationId);
|
|
||||||
}
|
|
||||||
return newApplication;
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error instanceof TRPCError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the application",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(
|
|
||||||
ctx.user.authId,
|
|
||||||
input.applicationId,
|
|
||||||
"access",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return application;
|
|
||||||
}),
|
|
||||||
|
|
||||||
reload: protectedProcedure
|
|
||||||
.input(apiReloadApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to reload this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (application.serverId) {
|
|
||||||
await stopServiceRemote(application.serverId, input.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(input.appName);
|
|
||||||
}
|
|
||||||
await updateApplicationStatus(input.applicationId, "idle");
|
|
||||||
|
|
||||||
if (application.serverId) {
|
|
||||||
await startServiceRemote(application.serverId, input.appName);
|
|
||||||
} else {
|
|
||||||
await startService(input.appName);
|
|
||||||
}
|
|
||||||
await updateApplicationStatus(input.applicationId, "done");
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
|
|
||||||
delete: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(
|
|
||||||
ctx.user.authId,
|
|
||||||
input.applicationId,
|
|
||||||
"delete",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await db
|
|
||||||
.delete(applications)
|
|
||||||
.where(eq(applications.applicationId, input.applicationId))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
const cleanupOperations = [
|
|
||||||
async () => await deleteAllMiddlewares(application),
|
|
||||||
async () => await removeDeployments(application),
|
|
||||||
async () =>
|
|
||||||
await removeDirectoryCode(application.appName, application.serverId),
|
|
||||||
async () =>
|
|
||||||
await removeMonitoringDirectory(
|
|
||||||
application.appName,
|
|
||||||
application.serverId,
|
|
||||||
),
|
|
||||||
async () =>
|
|
||||||
await removeTraefikConfig(application.appName, application.serverId),
|
|
||||||
async () =>
|
|
||||||
await removeService(application?.appName, application.serverId),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const operation of cleanupOperations) {
|
|
||||||
try {
|
|
||||||
await operation();
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result[0];
|
|
||||||
}),
|
|
||||||
|
|
||||||
stop: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const service = await findApplicationById(input.applicationId);
|
|
||||||
if (service.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to stop this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (service.serverId) {
|
|
||||||
await stopServiceRemote(service.serverId, service.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(service.appName);
|
|
||||||
}
|
|
||||||
await updateApplicationStatus(input.applicationId, "idle");
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}),
|
|
||||||
|
|
||||||
start: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const service = await findApplicationById(input.applicationId);
|
|
||||||
if (service.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to start this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service.serverId) {
|
|
||||||
await startServiceRemote(service.serverId, service.appName);
|
|
||||||
} else {
|
|
||||||
await startService(service.appName);
|
|
||||||
}
|
|
||||||
await updateApplicationStatus(input.applicationId, "done");
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}),
|
|
||||||
|
|
||||||
redeploy: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to redeploy this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const jobData: DeploymentJob = {
|
|
||||||
applicationId: input.applicationId,
|
|
||||||
titleLog: "Rebuild deployment",
|
|
||||||
descriptionLog: "",
|
|
||||||
type: "redeploy",
|
|
||||||
applicationType: "application",
|
|
||||||
server: !!application.serverId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (IS_CLOUD && application.serverId) {
|
|
||||||
jobData.serverId = application.serverId;
|
|
||||||
await deploy(jobData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
await myQueue.add(
|
|
||||||
"deployments",
|
|
||||||
{ ...jobData },
|
|
||||||
{
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
saveEnvironment: protectedProcedure
|
|
||||||
.input(apiSaveEnvironmentVariables)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
env: input.env,
|
|
||||||
buildArgs: input.buildArgs,
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
saveBuildType: protectedProcedure
|
|
||||||
.input(apiSaveBuildType)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this build type",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
buildType: input.buildType,
|
|
||||||
dockerfile: input.dockerfile,
|
|
||||||
publishDirectory: input.publishDirectory,
|
|
||||||
dockerContextPath: input.dockerContextPath,
|
|
||||||
dockerBuildStage: input.dockerBuildStage,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
saveGithubProvider: protectedProcedure
|
|
||||||
.input(apiSaveGithubProvider)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this github provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
repository: input.repository,
|
|
||||||
branch: input.branch,
|
|
||||||
sourceType: "github",
|
|
||||||
owner: input.owner,
|
|
||||||
buildPath: input.buildPath,
|
|
||||||
applicationStatus: "idle",
|
|
||||||
githubId: input.githubId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
saveGitlabProvider: protectedProcedure
|
|
||||||
.input(apiSaveGitlabProvider)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this gitlab provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
gitlabRepository: input.gitlabRepository,
|
|
||||||
gitlabOwner: input.gitlabOwner,
|
|
||||||
gitlabBranch: input.gitlabBranch,
|
|
||||||
gitlabBuildPath: input.gitlabBuildPath,
|
|
||||||
sourceType: "gitlab",
|
|
||||||
applicationStatus: "idle",
|
|
||||||
gitlabId: input.gitlabId,
|
|
||||||
gitlabProjectId: input.gitlabProjectId,
|
|
||||||
gitlabPathNamespace: input.gitlabPathNamespace,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
saveBitbucketProvider: protectedProcedure
|
|
||||||
.input(apiSaveBitbucketProvider)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this bitbucket provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
bitbucketRepository: input.bitbucketRepository,
|
|
||||||
bitbucketOwner: input.bitbucketOwner,
|
|
||||||
bitbucketBranch: input.bitbucketBranch,
|
|
||||||
bitbucketBuildPath: input.bitbucketBuildPath,
|
|
||||||
sourceType: "bitbucket",
|
|
||||||
applicationStatus: "idle",
|
|
||||||
bitbucketId: input.bitbucketId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
saveDockerProvider: protectedProcedure
|
|
||||||
.input(apiSaveDockerProvider)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this docker provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
dockerImage: input.dockerImage,
|
|
||||||
username: input.username,
|
|
||||||
password: input.password,
|
|
||||||
sourceType: "docker",
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
saveGitProdiver: protectedProcedure
|
|
||||||
.input(apiSaveGitProvider)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this git provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
customGitBranch: input.customGitBranch,
|
|
||||||
customGitBuildPath: input.customGitBuildPath,
|
|
||||||
customGitUrl: input.customGitUrl,
|
|
||||||
customGitSSHKeyId: input.customGitSSHKeyId,
|
|
||||||
sourceType: "git",
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
markRunning: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to mark this application as running",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplicationStatus(input.applicationId, "running");
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const { applicationId, ...rest } = input;
|
|
||||||
const updateApp = await updateApplication(applicationId, {
|
|
||||||
...rest,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!updateApp) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Update: Error to update application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
refreshToken: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to refresh this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateApplication(input.applicationId, {
|
|
||||||
refreshToken: nanoid(),
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
deploy: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to deploy this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const jobData: DeploymentJob = {
|
|
||||||
applicationId: input.applicationId,
|
|
||||||
titleLog: "Manual deployment",
|
|
||||||
descriptionLog: "",
|
|
||||||
type: "deploy",
|
|
||||||
applicationType: "application",
|
|
||||||
server: !!application.serverId,
|
|
||||||
};
|
|
||||||
if (IS_CLOUD && application.serverId) {
|
|
||||||
jobData.serverId = application.serverId;
|
|
||||||
await deploy(jobData);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
await myQueue.add(
|
|
||||||
"deployments",
|
|
||||||
{ ...jobData },
|
|
||||||
{
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
|
|
||||||
cleanQueues: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to clean this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await cleanQueuesByApplication(input.applicationId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
readTraefikConfig: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to read this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let traefikConfig = null;
|
|
||||||
if (application.serverId) {
|
|
||||||
traefikConfig = await readRemoteConfig(
|
|
||||||
application.serverId,
|
|
||||||
application.appName,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
traefikConfig = readConfig(application.appName);
|
|
||||||
}
|
|
||||||
return traefikConfig;
|
|
||||||
}),
|
|
||||||
|
|
||||||
dropDeployment: protectedProcedure
|
|
||||||
.meta({
|
|
||||||
openapi: {
|
|
||||||
path: "/drop-deployment",
|
|
||||||
method: "POST",
|
|
||||||
override: true,
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.use(uploadProcedure)
|
|
||||||
.input(uploadFileSchema)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const zipFile = input.zip;
|
|
||||||
|
|
||||||
const app = await findApplicationById(input.applicationId as string);
|
|
||||||
|
|
||||||
if (app.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to deploy this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateApplication(input.applicationId as string, {
|
|
||||||
sourceType: "drop",
|
|
||||||
dropBuildPath: input.dropBuildPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
await unzipDrop(zipFile, app);
|
|
||||||
const jobData: DeploymentJob = {
|
|
||||||
applicationId: app.applicationId,
|
|
||||||
titleLog: "Manual deployment",
|
|
||||||
descriptionLog: "",
|
|
||||||
type: "deploy",
|
|
||||||
applicationType: "application",
|
|
||||||
server: !!app.serverId,
|
|
||||||
};
|
|
||||||
if (IS_CLOUD && app.serverId) {
|
|
||||||
jobData.serverId = app.serverId;
|
|
||||||
await deploy(jobData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await myQueue.add(
|
|
||||||
"deployments",
|
|
||||||
{ ...jobData },
|
|
||||||
{
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
updateTraefikConfig: protectedProcedure
|
|
||||||
.input(z.object({ applicationId: z.string(), traefikConfig: z.string() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (application.serverId) {
|
|
||||||
await writeConfigRemote(
|
|
||||||
application.serverId,
|
|
||||||
application.appName,
|
|
||||||
input.traefikConfig,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
writeConfig(application.appName, input.traefikConfig);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
readAppMonitoring: protectedProcedure
|
|
||||||
.input(apiFindMonitoringStats)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Functionality not available in cloud version",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const stats = await getApplicationStats(input.appName);
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,339 +0,0 @@
|
|||||||
import {
|
|
||||||
apiCreateAdmin,
|
|
||||||
apiCreateUser,
|
|
||||||
apiFindOneAuth,
|
|
||||||
apiLogin,
|
|
||||||
apiUpdateAuth,
|
|
||||||
apiUpdateAuthByAdmin,
|
|
||||||
apiVerify2FA,
|
|
||||||
apiVerifyLogin2FA,
|
|
||||||
auth,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
createAdmin,
|
|
||||||
createUser,
|
|
||||||
findAuthByEmail,
|
|
||||||
findAuthById,
|
|
||||||
generate2FASecret,
|
|
||||||
getUserByToken,
|
|
||||||
lucia,
|
|
||||||
luciaToken,
|
|
||||||
sendEmailNotification,
|
|
||||||
updateAuthById,
|
|
||||||
validateRequest,
|
|
||||||
verify2FA,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import * as bcrypt from "bcrypt";
|
|
||||||
import { isBefore } from "date-fns";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { db } from "../../db";
|
|
||||||
import {
|
|
||||||
adminProcedure,
|
|
||||||
createTRPCRouter,
|
|
||||||
protectedProcedure,
|
|
||||||
publicProcedure,
|
|
||||||
} from "../trpc";
|
|
||||||
|
|
||||||
export const authRouter = createTRPCRouter({
|
|
||||||
createAdmin: publicProcedure
|
|
||||||
.input(apiCreateAdmin)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
try {
|
|
||||||
if (!IS_CLOUD) {
|
|
||||||
const admin = await db.query.admins.findFirst({});
|
|
||||||
if (admin) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Admin already exists",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const newAdmin = await createAdmin(input);
|
|
||||||
const session = await lucia.createSession(newAdmin.id || "", {});
|
|
||||||
ctx.res.appendHeader(
|
|
||||||
"Set-Cookie",
|
|
||||||
lucia.createSessionCookie(session.id).serialize(),
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
createUser: publicProcedure
|
|
||||||
.input(apiCreateUser)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
try {
|
|
||||||
const token = await getUserByToken(input.token);
|
|
||||||
if (token.isExpired) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Invalid token",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const newUser = await createUser(input);
|
|
||||||
const session = await lucia.createSession(newUser?.authId || "", {});
|
|
||||||
ctx.res.appendHeader(
|
|
||||||
"Set-Cookie",
|
|
||||||
lucia.createSessionCookie(session.id).serialize(),
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the user",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
login: publicProcedure.input(apiLogin).mutation(async ({ ctx, input }) => {
|
|
||||||
try {
|
|
||||||
const auth = await findAuthByEmail(input.email);
|
|
||||||
|
|
||||||
const correctPassword = bcrypt.compareSync(
|
|
||||||
input.password,
|
|
||||||
auth?.password || "",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!correctPassword) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Credentials do not match",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth?.is2FAEnabled) {
|
|
||||||
return {
|
|
||||||
is2FAEnabled: true,
|
|
||||||
authId: auth.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const session = await lucia.createSession(auth?.id || "", {});
|
|
||||||
|
|
||||||
ctx.res.appendHeader(
|
|
||||||
"Set-Cookie",
|
|
||||||
lucia.createSessionCookie(session.id).serialize(),
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
is2FAEnabled: false,
|
|
||||||
authId: auth?.id,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Credentials do not match",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
get: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
const auth = await findAuthById(ctx.user.authId);
|
|
||||||
return auth;
|
|
||||||
}),
|
|
||||||
|
|
||||||
logout: protectedProcedure.mutation(async ({ ctx }) => {
|
|
||||||
const { req, res } = ctx;
|
|
||||||
const { session } = await validateRequest(req, res);
|
|
||||||
if (!session) return false;
|
|
||||||
|
|
||||||
await lucia.invalidateSession(session.id);
|
|
||||||
res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateAuth)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const auth = await updateAuthById(ctx.user.authId, {
|
|
||||||
...(input.email && { email: input.email }),
|
|
||||||
...(input.password && {
|
|
||||||
password: bcrypt.hashSync(input.password, 10),
|
|
||||||
}),
|
|
||||||
...(input.image && { image: input.image }),
|
|
||||||
});
|
|
||||||
|
|
||||||
return auth;
|
|
||||||
}),
|
|
||||||
|
|
||||||
generateToken: protectedProcedure.mutation(async ({ ctx, input }) => {
|
|
||||||
const auth = await findAuthById(ctx.user.authId);
|
|
||||||
|
|
||||||
if (auth.token) {
|
|
||||||
await luciaToken.invalidateSession(auth.token);
|
|
||||||
}
|
|
||||||
const session = await luciaToken.createSession(auth?.id || "", {
|
|
||||||
expiresIn: 60 * 60 * 24 * 30,
|
|
||||||
});
|
|
||||||
|
|
||||||
await updateAuthById(auth.id, {
|
|
||||||
token: session.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return auth;
|
|
||||||
}),
|
|
||||||
|
|
||||||
one: adminProcedure.input(apiFindOneAuth).query(async ({ input }) => {
|
|
||||||
const auth = await findAuthById(input.id);
|
|
||||||
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 }) => {
|
|
||||||
return await generate2FASecret(ctx.user.authId);
|
|
||||||
}),
|
|
||||||
verify2FASetup: protectedProcedure
|
|
||||||
.input(apiVerify2FA)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const auth = await findAuthById(ctx.user.authId);
|
|
||||||
|
|
||||||
await verify2FA(auth, input.secret, input.pin);
|
|
||||||
await updateAuthById(auth.id, {
|
|
||||||
is2FAEnabled: true,
|
|
||||||
secret: input.secret,
|
|
||||||
});
|
|
||||||
return auth;
|
|
||||||
}),
|
|
||||||
|
|
||||||
verifyLogin2FA: publicProcedure
|
|
||||||
.input(apiVerifyLogin2FA)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const auth = await findAuthById(input.id);
|
|
||||||
|
|
||||||
await verify2FA(auth, auth.secret || "", input.pin);
|
|
||||||
|
|
||||||
const session = await lucia.createSession(auth.id, {});
|
|
||||||
|
|
||||||
ctx.res.appendHeader(
|
|
||||||
"Set-Cookie",
|
|
||||||
lucia.createSessionCookie(session.id).serialize(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
disable2FA: protectedProcedure.mutation(async ({ ctx }) => {
|
|
||||||
const auth = await findAuthById(ctx.user.authId);
|
|
||||||
await updateAuthById(auth.id, {
|
|
||||||
is2FAEnabled: false,
|
|
||||||
secret: null,
|
|
||||||
});
|
|
||||||
return auth;
|
|
||||||
}),
|
|
||||||
verifyToken: protectedProcedure.mutation(async () => {
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
sendResetPasswordEmail: publicProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
email: z.string().min(1).email(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
if (!IS_CLOUD) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "This feature is only available in the cloud version",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const authR = await db.query.auth.findFirst({
|
|
||||||
where: eq(auth.email, input.email),
|
|
||||||
});
|
|
||||||
if (!authR) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "User not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const token = nanoid();
|
|
||||||
await updateAuthById(authR.id, {
|
|
||||||
resetPasswordToken: token,
|
|
||||||
// Make resetPassword in 24 hours
|
|
||||||
resetPasswordExpiresAt: new Date(
|
|
||||||
new Date().getTime() + 24 * 60 * 60 * 1000,
|
|
||||||
).toISOString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const email = await sendEmailNotification(
|
|
||||||
{
|
|
||||||
fromAddress: process.env.SMTP_FROM_ADDRESS || "",
|
|
||||||
toAddresses: [authR.email],
|
|
||||||
smtpServer: process.env.SMTP_SERVER || "",
|
|
||||||
smtpPort: Number(process.env.SMTP_PORT),
|
|
||||||
username: process.env.SMTP_USERNAME || "",
|
|
||||||
password: process.env.SMTP_PASSWORD || "",
|
|
||||||
},
|
|
||||||
"Reset Password",
|
|
||||||
`
|
|
||||||
Reset your password by clicking the link below:
|
|
||||||
The link will expire in 24 hours.
|
|
||||||
<a href="http://localhost:3000/reset-password?token=${token}">
|
|
||||||
Reset Password
|
|
||||||
</a>
|
|
||||||
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
|
|
||||||
resetPassword: publicProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
resetPasswordToken: z.string().min(1),
|
|
||||||
password: z.string().min(1),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
if (!IS_CLOUD) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "This feature is only available in the cloud version",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const authR = await db.query.auth.findFirst({
|
|
||||||
where: eq(auth.resetPasswordToken, input.resetPasswordToken),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!authR || authR.resetPasswordExpiresAt === null) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Token not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const isExpired = isBefore(
|
|
||||||
new Date(authR.resetPasswordExpiresAt),
|
|
||||||
new Date(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isExpired) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Token expired",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateAuthById(authR.id, {
|
|
||||||
resetPasswordExpiresAt: null,
|
|
||||||
resetPasswordToken: null,
|
|
||||||
password: bcrypt.hashSync(input.password, 10),
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiCreateBackup,
|
|
||||||
apiFindOneBackup,
|
|
||||||
apiRemoveBackup,
|
|
||||||
apiUpdateBackup,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import { removeJob, schedule, updateJob } from "@/server/utils/backup";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
createBackup,
|
|
||||||
findBackupById,
|
|
||||||
findMariadbByBackupId,
|
|
||||||
findMongoByBackupId,
|
|
||||||
findMySqlByBackupId,
|
|
||||||
findPostgresByBackupId,
|
|
||||||
findServerById,
|
|
||||||
removeBackupById,
|
|
||||||
removeScheduleBackup,
|
|
||||||
runMariadbBackup,
|
|
||||||
runMongoBackup,
|
|
||||||
runMySqlBackup,
|
|
||||||
runPostgresBackup,
|
|
||||||
scheduleBackup,
|
|
||||||
updateBackupById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
export const backupRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateBackup)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const newBackup = await createBackup(input);
|
|
||||||
|
|
||||||
const backup = await findBackupById(newBackup.backupId);
|
|
||||||
|
|
||||||
if (IS_CLOUD && backup.enabled) {
|
|
||||||
const databaseType = backup.databaseType;
|
|
||||||
let serverId = "";
|
|
||||||
if (databaseType === "postgres" && backup.postgres?.serverId) {
|
|
||||||
serverId = backup.postgres.serverId;
|
|
||||||
} else if (databaseType === "mysql" && backup.mysql?.serverId) {
|
|
||||||
serverId = backup.mysql.serverId;
|
|
||||||
} else if (databaseType === "mongo" && backup.mongo?.serverId) {
|
|
||||||
serverId = backup.mongo.serverId;
|
|
||||||
} else if (databaseType === "mariadb" && backup.mariadb?.serverId) {
|
|
||||||
serverId = backup.mariadb.serverId;
|
|
||||||
}
|
|
||||||
const server = await findServerById(serverId);
|
|
||||||
|
|
||||||
if (server.serverStatus === "inactive") {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Server is inactive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await schedule({
|
|
||||||
cronSchedule: backup.schedule,
|
|
||||||
backupId: backup.backupId,
|
|
||||||
type: "backup",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (backup.enabled) {
|
|
||||||
scheduleBackup(backup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the Backup",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneBackup)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const backup = await findBackupById(input.backupId);
|
|
||||||
|
|
||||||
return backup;
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateBackup)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
await updateBackupById(input.backupId, input);
|
|
||||||
const backup = await findBackupById(input.backupId);
|
|
||||||
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
if (backup.enabled) {
|
|
||||||
await updateJob({
|
|
||||||
cronSchedule: backup.schedule,
|
|
||||||
backupId: backup.backupId,
|
|
||||||
type: "backup",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await removeJob({
|
|
||||||
cronSchedule: backup.schedule,
|
|
||||||
backupId: backup.backupId,
|
|
||||||
type: "backup",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (backup.enabled) {
|
|
||||||
removeScheduleBackup(input.backupId);
|
|
||||||
scheduleBackup(backup);
|
|
||||||
} else {
|
|
||||||
removeScheduleBackup(input.backupId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to update this Backup",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
remove: protectedProcedure
|
|
||||||
.input(apiRemoveBackup)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const value = await removeBackupById(input.backupId);
|
|
||||||
if (IS_CLOUD && value) {
|
|
||||||
removeJob({
|
|
||||||
backupId: input.backupId,
|
|
||||||
cronSchedule: value.schedule,
|
|
||||||
type: "backup",
|
|
||||||
});
|
|
||||||
} else if (!IS_CLOUD) {
|
|
||||||
removeScheduleBackup(input.backupId);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to delete this Backup",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
manualBackupPostgres: protectedProcedure
|
|
||||||
.input(apiFindOneBackup)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
const backup = await findBackupById(input.backupId);
|
|
||||||
const postgres = await findPostgresByBackupId(backup.backupId);
|
|
||||||
await runPostgresBackup(postgres, backup);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to run manual postgres backup ",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
manualBackupMySql: protectedProcedure
|
|
||||||
.input(apiFindOneBackup)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
const backup = await findBackupById(input.backupId);
|
|
||||||
const mysql = await findMySqlByBackupId(backup.backupId);
|
|
||||||
await runMySqlBackup(mysql, backup);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to run manual mysql backup ",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
manualBackupMariadb: protectedProcedure
|
|
||||||
.input(apiFindOneBackup)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
const backup = await findBackupById(input.backupId);
|
|
||||||
const mariadb = await findMariadbByBackupId(backup.backupId);
|
|
||||||
await runMariadbBackup(mariadb, backup);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to run manual mariadb backup ",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
manualBackupMongo: protectedProcedure
|
|
||||||
.input(apiFindOneBackup)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
const backup = await findBackupById(input.backupId);
|
|
||||||
const mongo = await findMongoByBackupId(backup.backupId);
|
|
||||||
await runMongoBackup(mongo, backup);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to run manual mongo backup ",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// export const getAdminId = async (backupId: string) => {
|
|
||||||
// const backup = await findBackupById(backupId);
|
|
||||||
|
|
||||||
// if (backup.databaseType === "postgres" && backup.postgresId) {
|
|
||||||
// const postgres = await findPostgresById(backup.postgresId);
|
|
||||||
// return postgres.project.adminId;
|
|
||||||
// }
|
|
||||||
// if (backup.databaseType === "mariadb" && backup.mariadbId) {
|
|
||||||
// const mariadb = await findMariadbById(backup.mariadbId);
|
|
||||||
// return mariadb.project.adminId;
|
|
||||||
// }
|
|
||||||
// if (backup.databaseType === "mysql" && backup.mysqlId) {
|
|
||||||
// const mysql = await findMySqlById(backup.mysqlId);
|
|
||||||
// return mysql.project.adminId;
|
|
||||||
// }
|
|
||||||
// if (backup.databaseType === "mongo" && backup.mongoId) {
|
|
||||||
// const mongo = await findMongoById(backup.mongoId);
|
|
||||||
// return mongo.project.adminId;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return null;
|
|
||||||
// };
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
apiBitbucketTestConnection,
|
|
||||||
apiCreateBitbucket,
|
|
||||||
apiFindBitbucketBranches,
|
|
||||||
apiFindOneBitbucket,
|
|
||||||
apiUpdateBitbucket,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
createBitbucket,
|
|
||||||
findBitbucketById,
|
|
||||||
getBitbucketBranches,
|
|
||||||
getBitbucketRepositories,
|
|
||||||
testBitbucketConnection,
|
|
||||||
updateBitbucket,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
export const bitbucketRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateBitbucket)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createBitbucket(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create this bitbucket provider",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneBitbucket)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
|
|
||||||
) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this bitbucket provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return bitbucketProvider;
|
|
||||||
}),
|
|
||||||
bitbucketProviders: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
let result = await db.query.bitbucket.findMany({
|
|
||||||
with: {
|
|
||||||
gitProvider: true,
|
|
||||||
},
|
|
||||||
columns: {
|
|
||||||
bitbucketId: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
// TODO: mAyBe a rEfaCtoR 🤫
|
|
||||||
result = result.filter(
|
|
||||||
(provider) => provider.gitProvider.adminId === ctx.user.adminId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
|
|
||||||
getBitbucketRepositories: protectedProcedure
|
|
||||||
.input(apiFindOneBitbucket)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
|
|
||||||
) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this bitbucket provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await getBitbucketRepositories(input.bitbucketId);
|
|
||||||
}),
|
|
||||||
getBitbucketBranches: protectedProcedure
|
|
||||||
.input(apiFindBitbucketBranches)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const bitbucketProvider = await findBitbucketById(
|
|
||||||
input.bitbucketId || "",
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
|
|
||||||
) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this bitbucket provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await getBitbucketBranches(input);
|
|
||||||
}),
|
|
||||||
testConnection: protectedProcedure
|
|
||||||
.input(apiBitbucketTestConnection)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
|
|
||||||
) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this bitbucket provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const result = await testBitbucketConnection(input);
|
|
||||||
|
|
||||||
return `Found ${result} repositories`;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: error instanceof Error ? error?.message : `Error: ${error}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateBitbucket)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
|
|
||||||
) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this bitbucket provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateBitbucket(input.bitbucketId, {
|
|
||||||
...input,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { adminProcedure, createTRPCRouter } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiCreateCertificate,
|
|
||||||
apiFindCertificate,
|
|
||||||
certificates,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
createCertificate,
|
|
||||||
findCertificateById,
|
|
||||||
removeCertificateById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
export const certificateRouter = createTRPCRouter({
|
|
||||||
create: adminProcedure
|
|
||||||
.input(apiCreateCertificate)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Please set a server to create a certificate",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await createCertificate(input, ctx.user.adminId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
one: adminProcedure
|
|
||||||
.input(apiFindCertificate)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const certificates = await findCertificateById(input.certificateId);
|
|
||||||
if (IS_CLOUD && certificates.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this certificate",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return certificates;
|
|
||||||
}),
|
|
||||||
remove: adminProcedure
|
|
||||||
.input(apiFindCertificate)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const certificates = await findCertificateById(input.certificateId);
|
|
||||||
if (IS_CLOUD && certificates.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to delete this certificate",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await removeCertificateById(input.certificateId);
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
all: adminProcedure.query(async ({ ctx }) => {
|
|
||||||
return await db.query.certificates.findMany({
|
|
||||||
// TODO: Remove this line when the cloud version is ready
|
|
||||||
...(IS_CLOUD && { where: eq(certificates.adminId, ctx.user.adminId) }),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { getPublicIpWithFallback } from "@/server/wss/terminal";
|
|
||||||
import { type DockerNode, IS_CLOUD, docker, execAsync } from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
||||||
|
|
||||||
export const clusterRouter = createTRPCRouter({
|
|
||||||
getNodes: protectedProcedure.query(async () => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const workers: DockerNode[] = await docker.listNodes();
|
|
||||||
|
|
||||||
return workers;
|
|
||||||
}),
|
|
||||||
removeWorker: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
nodeId: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Functionality not available in cloud version",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await execAsync(
|
|
||||||
`docker node update --availability drain ${input.nodeId}`,
|
|
||||||
);
|
|
||||||
await execAsync(`docker node rm ${input.nodeId} --force`);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: "Error to remove the node",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
addWorker: protectedProcedure.query(async ({ input }) => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
return {
|
|
||||||
command: "",
|
|
||||||
version: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const result = await docker.swarmInspect();
|
|
||||||
const docker_version = await docker.version();
|
|
||||||
|
|
||||||
return {
|
|
||||||
command: `docker swarm join --token ${
|
|
||||||
result.JoinTokens.Worker
|
|
||||||
} ${await getPublicIpWithFallback()}:2377`,
|
|
||||||
version: docker_version.Version,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
addManager: protectedProcedure.query(async ({ input }) => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
return {
|
|
||||||
command: "",
|
|
||||||
version: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const result = await docker.swarmInspect();
|
|
||||||
const docker_version = await docker.version();
|
|
||||||
return {
|
|
||||||
command: `docker swarm join --token ${
|
|
||||||
result.JoinTokens.Manager
|
|
||||||
} ${await getPublicIpWithFallback()}:2377`,
|
|
||||||
version: docker_version.Version,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,448 +0,0 @@
|
|||||||
import { slugify } from "@/lib/slug";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
apiCreateCompose,
|
|
||||||
apiCreateComposeByTemplate,
|
|
||||||
apiFetchServices,
|
|
||||||
apiFindCompose,
|
|
||||||
apiRandomizeCompose,
|
|
||||||
apiUpdateCompose,
|
|
||||||
compose,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
type DeploymentJob,
|
|
||||||
cleanQueuesByCompose,
|
|
||||||
} from "@/server/queues/deployments-queue";
|
|
||||||
import { myQueue } from "@/server/queues/queueSetup";
|
|
||||||
import { templates } from "@/templates/templates";
|
|
||||||
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
|
|
||||||
import {
|
|
||||||
generatePassword,
|
|
||||||
loadTemplateModule,
|
|
||||||
readTemplateComposeFile,
|
|
||||||
} from "@/templates/utils";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { dump } from "js-yaml";
|
|
||||||
import _ from "lodash";
|
|
||||||
import { nanoid } from "nanoid";
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
||||||
|
|
||||||
import { deploy } from "@/server/utils/deploy";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
addDomainToCompose,
|
|
||||||
addNewService,
|
|
||||||
checkServiceAccess,
|
|
||||||
cloneCompose,
|
|
||||||
cloneComposeRemote,
|
|
||||||
createCommand,
|
|
||||||
createCompose,
|
|
||||||
createComposeByTemplate,
|
|
||||||
createDomain,
|
|
||||||
createMount,
|
|
||||||
findAdmin,
|
|
||||||
findAdminById,
|
|
||||||
findComposeById,
|
|
||||||
findDomainsByComposeId,
|
|
||||||
findProjectById,
|
|
||||||
findServerById,
|
|
||||||
loadServices,
|
|
||||||
randomizeComposeFile,
|
|
||||||
removeCompose,
|
|
||||||
removeComposeDirectory,
|
|
||||||
removeDeploymentsByComposeId,
|
|
||||||
stopCompose,
|
|
||||||
updateCompose,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
|
|
||||||
export const composeRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateCompose)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You need to use a server to create a compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const project = await findProjectById(input.projectId);
|
|
||||||
if (project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this project",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const newService = await createCompose(input);
|
|
||||||
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await addNewService(ctx.user.authId, newService.composeId);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.composeId, "access");
|
|
||||||
}
|
|
||||||
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return compose;
|
|
||||||
}),
|
|
||||||
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return updateCompose(input.composeId, input);
|
|
||||||
}),
|
|
||||||
delete: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.composeId, "delete");
|
|
||||||
}
|
|
||||||
const composeResult = await findComposeById(input.composeId);
|
|
||||||
|
|
||||||
if (composeResult.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
4;
|
|
||||||
|
|
||||||
const result = await db
|
|
||||||
.delete(compose)
|
|
||||||
.where(eq(compose.composeId, input.composeId))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
const cleanupOperations = [
|
|
||||||
async () => await removeCompose(composeResult),
|
|
||||||
async () => await removeDeploymentsByComposeId(composeResult),
|
|
||||||
async () => await removeComposeDirectory(composeResult.appName),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const operation of cleanupOperations) {
|
|
||||||
try {
|
|
||||||
await operation();
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result[0];
|
|
||||||
}),
|
|
||||||
cleanQueues: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to clean this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await cleanQueuesByCompose(input.composeId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
loadServices: protectedProcedure
|
|
||||||
.input(apiFetchServices)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to load this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await loadServices(input.composeId, input.type);
|
|
||||||
}),
|
|
||||||
fetchSourceType: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to fetch this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (compose.serverId) {
|
|
||||||
await cloneComposeRemote(compose);
|
|
||||||
} else {
|
|
||||||
await cloneCompose(compose);
|
|
||||||
}
|
|
||||||
return compose.sourceType;
|
|
||||||
} catch (err) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to fetch source type",
|
|
||||||
cause: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
randomizeCompose: protectedProcedure
|
|
||||||
.input(apiRandomizeCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to randomize this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await randomizeComposeFile(input.composeId, input.suffix);
|
|
||||||
}),
|
|
||||||
getConvertedCompose: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to get this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const domains = await findDomainsByComposeId(input.composeId);
|
|
||||||
const composeFile = await addDomainToCompose(compose, domains);
|
|
||||||
return dump(composeFile, {
|
|
||||||
lineWidth: 1000,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
deploy: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to deploy this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const jobData: DeploymentJob = {
|
|
||||||
composeId: input.composeId,
|
|
||||||
titleLog: "Manual deployment",
|
|
||||||
type: "deploy",
|
|
||||||
applicationType: "compose",
|
|
||||||
descriptionLog: "",
|
|
||||||
server: !!compose.serverId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (IS_CLOUD && compose.serverId) {
|
|
||||||
jobData.serverId = compose.serverId;
|
|
||||||
await deploy(jobData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
await myQueue.add(
|
|
||||||
"deployments",
|
|
||||||
{ ...jobData },
|
|
||||||
{
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
redeploy: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to redeploy this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const jobData: DeploymentJob = {
|
|
||||||
composeId: input.composeId,
|
|
||||||
titleLog: "Rebuild deployment",
|
|
||||||
type: "redeploy",
|
|
||||||
applicationType: "compose",
|
|
||||||
descriptionLog: "",
|
|
||||||
server: !!compose.serverId,
|
|
||||||
};
|
|
||||||
if (IS_CLOUD && compose.serverId) {
|
|
||||||
jobData.serverId = compose.serverId;
|
|
||||||
await deploy(jobData);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
await myQueue.add(
|
|
||||||
"deployments",
|
|
||||||
{ ...jobData },
|
|
||||||
{
|
|
||||||
removeOnComplete: true,
|
|
||||||
removeOnFail: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
stop: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to stop this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await stopCompose(input.composeId);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
getDefaultCommand: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to get this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const command = createCommand(compose);
|
|
||||||
return `docker ${command}`;
|
|
||||||
}),
|
|
||||||
refreshToken: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to refresh this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateCompose(input.composeId, {
|
|
||||||
refreshToken: nanoid(),
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
deployTemplate: protectedProcedure
|
|
||||||
.input(apiCreateComposeByTemplate)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You need to use a server to create a compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const composeFile = await readTemplateComposeFile(input.id);
|
|
||||||
|
|
||||||
const generate = await loadTemplateModule(input.id as TemplatesKeys);
|
|
||||||
|
|
||||||
const admin = await findAdminById(ctx.user.adminId);
|
|
||||||
let serverIp = admin.serverIp;
|
|
||||||
|
|
||||||
if (!admin.serverIp) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message:
|
|
||||||
"You need to have a server IP to deploy this template in order to generate domains",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const project = await findProjectById(input.projectId);
|
|
||||||
|
|
||||||
if (input.serverId) {
|
|
||||||
const server = await findServerById(input.serverId);
|
|
||||||
serverIp = server.ipAddress;
|
|
||||||
} else if (process.env.NODE_ENV === "development") {
|
|
||||||
serverIp = "127.0.0.1";
|
|
||||||
}
|
|
||||||
const projectName = slugify(`${project.name} ${input.id}`);
|
|
||||||
const { envs, mounts, domains } = generate({
|
|
||||||
serverIp: serverIp || "",
|
|
||||||
projectName: projectName,
|
|
||||||
});
|
|
||||||
|
|
||||||
const compose = await createComposeByTemplate({
|
|
||||||
...input,
|
|
||||||
composeFile: composeFile,
|
|
||||||
env: envs?.join("\n"),
|
|
||||||
serverId: input.serverId,
|
|
||||||
name: input.id,
|
|
||||||
sourceType: "raw",
|
|
||||||
appName: `${projectName}-${generatePassword(6)}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await addNewService(ctx.user.authId, compose.composeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounts && mounts?.length > 0) {
|
|
||||||
for (const mount of mounts) {
|
|
||||||
await createMount({
|
|
||||||
filePath: mount.filePath,
|
|
||||||
mountPath: "",
|
|
||||||
content: mount.content,
|
|
||||||
serviceId: compose.composeId,
|
|
||||||
serviceType: "compose",
|
|
||||||
type: "file",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (domains && domains?.length > 0) {
|
|
||||||
for (const domain of domains) {
|
|
||||||
await createDomain({
|
|
||||||
...domain,
|
|
||||||
domainType: "compose",
|
|
||||||
certificateType: "none",
|
|
||||||
composeId: compose.composeId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}),
|
|
||||||
|
|
||||||
templates: protectedProcedure.query(async () => {
|
|
||||||
const templatesData = templates.map((t) => ({
|
|
||||||
name: t.name,
|
|
||||||
description: t.description,
|
|
||||||
id: t.id,
|
|
||||||
links: t.links,
|
|
||||||
tags: t.tags,
|
|
||||||
logo: t.logo,
|
|
||||||
version: t.version,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return templatesData;
|
|
||||||
}),
|
|
||||||
|
|
||||||
getTags: protectedProcedure.query(async ({ input }) => {
|
|
||||||
const allTags = templates.flatMap((template) => template.tags);
|
|
||||||
const uniqueTags = _.uniq(allTags);
|
|
||||||
return uniqueTags;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import {
|
|
||||||
apiFindAllByApplication,
|
|
||||||
apiFindAllByCompose,
|
|
||||||
apiFindAllByServer,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
findAllDeploymentsByApplicationId,
|
|
||||||
findAllDeploymentsByComposeId,
|
|
||||||
findAllDeploymentsByServerId,
|
|
||||||
findApplicationById,
|
|
||||||
findComposeById,
|
|
||||||
findServerById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
||||||
|
|
||||||
export const deploymentRouter = createTRPCRouter({
|
|
||||||
all: protectedProcedure
|
|
||||||
.input(apiFindAllByApplication)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await findAllDeploymentsByApplicationId(input.applicationId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
allByCompose: protectedProcedure
|
|
||||||
.input(apiFindAllByCompose)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await findAllDeploymentsByComposeId(input.composeId);
|
|
||||||
}),
|
|
||||||
allByServer: protectedProcedure
|
|
||||||
.input(apiFindAllByServer)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const server = await findServerById(input.serverId);
|
|
||||||
if (server.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this server",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await findAllDeploymentsByServerId(input.serverId);
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import {
|
|
||||||
adminProcedure,
|
|
||||||
createTRPCRouter,
|
|
||||||
protectedProcedure,
|
|
||||||
} from "@/server/api/trpc";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
apiCreateDestination,
|
|
||||||
apiFindOneDestination,
|
|
||||||
apiRemoveDestination,
|
|
||||||
apiUpdateDestination,
|
|
||||||
destinations,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
createDestintation,
|
|
||||||
execAsync,
|
|
||||||
execAsyncRemote,
|
|
||||||
findDestinationById,
|
|
||||||
removeDestinationById,
|
|
||||||
updateDestinationById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
export const destinationRouter = createTRPCRouter({
|
|
||||||
create: adminProcedure
|
|
||||||
.input(apiCreateDestination)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createDestintation(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the destination",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
testConnection: adminProcedure
|
|
||||||
.input(apiCreateDestination)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
const { secretAccessKey, bucket, region, endpoint, accessKey } = input;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const rcloneFlags = [
|
|
||||||
// `--s3-provider=Cloudflare`,
|
|
||||||
`--s3-access-key-id=${accessKey}`,
|
|
||||||
`--s3-secret-access-key=${secretAccessKey}`,
|
|
||||||
`--s3-region=${region}`,
|
|
||||||
`--s3-endpoint=${endpoint}`,
|
|
||||||
"--s3-no-check-bucket",
|
|
||||||
"--s3-force-path-style",
|
|
||||||
];
|
|
||||||
const rcloneDestination = `:s3:${bucket}`;
|
|
||||||
const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`;
|
|
||||||
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Server not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
await execAsyncRemote(input.serverId || "", rcloneCommand);
|
|
||||||
} else {
|
|
||||||
await execAsync(rcloneCommand);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message:
|
|
||||||
error instanceof Error
|
|
||||||
? error?.message
|
|
||||||
: "Error to connect to bucket",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneDestination)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const destination = await findDestinationById(input.destinationId);
|
|
||||||
if (destination.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this destination",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return destination;
|
|
||||||
}),
|
|
||||||
all: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
return await db.query.destinations.findMany({
|
|
||||||
where: eq(destinations.adminId, ctx.user.adminId),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
remove: adminProcedure
|
|
||||||
.input(apiRemoveDestination)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const destination = await findDestinationById(input.destinationId);
|
|
||||||
|
|
||||||
if (destination.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to delete this destination",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await removeDestinationById(
|
|
||||||
input.destinationId,
|
|
||||||
ctx.user.adminId,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
update: adminProcedure
|
|
||||||
.input(apiUpdateDestination)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const destination = await findDestinationById(input.destinationId);
|
|
||||||
if (destination.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to update this destination",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateDestinationById(input.destinationId, {
|
|
||||||
...input,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import {
|
|
||||||
containerRestart,
|
|
||||||
getConfig,
|
|
||||||
getContainers,
|
|
||||||
getContainersByAppLabel,
|
|
||||||
getContainersByAppNameMatch,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
||||||
|
|
||||||
export const dockerRouter = createTRPCRouter({
|
|
||||||
getContainers: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
serverId: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getContainers(input.serverId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
restartContainer: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
containerId: z.string().min(1),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await containerRestart(input.containerId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
getConfig: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
containerId: z.string().min(1),
|
|
||||||
serverId: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getConfig(input.containerId, input.serverId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
getContainersByAppNameMatch: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
appType: z
|
|
||||||
.union([z.literal("stack"), z.literal("docker-compose")])
|
|
||||||
.optional(),
|
|
||||||
appName: z.string().min(1),
|
|
||||||
serverId: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getContainersByAppNameMatch(
|
|
||||||
input.appName,
|
|
||||||
input.appType,
|
|
||||||
input.serverId,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
|
|
||||||
getContainersByAppLabel: protectedProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
appName: z.string().min(1),
|
|
||||||
serverId: z.string().optional(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getContainersByAppLabel(input.appName, input.serverId);
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiCreateDomain,
|
|
||||||
apiFindCompose,
|
|
||||||
apiFindDomain,
|
|
||||||
apiFindOneApplication,
|
|
||||||
apiUpdateDomain,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
createDomain,
|
|
||||||
findApplicationById,
|
|
||||||
findComposeById,
|
|
||||||
findDomainById,
|
|
||||||
findDomainsByApplicationId,
|
|
||||||
findDomainsByComposeId,
|
|
||||||
generateTraefikMeDomain,
|
|
||||||
manageDomain,
|
|
||||||
removeDomain,
|
|
||||||
removeDomainById,
|
|
||||||
updateDomainById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const domainRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateDomain)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (input.domainType === "compose" && input.composeId) {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (input.domainType === "application" && input.applicationId) {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await createDomain(input);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the domain",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
byApplicationId: protectedProcedure
|
|
||||||
.input(apiFindOneApplication)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const application = await findApplicationById(input.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await findDomainsByApplicationId(input.applicationId);
|
|
||||||
}),
|
|
||||||
byComposeId: protectedProcedure
|
|
||||||
.input(apiFindCompose)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const compose = await findComposeById(input.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await findDomainsByComposeId(input.composeId);
|
|
||||||
}),
|
|
||||||
generateDomain: protectedProcedure
|
|
||||||
.input(z.object({ appName: z.string(), serverId: z.string().optional() }))
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
return generateTraefikMeDomain(
|
|
||||||
input.appName,
|
|
||||||
ctx.user.adminId,
|
|
||||||
input.serverId,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateDomain)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const currentDomain = await findDomainById(input.domainId);
|
|
||||||
|
|
||||||
if (currentDomain.applicationId) {
|
|
||||||
const newApp = await findApplicationById(currentDomain.applicationId);
|
|
||||||
if (newApp.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (currentDomain.composeId) {
|
|
||||||
const newCompose = await findComposeById(currentDomain.composeId);
|
|
||||||
if (newCompose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const result = await updateDomainById(input.domainId, input);
|
|
||||||
const domain = await findDomainById(input.domainId);
|
|
||||||
if (domain.applicationId) {
|
|
||||||
const application = await findApplicationById(domain.applicationId);
|
|
||||||
await manageDomain(application, domain);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
one: protectedProcedure.input(apiFindDomain).query(async ({ input, ctx }) => {
|
|
||||||
const domain = await findDomainById(input.domainId);
|
|
||||||
if (domain.applicationId) {
|
|
||||||
const application = await findApplicationById(domain.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (domain.composeId) {
|
|
||||||
const compose = await findComposeById(domain.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await findDomainById(input.domainId);
|
|
||||||
}),
|
|
||||||
delete: protectedProcedure
|
|
||||||
.input(apiFindDomain)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const domain = await findDomainById(input.domainId);
|
|
||||||
if (domain.applicationId) {
|
|
||||||
const application = await findApplicationById(domain.applicationId);
|
|
||||||
if (application.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this application",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (domain.composeId) {
|
|
||||||
const compose = await findComposeById(domain.composeId);
|
|
||||||
if (compose.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this compose",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const result = await removeDomainById(input.domainId);
|
|
||||||
|
|
||||||
if (domain.applicationId) {
|
|
||||||
const application = await findApplicationById(domain.applicationId);
|
|
||||||
await removeDomain(application, domain.uniqueConfigKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
findGitProviderById,
|
|
||||||
removeGitProvider,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { desc, eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
export const gitProviderRouter = createTRPCRouter({
|
|
||||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
return await db.query.gitProvider.findMany({
|
|
||||||
with: {
|
|
||||||
gitlab: true,
|
|
||||||
bitbucket: true,
|
|
||||||
github: true,
|
|
||||||
},
|
|
||||||
orderBy: desc(gitProvider.createdAt),
|
|
||||||
...(IS_CLOUD && { where: eq(gitProvider.adminId, ctx.user.adminId) }),
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
remove: protectedProcedure
|
|
||||||
.input(apiRemoveGitProvider)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const gitProvider = await findGitProviderById(input.gitProviderId);
|
|
||||||
|
|
||||||
if (IS_CLOUD && gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
// TODO: Remove isCloud in the next versions of dokploy
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to delete this git provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await removeGitProvider(input.gitProviderId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to delete this git provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
apiFindGithubBranches,
|
|
||||||
apiFindOneGithub,
|
|
||||||
apiUpdateGithub,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
findGithubById,
|
|
||||||
getGithubBranches,
|
|
||||||
getGithubRepositories,
|
|
||||||
haveGithubRequirements,
|
|
||||||
updateGitProvider,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
export const githubRouter = createTRPCRouter({
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneGithub)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const githubProvider = await findGithubById(input.githubId);
|
|
||||||
if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this github provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return githubProvider;
|
|
||||||
}),
|
|
||||||
getGithubRepositories: protectedProcedure
|
|
||||||
.input(apiFindOneGithub)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const githubProvider = await findGithubById(input.githubId);
|
|
||||||
if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this github provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await getGithubRepositories(input.githubId);
|
|
||||||
}),
|
|
||||||
getGithubBranches: protectedProcedure
|
|
||||||
.input(apiFindGithubBranches)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const githubProvider = await findGithubById(input.githubId || "");
|
|
||||||
if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this github provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await getGithubBranches(input);
|
|
||||||
}),
|
|
||||||
githubProviders: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
let result = await db.query.github.findMany({
|
|
||||||
with: {
|
|
||||||
gitProvider: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
// TODO: mAyBe a rEfaCtoR 🤫
|
|
||||||
result = result.filter(
|
|
||||||
(provider) => provider.gitProvider.adminId === ctx.user.adminId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filtered = result
|
|
||||||
.filter((provider) => haveGithubRequirements(provider))
|
|
||||||
.map((provider) => {
|
|
||||||
return {
|
|
||||||
githubId: provider.githubId,
|
|
||||||
gitProvider: {
|
|
||||||
...provider.gitProvider,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
}),
|
|
||||||
|
|
||||||
testConnection: protectedProcedure
|
|
||||||
.input(apiFindOneGithub)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const githubProvider = await findGithubById(input.githubId);
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
githubProvider.gitProvider.adminId !== ctx.user.adminId
|
|
||||||
) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this github provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const result = await getGithubRepositories(input.githubId);
|
|
||||||
return `Found ${result.length} repositories`;
|
|
||||||
} catch (err) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: err instanceof Error ? err?.message : `Error: ${err}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateGithub)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const githubProvider = await findGithubById(input.githubId);
|
|
||||||
if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this github provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateGitProvider(input.gitProviderId, {
|
|
||||||
name: input.name,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiCreateGitlab,
|
|
||||||
apiFindGitlabBranches,
|
|
||||||
apiFindOneGitlab,
|
|
||||||
apiGitlabTestConnection,
|
|
||||||
apiUpdateGitlab,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
createGitlab,
|
|
||||||
findGitlabById,
|
|
||||||
getGitlabBranches,
|
|
||||||
getGitlabRepositories,
|
|
||||||
haveGitlabRequirements,
|
|
||||||
testGitlabConnection,
|
|
||||||
updateGitProvider,
|
|
||||||
updateGitlab,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
export const gitlabRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateGitlab)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createGitlab(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create this gitlab provider",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneGitlab)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const gitlabProvider = await findGitlabById(input.gitlabId);
|
|
||||||
if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this gitlab provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return gitlabProvider;
|
|
||||||
}),
|
|
||||||
gitlabProviders: protectedProcedure.query(async ({ ctx }) => {
|
|
||||||
let result = await db.query.gitlab.findMany({
|
|
||||||
with: {
|
|
||||||
gitProvider: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
// TODO: mAyBe a rEfaCtoR 🤫
|
|
||||||
result = result.filter(
|
|
||||||
(provider) => provider.gitProvider.adminId === ctx.user.adminId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const filtered = result
|
|
||||||
.filter((provider) => haveGitlabRequirements(provider))
|
|
||||||
.map((provider) => {
|
|
||||||
return {
|
|
||||||
gitlabId: provider.gitlabId,
|
|
||||||
gitProvider: {
|
|
||||||
...provider.gitProvider,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return filtered;
|
|
||||||
}),
|
|
||||||
getGitlabRepositories: protectedProcedure
|
|
||||||
.input(apiFindOneGitlab)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const gitlabProvider = await findGitlabById(input.gitlabId);
|
|
||||||
if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this gitlab provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await getGitlabRepositories(input.gitlabId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
getGitlabBranches: protectedProcedure
|
|
||||||
.input(apiFindGitlabBranches)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const gitlabProvider = await findGitlabById(input.gitlabId || "");
|
|
||||||
if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this gitlab provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await getGitlabBranches(input);
|
|
||||||
}),
|
|
||||||
testConnection: protectedProcedure
|
|
||||||
.input(apiGitlabTestConnection)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const gitlabProvider = await findGitlabById(input.gitlabId || "");
|
|
||||||
if (
|
|
||||||
IS_CLOUD &&
|
|
||||||
gitlabProvider.gitProvider.adminId !== ctx.user.adminId
|
|
||||||
) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this gitlab provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const result = await testGitlabConnection(input);
|
|
||||||
|
|
||||||
return `Found ${result} repositories`;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: error instanceof Error ? error?.message : `Error: ${error}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateGitlab)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const gitlabProvider = await findGitlabById(input.gitlabId);
|
|
||||||
if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
|
|
||||||
//TODO: Remove this line when the cloud version is ready
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not allowed to access this gitlab provider",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (input.name) {
|
|
||||||
await updateGitProvider(input.gitProviderId, {
|
|
||||||
name: input.name,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await updateGitlab(input.gitlabId, {
|
|
||||||
...input,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiChangeMariaDBStatus,
|
|
||||||
apiCreateMariaDB,
|
|
||||||
apiDeployMariaDB,
|
|
||||||
apiFindOneMariaDB,
|
|
||||||
apiResetMariadb,
|
|
||||||
apiSaveEnvironmentVariablesMariaDB,
|
|
||||||
apiSaveExternalPortMariaDB,
|
|
||||||
apiUpdateMariaDB,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
addNewService,
|
|
||||||
checkServiceAccess,
|
|
||||||
createMariadb,
|
|
||||||
createMount,
|
|
||||||
deployMariadb,
|
|
||||||
findMariadbById,
|
|
||||||
findProjectById,
|
|
||||||
findServerById,
|
|
||||||
removeMariadbById,
|
|
||||||
removeService,
|
|
||||||
startService,
|
|
||||||
startServiceRemote,
|
|
||||||
stopService,
|
|
||||||
stopServiceRemote,
|
|
||||||
updateMariadbById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
export const mariadbRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateMariaDB)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You need to use a server to create a mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const project = await findProjectById(input.projectId);
|
|
||||||
if (project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this project",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const newMariadb = await createMariadb(input);
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await addNewService(ctx.user.authId, newMariadb.mariadbId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await createMount({
|
|
||||||
serviceId: newMariadb.mariadbId,
|
|
||||||
serviceType: "mariadb",
|
|
||||||
volumeName: `${newMariadb.appName}-data`,
|
|
||||||
mountPath: "/var/lib/mysql",
|
|
||||||
type: "volume",
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof TRPCError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneMariaDB)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.mariadbId, "access");
|
|
||||||
}
|
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
|
||||||
if (mariadb.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return mariadb;
|
|
||||||
}),
|
|
||||||
|
|
||||||
start: protectedProcedure
|
|
||||||
.input(apiFindOneMariaDB)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const service = await findMariadbById(input.mariadbId);
|
|
||||||
if (service.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to start this mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (service.serverId) {
|
|
||||||
await startServiceRemote(service.serverId, service.appName);
|
|
||||||
} else {
|
|
||||||
await startService(service.appName);
|
|
||||||
}
|
|
||||||
await updateMariadbById(input.mariadbId, {
|
|
||||||
applicationStatus: "done",
|
|
||||||
});
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}),
|
|
||||||
stop: protectedProcedure
|
|
||||||
.input(apiFindOneMariaDB)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
|
||||||
|
|
||||||
if (mariadb.serverId) {
|
|
||||||
await stopServiceRemote(mariadb.serverId, mariadb.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(mariadb.appName);
|
|
||||||
}
|
|
||||||
await updateMariadbById(input.mariadbId, {
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
return mariadb;
|
|
||||||
}),
|
|
||||||
saveExternalPort: protectedProcedure
|
|
||||||
.input(apiSaveExternalPortMariaDB)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMariadbById(input.mariadbId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this external port",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateMariadbById(input.mariadbId, {
|
|
||||||
externalPort: input.externalPort,
|
|
||||||
});
|
|
||||||
await deployMariadb(input.mariadbId);
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
deploy: protectedProcedure
|
|
||||||
.input(apiDeployMariaDB)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
|
||||||
if (mariadb.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to deploy this mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deployMariadb(input.mariadbId);
|
|
||||||
}),
|
|
||||||
changeStatus: protectedProcedure
|
|
||||||
.input(apiChangeMariaDBStatus)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMariadbById(input.mariadbId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to change this mariadb status",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateMariadbById(input.mariadbId, {
|
|
||||||
applicationStatus: input.applicationStatus,
|
|
||||||
});
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
remove: protectedProcedure
|
|
||||||
.input(apiFindOneMariaDB)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.mariadbId, "delete");
|
|
||||||
}
|
|
||||||
|
|
||||||
const mongo = await findMariadbById(input.mariadbId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanupOperations = [
|
|
||||||
async () => await removeService(mongo?.appName, mongo.serverId),
|
|
||||||
async () => await removeMariadbById(input.mariadbId),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const operation of cleanupOperations) {
|
|
||||||
try {
|
|
||||||
await operation();
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
saveEnvironment: protectedProcedure
|
|
||||||
.input(apiSaveEnvironmentVariablesMariaDB)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
|
||||||
if (mariadb.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const service = await updateMariadbById(input.mariadbId, {
|
|
||||||
env: input.env,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Update: Error to add environment variables",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
reload: protectedProcedure
|
|
||||||
.input(apiResetMariadb)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mariadb = await findMariadbById(input.mariadbId);
|
|
||||||
if (mariadb.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to reload this mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (mariadb.serverId) {
|
|
||||||
await stopServiceRemote(mariadb.serverId, mariadb.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(mariadb.appName);
|
|
||||||
}
|
|
||||||
await updateMariadbById(input.mariadbId, {
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mariadb.serverId) {
|
|
||||||
await startServiceRemote(mariadb.serverId, mariadb.appName);
|
|
||||||
} else {
|
|
||||||
await startService(mariadb.appName);
|
|
||||||
}
|
|
||||||
await updateMariadbById(input.mariadbId, {
|
|
||||||
applicationStatus: "done",
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateMariaDB)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { mariadbId, ...rest } = input;
|
|
||||||
const mariadb = await findMariadbById(mariadbId);
|
|
||||||
if (mariadb.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const service = await updateMariadbById(mariadbId, {
|
|
||||||
...rest,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Update: Error to update mariadb",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiChangeMongoStatus,
|
|
||||||
apiCreateMongo,
|
|
||||||
apiDeployMongo,
|
|
||||||
apiFindOneMongo,
|
|
||||||
apiResetMongo,
|
|
||||||
apiSaveEnvironmentVariablesMongo,
|
|
||||||
apiSaveExternalPortMongo,
|
|
||||||
apiUpdateMongo,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
addNewService,
|
|
||||||
checkServiceAccess,
|
|
||||||
createMongo,
|
|
||||||
createMount,
|
|
||||||
deployMongo,
|
|
||||||
findMongoById,
|
|
||||||
findProjectById,
|
|
||||||
removeMongoById,
|
|
||||||
removeService,
|
|
||||||
startService,
|
|
||||||
startServiceRemote,
|
|
||||||
stopService,
|
|
||||||
stopServiceRemote,
|
|
||||||
updateMongoById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
export const mongoRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You need to use a server to create a mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const project = await findProjectById(input.projectId);
|
|
||||||
if (project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this project",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const newMongo = await createMongo(input);
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await addNewService(ctx.user.authId, newMongo.mongoId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await createMount({
|
|
||||||
serviceId: newMongo.mongoId,
|
|
||||||
serviceType: "mongo",
|
|
||||||
volumeName: `${newMongo.appName}-data`,
|
|
||||||
mountPath: "/data/db",
|
|
||||||
type: "volume",
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof TRPCError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error input: Inserting mongo database",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneMongo)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.mongoId, "access");
|
|
||||||
}
|
|
||||||
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
|
|
||||||
start: protectedProcedure
|
|
||||||
.input(apiFindOneMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const service = await findMongoById(input.mongoId);
|
|
||||||
|
|
||||||
if (service.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to start this mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service.serverId) {
|
|
||||||
await startServiceRemote(service.serverId, service.appName);
|
|
||||||
} else {
|
|
||||||
await startService(service.appName);
|
|
||||||
}
|
|
||||||
await updateMongoById(input.mongoId, {
|
|
||||||
applicationStatus: "done",
|
|
||||||
});
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}),
|
|
||||||
stop: protectedProcedure
|
|
||||||
.input(apiFindOneMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to stop this mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mongo.serverId) {
|
|
||||||
await stopServiceRemote(mongo.serverId, mongo.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(mongo.appName);
|
|
||||||
}
|
|
||||||
await updateMongoById(input.mongoId, {
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
saveExternalPort: protectedProcedure
|
|
||||||
.input(apiSaveExternalPortMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this external port",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateMongoById(input.mongoId, {
|
|
||||||
externalPort: input.externalPort,
|
|
||||||
});
|
|
||||||
await deployMongo(input.mongoId);
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
deploy: protectedProcedure
|
|
||||||
.input(apiDeployMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to deploy this mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return deployMongo(input.mongoId);
|
|
||||||
}),
|
|
||||||
changeStatus: protectedProcedure
|
|
||||||
.input(apiChangeMongoStatus)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to change this mongo status",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateMongoById(input.mongoId, {
|
|
||||||
applicationStatus: input.applicationStatus,
|
|
||||||
});
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
reload: protectedProcedure
|
|
||||||
.input(apiResetMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to reload this mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (mongo.serverId) {
|
|
||||||
await stopServiceRemote(mongo.serverId, mongo.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(mongo.appName);
|
|
||||||
}
|
|
||||||
await updateMongoById(input.mongoId, {
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mongo.serverId) {
|
|
||||||
await startServiceRemote(mongo.serverId, mongo.appName);
|
|
||||||
} else {
|
|
||||||
await startService(mongo.appName);
|
|
||||||
}
|
|
||||||
await updateMongoById(input.mongoId, {
|
|
||||||
applicationStatus: "done",
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
remove: protectedProcedure
|
|
||||||
.input(apiFindOneMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.mongoId, "delete");
|
|
||||||
}
|
|
||||||
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanupOperations = [
|
|
||||||
async () => await removeService(mongo?.appName, mongo.serverId),
|
|
||||||
async () => await removeMongoById(input.mongoId),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const operation of cleanupOperations) {
|
|
||||||
try {
|
|
||||||
await operation();
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
saveEnvironment: protectedProcedure
|
|
||||||
.input(apiSaveEnvironmentVariablesMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMongoById(input.mongoId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const service = await updateMongoById(input.mongoId, {
|
|
||||||
env: input.env,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Update: Error to add environment variables",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateMongo)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { mongoId, ...rest } = input;
|
|
||||||
const mongo = await findMongoById(mongoId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const service = await updateMongoById(mongoId, {
|
|
||||||
...rest,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Update: Error to update mongo",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import {
|
|
||||||
apiCreateMount,
|
|
||||||
apiFindOneMount,
|
|
||||||
apiRemoveMount,
|
|
||||||
apiUpdateMount,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
createMount,
|
|
||||||
deleteMount,
|
|
||||||
findMountById,
|
|
||||||
updateMount,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
||||||
|
|
||||||
export const mountRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateMount)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
await createMount(input);
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
remove: protectedProcedure
|
|
||||||
.input(apiRemoveMount)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await deleteMount(input.mountId);
|
|
||||||
}),
|
|
||||||
|
|
||||||
one: protectedProcedure.input(apiFindOneMount).query(async ({ input }) => {
|
|
||||||
return await findMountById(input.mountId);
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateMount)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
await updateMount(input.mountId, input);
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
|
||||||
import {
|
|
||||||
apiChangeMySqlStatus,
|
|
||||||
apiCreateMySql,
|
|
||||||
apiDeployMySql,
|
|
||||||
apiFindOneMySql,
|
|
||||||
apiResetMysql,
|
|
||||||
apiSaveEnvironmentVariablesMySql,
|
|
||||||
apiSaveExternalPortMySql,
|
|
||||||
apiUpdateMySql,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
addNewService,
|
|
||||||
checkServiceAccess,
|
|
||||||
createMount,
|
|
||||||
createMysql,
|
|
||||||
deployMySql,
|
|
||||||
findMySqlById,
|
|
||||||
findProjectById,
|
|
||||||
removeMySqlById,
|
|
||||||
removeService,
|
|
||||||
startService,
|
|
||||||
startServiceRemote,
|
|
||||||
stopService,
|
|
||||||
stopServiceRemote,
|
|
||||||
updateMySqlById,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
|
|
||||||
export const mysqlRouter = createTRPCRouter({
|
|
||||||
create: protectedProcedure
|
|
||||||
.input(apiCreateMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_CLOUD && !input.serverId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You need to use a server to create a mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
1;
|
|
||||||
const project = await findProjectById(input.projectId);
|
|
||||||
if (project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this project",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMysql = await createMysql(input);
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await addNewService(ctx.user.authId, newMysql.mysqlId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await createMount({
|
|
||||||
serviceId: newMysql.mysqlId,
|
|
||||||
serviceType: "mysql",
|
|
||||||
volumeName: `${newMysql.appName}-data`,
|
|
||||||
mountPath: "/var/lib/mysql",
|
|
||||||
type: "volume",
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof TRPCError) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error input: Inserting mysql database",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneMySql)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.mysqlId, "access");
|
|
||||||
}
|
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
|
||||||
if (mysql.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return mysql;
|
|
||||||
}),
|
|
||||||
|
|
||||||
start: protectedProcedure
|
|
||||||
.input(apiFindOneMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const service = await findMySqlById(input.mysqlId);
|
|
||||||
if (service.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to start this mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service.serverId) {
|
|
||||||
await startServiceRemote(service.serverId, service.appName);
|
|
||||||
} else {
|
|
||||||
await startService(service.appName);
|
|
||||||
}
|
|
||||||
await updateMySqlById(input.mysqlId, {
|
|
||||||
applicationStatus: "done",
|
|
||||||
});
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}),
|
|
||||||
stop: protectedProcedure
|
|
||||||
.input(apiFindOneMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to stop this mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (mongo.serverId) {
|
|
||||||
await stopServiceRemote(mongo.serverId, mongo.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(mongo.appName);
|
|
||||||
}
|
|
||||||
await updateMySqlById(input.mysqlId, {
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
saveExternalPort: protectedProcedure
|
|
||||||
.input(apiSaveExternalPortMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this external port",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateMySqlById(input.mysqlId, {
|
|
||||||
externalPort: input.externalPort,
|
|
||||||
});
|
|
||||||
await deployMySql(input.mysqlId);
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
deploy: protectedProcedure
|
|
||||||
.input(apiDeployMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
|
||||||
if (mysql.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to deploy this mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return deployMySql(input.mysqlId);
|
|
||||||
}),
|
|
||||||
changeStatus: protectedProcedure
|
|
||||||
.input(apiChangeMySqlStatus)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to change this mysql status",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateMySqlById(input.mysqlId, {
|
|
||||||
applicationStatus: input.applicationStatus,
|
|
||||||
});
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
reload: protectedProcedure
|
|
||||||
.input(apiResetMysql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
|
||||||
if (mysql.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to reload this mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (mysql.serverId) {
|
|
||||||
await stopServiceRemote(mysql.serverId, mysql.appName);
|
|
||||||
} else {
|
|
||||||
await stopService(mysql.appName);
|
|
||||||
}
|
|
||||||
await updateMySqlById(input.mysqlId, {
|
|
||||||
applicationStatus: "idle",
|
|
||||||
});
|
|
||||||
if (mysql.serverId) {
|
|
||||||
await startServiceRemote(mysql.serverId, mysql.appName);
|
|
||||||
} else {
|
|
||||||
await startService(mysql.appName);
|
|
||||||
}
|
|
||||||
await updateMySqlById(input.mysqlId, {
|
|
||||||
applicationStatus: "done",
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
remove: protectedProcedure
|
|
||||||
.input(apiFindOneMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
if (ctx.user.rol === "user") {
|
|
||||||
await checkServiceAccess(ctx.user.authId, input.mysqlId, "delete");
|
|
||||||
}
|
|
||||||
const mongo = await findMySqlById(input.mysqlId);
|
|
||||||
if (mongo.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanupOperations = [
|
|
||||||
async () => await removeService(mongo?.appName, mongo.serverId),
|
|
||||||
async () => await removeMySqlById(input.mysqlId),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const operation of cleanupOperations) {
|
|
||||||
try {
|
|
||||||
await operation();
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mongo;
|
|
||||||
}),
|
|
||||||
saveEnvironment: protectedProcedure
|
|
||||||
.input(apiSaveEnvironmentVariablesMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const mysql = await findMySqlById(input.mysqlId);
|
|
||||||
if (mysql.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to save this environment",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const service = await updateMySqlById(input.mysqlId, {
|
|
||||||
env: input.env,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Update: Error to add environment variables",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
update: protectedProcedure
|
|
||||||
.input(apiUpdateMySql)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
const { mysqlId, ...rest } = input;
|
|
||||||
const mysql = await findMySqlById(mysqlId);
|
|
||||||
if (mysql.project.adminId !== ctx.user.adminId) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const service = await updateMySqlById(mysqlId, {
|
|
||||||
...rest,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!service) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Update: Error to update mysql",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
import {
|
|
||||||
adminProcedure,
|
|
||||||
createTRPCRouter,
|
|
||||||
protectedProcedure,
|
|
||||||
} from "@/server/api/trpc";
|
|
||||||
import { db } from "@/server/db";
|
|
||||||
import {
|
|
||||||
apiCreateDiscord,
|
|
||||||
apiCreateEmail,
|
|
||||||
apiCreateSlack,
|
|
||||||
apiCreateTelegram,
|
|
||||||
apiFindOneNotification,
|
|
||||||
apiTestDiscordConnection,
|
|
||||||
apiTestEmailConnection,
|
|
||||||
apiTestSlackConnection,
|
|
||||||
apiTestTelegramConnection,
|
|
||||||
apiUpdateDiscord,
|
|
||||||
apiUpdateEmail,
|
|
||||||
apiUpdateSlack,
|
|
||||||
apiUpdateTelegram,
|
|
||||||
notifications,
|
|
||||||
} from "@/server/db/schema";
|
|
||||||
import {
|
|
||||||
IS_CLOUD,
|
|
||||||
createDiscordNotification,
|
|
||||||
createEmailNotification,
|
|
||||||
createSlackNotification,
|
|
||||||
createTelegramNotification,
|
|
||||||
findNotificationById,
|
|
||||||
removeNotificationById,
|
|
||||||
sendDiscordNotification,
|
|
||||||
sendEmailNotification,
|
|
||||||
sendSlackNotification,
|
|
||||||
sendTelegramNotification,
|
|
||||||
updateDiscordNotification,
|
|
||||||
updateEmailNotification,
|
|
||||||
updateSlackNotification,
|
|
||||||
updateTelegramNotification,
|
|
||||||
} from "@dokploy/server";
|
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { desc, eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
// TODO: Uncomment the validations when is cloud ready
|
|
||||||
export const notificationRouter = createTRPCRouter({
|
|
||||||
createSlack: adminProcedure
|
|
||||||
.input(apiCreateSlack)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createSlackNotification(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
updateSlack: adminProcedure
|
|
||||||
.input(apiUpdateSlack)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const notification = await findNotificationById(input.notificationId);
|
|
||||||
if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
|
|
||||||
// TODO: Remove isCloud in the next versions of dokploy
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateSlackNotification({
|
|
||||||
...input,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
testSlackConnection: adminProcedure
|
|
||||||
.input(apiTestSlackConnection)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
await sendSlackNotification(input, {
|
|
||||||
channel: input.channel,
|
|
||||||
text: "Hi, From Dokploy 👋",
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to test the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
createTelegram: adminProcedure
|
|
||||||
.input(apiCreateTelegram)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createTelegramNotification(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
updateTelegram: adminProcedure
|
|
||||||
.input(apiUpdateTelegram)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const notification = await findNotificationById(input.notificationId);
|
|
||||||
if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
|
|
||||||
// TODO: Remove isCloud in the next versions of dokploy
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateTelegramNotification({
|
|
||||||
...input,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to update the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
testTelegramConnection: adminProcedure
|
|
||||||
.input(apiTestTelegramConnection)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
await sendTelegramNotification(input, "Hi, From Dokploy 👋");
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to test the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
createDiscord: adminProcedure
|
|
||||||
.input(apiCreateDiscord)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createDiscordNotification(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
updateDiscord: adminProcedure
|
|
||||||
.input(apiUpdateDiscord)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const notification = await findNotificationById(input.notificationId);
|
|
||||||
if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
|
|
||||||
// TODO: Remove isCloud in the next versions of dokploy
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateDiscordNotification({
|
|
||||||
...input,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to update the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
testDiscordConnection: adminProcedure
|
|
||||||
.input(apiTestDiscordConnection)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
await sendDiscordNotification(input, {
|
|
||||||
title: "Test Notification",
|
|
||||||
description: "Hi, From Dokploy 👋",
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to test the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
createEmail: adminProcedure
|
|
||||||
.input(apiCreateEmail)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
return await createEmailNotification(input, ctx.user.adminId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to create the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
updateEmail: adminProcedure
|
|
||||||
.input(apiUpdateEmail)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const notification = await findNotificationById(input.notificationId);
|
|
||||||
if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
|
|
||||||
// TODO: Remove isCloud in the next versions of dokploy
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to update this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await updateEmailNotification({
|
|
||||||
...input,
|
|
||||||
adminId: ctx.user.adminId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to update the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
testEmailConnection: adminProcedure
|
|
||||||
.input(apiTestEmailConnection)
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
try {
|
|
||||||
await sendEmailNotification(
|
|
||||||
input,
|
|
||||||
"Test Email",
|
|
||||||
"<p>Hi, From Dokploy 👋</p>",
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to test the notification",
|
|
||||||
cause: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
remove: adminProcedure
|
|
||||||
.input(apiFindOneNotification)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
try {
|
|
||||||
const notification = await findNotificationById(input.notificationId);
|
|
||||||
if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
|
|
||||||
// TODO: Remove isCloud in the next versions of dokploy
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to delete this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return await removeNotificationById(input.notificationId);
|
|
||||||
} catch (error) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Error to delete this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
one: protectedProcedure
|
|
||||||
.input(apiFindOneNotification)
|
|
||||||
.query(async ({ input, ctx }) => {
|
|
||||||
const notification = await findNotificationById(input.notificationId);
|
|
||||||
if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
|
|
||||||
// TODO: Remove isCloud in the next versions of dokploy
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "You are not authorized to access this notification",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return notification;
|
|
||||||
}),
|
|
||||||
all: adminProcedure.query(async ({ ctx }) => {
|
|
||||||
return await db.query.notifications.findMany({
|
|
||||||
with: {
|
|
||||||
slack: true,
|
|
||||||
telegram: true,
|
|
||||||
discord: true,
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
orderBy: desc(notifications.createdAt),
|
|
||||||
...(IS_CLOUD && { where: eq(notifications.adminId, ctx.user.adminId) }),
|
|
||||||
// TODO: Remove this line when the cloud version is ready
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user