mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-08 15:35:23 +02:00
Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4259e2533e | ||
|
|
a138d12082 | ||
|
|
a2405ddd84 | ||
|
|
e785ad5599 | ||
|
|
cc6445a8ec | ||
|
|
b8f27d7b76 | ||
|
|
32f61b5e9b | ||
|
|
e2d6b5eb8a | ||
|
|
7413c9484a | ||
|
|
607c505c4b | ||
|
|
42629e83a1 | ||
|
|
b9f18cddf7 | ||
|
|
2790895642 | ||
|
|
c21c88d89f | ||
|
|
bf6b9c6893 | ||
|
|
e08fe1dbea | ||
|
|
0b9eaac390 | ||
|
|
5ed49a5ca1 | ||
|
|
1300a6242c | ||
|
|
201f07c084 | ||
|
|
c5161f1612 | ||
|
|
0755de03c2 | ||
|
|
f376ea5fec | ||
|
|
346eb24926 | ||
|
|
fe45c69939 | ||
|
|
39d46a51b3 | ||
|
|
3e193590cc | ||
|
|
c157a353f3 | ||
|
|
2a14ae0c7f | ||
|
|
144c74e7f7 | ||
|
|
1d4d766f3a | ||
|
|
8532cba638 | ||
|
|
fdb4b176cb | ||
|
|
f2b214f8f0 | ||
|
|
0bcc59f90f | ||
|
|
7ae4bf3215 | ||
|
|
0f5cf37757 | ||
|
|
a7bde655da | ||
|
|
295b6df5e1 | ||
|
|
b5b63eae4f | ||
|
|
794e03460f | ||
|
|
e8f36f8ba5 | ||
| f9210d3165 | |||
|
|
9bc6411c98 | ||
| f8261b5364 | |||
| 30c2c7afb0 | |||
|
|
f26c1c0da6 | ||
|
|
d02976476a | ||
|
|
17e9154887 | ||
|
|
2442494096 | ||
|
|
bac2afb423 | ||
|
|
4e9630e976 | ||
|
|
558f6aecae | ||
|
|
c3e2b0d0f1 | ||
|
|
11d584316a | ||
|
|
f78dc555b2 | ||
|
|
5812b12a59 | ||
|
|
7301d15e8f | ||
|
|
f79796a6c8 | ||
|
|
4122b37abd | ||
|
|
79e9593663 | ||
|
|
def3fa0030 | ||
|
|
d561068bcd | ||
|
|
212c1b2d5f | ||
|
|
d3a54172b5 | ||
|
|
cda33eb291 | ||
|
|
c178234e53 | ||
|
|
329db1fd1a | ||
|
|
6efbf030a7 | ||
|
|
b95dfed8fc | ||
|
|
7fe3418d55 | ||
|
|
288d86c73b | ||
|
|
ffd5ccd386 | ||
|
|
98ddd096e5 | ||
|
|
da6cc9fe72 | ||
|
|
22d0af269e | ||
|
|
f0fdc46de5 | ||
|
|
9aea24115d | ||
|
|
a9ee6c2393 | ||
|
|
349717044c | ||
|
|
f94f32695f | ||
|
|
37b78ea09c | ||
|
|
9b89b4631f | ||
|
|
7100095f2b | ||
|
|
a36ab65aa6 | ||
|
|
bf81ba20ff | ||
|
|
658a4a9b99 | ||
|
|
47cb096cf3 | ||
|
|
f3856722da | ||
|
|
a67c3eb979 | ||
|
|
aaa205f104 | ||
|
|
cadea7ff28 | ||
|
|
9ab937f726 | ||
|
|
d0af517eb7 | ||
|
|
66bdf9bf0a | ||
|
|
d4a3af475a | ||
|
|
e92a8d7c98 | ||
|
|
c4fec8cee5 | ||
|
|
55f75bce53 | ||
|
|
fdc524d79d | ||
|
|
93d6662466 | ||
|
|
1977235d31 | ||
|
|
1dd713a1d1 | ||
|
|
18b65f28f2 | ||
|
|
666db23b8e | ||
|
|
2ca5321fdc | ||
|
|
3f3ff9670b | ||
|
|
7fb902551e | ||
|
|
a201b3f979 | ||
|
|
01d78e50fc | ||
|
|
6681ba7bbd | ||
|
|
0b71411c0e | ||
|
|
19f7465910 | ||
|
|
f33dd37571 | ||
|
|
a0031ed07f | ||
|
|
2ca4e264c4 | ||
|
|
fa81d04fb3 | ||
|
|
bd8745393b | ||
|
|
691c83c256 | ||
|
|
6bd85e9216 | ||
|
|
79c29fa92d | ||
|
|
89f71fe889 | ||
|
|
bddafe294d | ||
|
|
94829daf15 | ||
|
|
2209d44ea5 | ||
|
|
b12c035527 | ||
|
|
baadba542f | ||
|
|
a8fc052cbf | ||
|
|
fa5994bd47 | ||
|
|
96d0810607 | ||
|
|
2d382ea1be | ||
|
|
d78974efc0 | ||
|
|
81040c899f | ||
|
|
c7344190b4 | ||
|
|
257c0eb106 | ||
|
|
c03b9509c8 | ||
|
|
d87205c4dc | ||
|
|
48aef798e4 | ||
|
|
baa5cd5c58 | ||
|
|
5aae36996e | ||
|
|
ec8fa9fefe | ||
|
|
d959f59c2d | ||
|
|
a1169795e4 | ||
|
|
10af7925db | ||
|
|
c64cdca2e8 | ||
|
|
a5b95d8cf3 | ||
|
|
78b60f7d8a | ||
|
|
58e6a14cd6 | ||
|
|
0aac6da554 | ||
|
|
978c4d85c5 | ||
|
|
70e08c96eb | ||
|
|
027853a361 | ||
|
|
43ebe4dc7c | ||
|
|
0113ebe7da | ||
|
|
c36b40aa29 | ||
|
|
caea934f88 | ||
|
|
9b2ea1cade | ||
|
|
3a82c4b27b | ||
|
|
22a26e9873 | ||
|
|
226a287ce7 | ||
|
|
320b927aac | ||
|
|
d799b460bd |
3
.github/workflows/deploy.yml
vendored
3
.github/workflows/deploy.yml
vendored
@@ -2,7 +2,8 @@ name: Build Docker images
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["canary", "main", "feat/monitoring"]
|
branches: [main, canary]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push-cloud-image:
|
build-and-push-cloud-image:
|
||||||
|
|||||||
3
.github/workflows/dokploy.yml
vendored
3
.github/workflows/dokploy.yml
vendored
@@ -2,7 +2,8 @@ name: Dokploy Docker Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, canary, "1061-custom-docker-service-hostname"]
|
branches: [main, canary]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: dokploy/dokploy
|
IMAGE_NAME: dokploy/dokploy
|
||||||
|
|||||||
6
.github/workflows/format.yml
vendored
6
.github/workflows/format.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup biomeJs
|
- name: Setup biomeJs
|
||||||
uses: biomejs/setup-biome@v2
|
uses: biomejs/setup-biome@v2
|
||||||
|
|
||||||
- name: Run Biome formatter
|
- name: Run Biome formatter
|
||||||
run: biome format . --write
|
run: biome format --write
|
||||||
|
|
||||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27 # v1.3.2
|
||||||
|
|||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["biomejs.biome"]
|
||||||
|
}
|
||||||
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "biomejs.biome",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.biome": "explicit",
|
||||||
|
"source.organizeImports.biome": "explicit"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.16.0-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
RUN corepack prepare pnpm@9.12.0 --activate
|
||||||
|
|
||||||
FROM base AS build
|
FROM base AS build
|
||||||
COPY . /usr/src/app
|
COPY . /usr/src/app
|
||||||
@@ -57,7 +58,7 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
|||||||
&& pnpm install -g tsx
|
&& pnpm install -g tsx
|
||||||
|
|
||||||
# Install Railpack
|
# Install Railpack
|
||||||
ARG RAILPACK_VERSION=0.0.64
|
ARG RAILPACK_VERSION=0.2.2
|
||||||
RUN curl -sSL https://railpack.com/install.sh | bash
|
RUN curl -sSL https://railpack.com/install.sh | bash
|
||||||
|
|
||||||
# Install buildpacks
|
# Install buildpacks
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.16.0-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
RUN corepack prepare pnpm@9.12.0 --activate
|
||||||
|
|
||||||
FROM base AS build
|
FROM base AS build
|
||||||
COPY . /usr/src/app
|
COPY . /usr/src/app
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.16.0-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
RUN corepack prepare pnpm@9.12.0 --activate
|
||||||
|
|
||||||
FROM base AS build
|
FROM base AS build
|
||||||
COPY . /usr/src/app
|
COPY . /usr/src/app
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.16.0-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
RUN corepack prepare pnpm@9.12.0 --activate
|
||||||
|
|
||||||
FROM base AS build
|
FROM base AS build
|
||||||
COPY . /usr/src/app
|
COPY . /usr/src/app
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://dokploy.com">
|
<a href="https://dokploy.com">
|
||||||
<img src=".github/sponsors/logo.png" alt="Dokploy - Open Source Alternative to Vercel, Heroku and Netlify." align="center" width="100%" />
|
<img src=".github/sponsors/logo.png" alt="Dokploy - Open Source Alternative to Vercel, Heroku and Netlify." width="100%" />
|
||||||
</a>
|
</a>
|
||||||
</br>
|
</br>
|
||||||
</br>
|
</br>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
Dokploy is a free, self-hostable Platform as a Service (PaaS) that simplifies the deployment and management of applications and databases.
|
||||||
|
|
||||||
### Features
|
## ✨ Features
|
||||||
|
|
||||||
Dokploy includes multiple features to make your life easier.
|
Dokploy includes multiple features to make your life easier.
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ curl -sSL https://dokploy.com/install.sh | sh
|
|||||||
|
|
||||||
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
||||||
|
|
||||||
## Sponsors
|
## ♥️ Sponsors
|
||||||
|
|
||||||
🙏 We're deeply grateful to all our sponsors who make Dokploy possible! Your support helps cover the costs of hosting, testing, and developing new features.
|
🙏 We're deeply grateful to all our sponsors who make Dokploy possible! Your support helps cover the costs of hosting, testing, and developing new features.
|
||||||
|
|
||||||
@@ -60,8 +60,6 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
<div>
|
<div>
|
||||||
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy"><img src=".github/sponsors/hostinger.jpg" alt="Hostinger" width="300"/></a>
|
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy"><img src=".github/sponsors/hostinger.jpg" alt="Hostinger" width="300"/></a>
|
||||||
<a href="https://www.lxaer.com/?ref=dokploy"><img src=".github/sponsors/lxaer.png" alt="LX Aer" width="100"/></a>
|
<a href="https://www.lxaer.com/?ref=dokploy"><img src=".github/sponsors/lxaer.png" alt="LX Aer" width="100"/></a>
|
||||||
<a href="https://mandarin3d.com/?ref=dokploy"><img src=".github/sponsors/mandarin.png" alt="Mandarin" width="100"/></a>
|
|
||||||
<a href="https://lightnode.com/?ref=dokploy"><img src=".github/sponsors/light-node.webp" alt="Lightnode" width="300"/></a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Premium Supporters 🥇 -->
|
<!-- Premium Supporters 🥇 -->
|
||||||
@@ -89,25 +87,17 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
### Supporting Members 🥉
|
### Supporting Members 🥉
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
|
||||||
<a href="https://cloudblast.io/?ref=dokploy"><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
|
<a href="https://cloudblast.io/?ref=dokploy"><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
|
||||||
<a href="https://startupfa.me/?ref=dokploy"><img src=".github/sponsors/startupfame.png" width="65px" alt="Startupfame"/></a>
|
|
||||||
<a href="https://itsdb-center.com?ref=dokploy"><img src=".github/sponsors/its.png" width="65px" alt="Itsdb-center"/></a>
|
|
||||||
<a href="https://openalternative.co/?ref=dokploy"><img src=".github/sponsors/openalternative.png" width="65px" alt="Openalternative"/></a>
|
|
||||||
<a href="https://synexa.ai/?ref=dokploy"><img src=".github/sponsors/synexa.png" width="65px" alt="Synexa"/></a>
|
<a href="https://synexa.ai/?ref=dokploy"><img src=".github/sponsors/synexa.png" width="65px" alt="Synexa"/></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Community Backers 🤝
|
### Community Backers 🤝
|
||||||
|
|
||||||
<div>
|
|
||||||
<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://photoquest.wedding/?ref=dokploy"><img src="https://photoquest.wedding/favicon/android-chrome-512x512.png" width="60px" alt="Rivo.gg"/></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
#### Organizations:
|
#### Organizations:
|
||||||
|
|
||||||
[](https://opencollective.com/dokploy)
|
[Sponsors on Open Collective](https://opencollective.com/dokploy)
|
||||||
|
|
||||||
#### Individuals:
|
#### Individuals:
|
||||||
|
|
||||||
@@ -116,15 +106,15 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
|||||||
### Contributors 🤝
|
### Contributors 🤝
|
||||||
|
|
||||||
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
|
<a href="https://github.com/dokploy/dokploy/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" />
|
<img src="https://contrib.rocks/image?repo=dokploy/dokploy" alt="Contributors" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Video Tutorial
|
## 📺 Video Tutorial
|
||||||
|
|
||||||
<a href="https://youtu.be/mznYKPvhcfw">
|
<a href="https://youtu.be/mznYKPvhcfw">
|
||||||
<img src="https://dokploy.com/banner.png" alt="Watch the video" width="400"/>
|
<img src="https://dokploy.com/banner.png" alt="Watch the video" width="400"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
## Contributing
|
## 🤝 Contributing
|
||||||
|
|
||||||
Check out the [Contributing Guide](CONTRIBUTING.md) for more information.
|
Check out the [Contributing Guide](CONTRIBUTING.md) for more information.
|
||||||
|
|||||||
@@ -10,24 +10,28 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dokploy/server": "workspace:*",
|
"@dokploy/server": "workspace:*",
|
||||||
"@hono/node-server": "^1.12.1",
|
"@hono/node-server": "^1.14.3",
|
||||||
"@hono/zod-validator": "0.3.0",
|
"@hono/zod-validator": "0.3.0",
|
||||||
"@nerimity/mimiqueue": "1.2.3",
|
"@nerimity/mimiqueue": "1.2.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.4.5",
|
||||||
"hono": "^4.5.8",
|
"hono": "^4.7.10",
|
||||||
"pino": "9.4.0",
|
"pino": "9.4.0",
|
||||||
"pino-pretty": "11.2.2",
|
"pino-pretty": "11.2.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"redis": "4.7.0",
|
"redis": "4.7.0",
|
||||||
"zod": "^3.23.4"
|
"zod": "^3.25.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.11.17",
|
"@types/node": "^20.17.51",
|
||||||
"@types/react": "^18.2.37",
|
"@types/react": "^18.2.37",
|
||||||
"@types/react-dom": "^18.2.15",
|
"@types/react-dom": "^18.2.15",
|
||||||
"tsx": "^4.7.1",
|
"tsx": "^4.16.2",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.5.0"
|
"packageManager": "pnpm@9.12.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.16.0",
|
||||||
|
"pnpm": ">=9.12.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { addSuffixToAllProperties } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToAllProperties } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToConfigsRoot } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToConfigsRoot, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToConfigsInServices } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import {
|
||||||
|
addSuffixToConfigsInServices,
|
||||||
|
generateRandomHash,
|
||||||
|
} from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToAllConfigs } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToAllConfigs, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -108,4 +108,136 @@ describe("createDomainLabels", () => {
|
|||||||
"traefik.http.services.test-app-1-web.loadbalancer.server.port=3000",
|
"traefik.http.services.test-app-1-web.loadbalancer.server.port=3000",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should add stripPath middleware when stripPath is enabled", async () => {
|
||||||
|
const stripPathDomain = {
|
||||||
|
...baseDomain,
|
||||||
|
path: "/api",
|
||||||
|
stripPath: true,
|
||||||
|
};
|
||||||
|
const labels = await createDomainLabels(appName, stripPathDomain, "web");
|
||||||
|
|
||||||
|
expect(labels).toContain(
|
||||||
|
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
|
||||||
|
);
|
||||||
|
expect(labels).toContain(
|
||||||
|
"traefik.http.routers.test-app-1-web.middlewares=stripprefix-test-app-1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add internalPath middleware when internalPath is set", async () => {
|
||||||
|
const internalPathDomain = {
|
||||||
|
...baseDomain,
|
||||||
|
internalPath: "/hello",
|
||||||
|
};
|
||||||
|
const webLabels = await createDomainLabels(
|
||||||
|
appName,
|
||||||
|
internalPathDomain,
|
||||||
|
"web",
|
||||||
|
);
|
||||||
|
const websecureLabels = await createDomainLabels(
|
||||||
|
appName,
|
||||||
|
internalPathDomain,
|
||||||
|
"websecure",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Middleware definition should only appear in web entrypoint
|
||||||
|
expect(webLabels).toContain(
|
||||||
|
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||||
|
);
|
||||||
|
expect(websecureLabels).not.toContain(
|
||||||
|
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Both routers should reference the middleware
|
||||||
|
expect(webLabels).toContain(
|
||||||
|
"traefik.http.routers.test-app-1-web.middlewares=addprefix-test-app-1",
|
||||||
|
);
|
||||||
|
expect(websecureLabels).toContain(
|
||||||
|
"traefik.http.routers.test-app-1-websecure.middlewares=addprefix-test-app-1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should combine HTTPS redirect with internalPath middleware in correct order", async () => {
|
||||||
|
const combinedDomain = {
|
||||||
|
...baseDomain,
|
||||||
|
https: true,
|
||||||
|
internalPath: "/hello",
|
||||||
|
};
|
||||||
|
const webLabels = await createDomainLabels(appName, combinedDomain, "web");
|
||||||
|
const websecureLabels = await createDomainLabels(
|
||||||
|
appName,
|
||||||
|
combinedDomain,
|
||||||
|
"websecure",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Web entrypoint should have both middlewares with redirect first
|
||||||
|
expect(webLabels).toContain(
|
||||||
|
"traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,addprefix-test-app-1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Websecure should only have the addprefix middleware
|
||||||
|
expect(websecureLabels).toContain(
|
||||||
|
"traefik.http.routers.test-app-1-websecure.middlewares=addprefix-test-app-1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Middleware definition should only appear once (in web)
|
||||||
|
expect(webLabels).toContain(
|
||||||
|
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||||
|
);
|
||||||
|
expect(websecureLabels).not.toContain(
|
||||||
|
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should combine all middlewares in correct order", async () => {
|
||||||
|
const fullDomain = {
|
||||||
|
...baseDomain,
|
||||||
|
https: true,
|
||||||
|
path: "/api",
|
||||||
|
stripPath: true,
|
||||||
|
internalPath: "/hello",
|
||||||
|
};
|
||||||
|
const webLabels = await createDomainLabels(appName, fullDomain, "web");
|
||||||
|
|
||||||
|
// Should have all middleware definitions (only in web)
|
||||||
|
expect(webLabels).toContain(
|
||||||
|
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
|
||||||
|
);
|
||||||
|
expect(webLabels).toContain(
|
||||||
|
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should have middlewares in correct order: redirect, stripprefix, addprefix
|
||||||
|
expect(webLabels).toContain(
|
||||||
|
"traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file,stripprefix-test-app-1,addprefix-test-app-1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not add middleware definitions for websecure entrypoint", async () => {
|
||||||
|
const internalPathDomain = {
|
||||||
|
...baseDomain,
|
||||||
|
path: "/api",
|
||||||
|
stripPath: true,
|
||||||
|
internalPath: "/hello",
|
||||||
|
};
|
||||||
|
const websecureLabels = await createDomainLabels(
|
||||||
|
appName,
|
||||||
|
internalPathDomain,
|
||||||
|
"websecure",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should not contain any middleware definitions
|
||||||
|
expect(websecureLabels).not.toContain(
|
||||||
|
"traefik.http.middlewares.stripprefix-test-app-1.stripprefix.prefixes=/api",
|
||||||
|
);
|
||||||
|
expect(websecureLabels).not.toContain(
|
||||||
|
"traefik.http.middlewares.addprefix-test-app-1.addprefix.prefix=/hello",
|
||||||
|
);
|
||||||
|
|
||||||
|
// But should reference the middlewares
|
||||||
|
expect(websecureLabels).toContain(
|
||||||
|
"traefik.http.routers.test-app-1-websecure.middlewares=stripprefix-test-app-1,addprefix-test-app-1",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToNetworksRoot } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToNetworksRoot, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToServiceNetworks } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import {
|
||||||
|
addSuffixToServiceNetworks,
|
||||||
|
generateRandomHash,
|
||||||
|
} from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
import {
|
import {
|
||||||
addSuffixToAllNetworks,
|
addSuffixToAllNetworks,
|
||||||
|
addSuffixToNetworksRoot,
|
||||||
addSuffixToServiceNetworks,
|
addSuffixToServiceNetworks,
|
||||||
|
generateRandomHash,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { addSuffixToNetworksRoot } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToSecretsRoot } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToSecretsRoot, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToSecretsInServices } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import {
|
||||||
|
addSuffixToSecretsInServices,
|
||||||
|
generateRandomHash,
|
||||||
|
} from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { addSuffixToAllSecrets } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToAllSecrets } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
import {
|
import {
|
||||||
addSuffixToAllServiceNames,
|
addSuffixToAllServiceNames,
|
||||||
addSuffixToServiceNames,
|
addSuffixToServiceNames,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToServiceNames } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToServiceNames, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToAllVolumes, addSuffixToVolumesRoot } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import {
|
||||||
|
addSuffixToAllVolumes,
|
||||||
|
addSuffixToVolumesRoot,
|
||||||
|
generateRandomHash,
|
||||||
|
} from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToVolumesRoot } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToVolumesRoot, generateRandomHash } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { generateRandomHash } from "@dokploy/server";
|
|
||||||
import { addSuffixToVolumesInServices } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import {
|
||||||
|
addSuffixToVolumesInServices,
|
||||||
|
generateRandomHash,
|
||||||
|
} from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { addSuffixToAllVolumes } from "@dokploy/server";
|
|
||||||
import type { ComposeSpecification } from "@dokploy/server";
|
import type { ComposeSpecification } from "@dokploy/server";
|
||||||
|
import { addSuffixToAllVolumes } from "@dokploy/server";
|
||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]";
|
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { extractCommitMessage } from "@/pages/api/deploy/[refreshToken]";
|
||||||
|
|
||||||
describe("GitHub Webhook Skip CI", () => {
|
describe("GitHub Webhook Skip CI", () => {
|
||||||
const mockGithubHeaders = {
|
const mockGithubHeaders = {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
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/constants";
|
|
||||||
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 { paths } from "@dokploy/server/constants";
|
||||||
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";
|
||||||
|
|
||||||
|
const { APPLICATIONS_PATH } = paths();
|
||||||
vi.mock("@dokploy/server/constants", async (importOriginal) => {
|
vi.mock("@dokploy/server/constants", async (importOriginal) => {
|
||||||
const actual = await importOriginal();
|
const actual = await importOriginal();
|
||||||
return {
|
return {
|
||||||
@@ -25,10 +25,12 @@ if (typeof window === "undefined") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const baseApp: ApplicationNested = {
|
const baseApp: ApplicationNested = {
|
||||||
|
railpackVersion: "0.2.2",
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
herokuVersion: "",
|
herokuVersion: "",
|
||||||
giteaBranch: "",
|
giteaBranch: "",
|
||||||
giteaBuildPath: "",
|
giteaBuildPath: "",
|
||||||
|
previewRequireCollaboratorPermissions: false,
|
||||||
giteaId: "",
|
giteaId: "",
|
||||||
giteaOwner: "",
|
giteaOwner: "",
|
||||||
giteaRepository: "",
|
giteaRepository: "",
|
||||||
@@ -141,7 +143,7 @@ describe("unzipDrop using real zip files", () => {
|
|||||||
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
|
const outputPath = path.join(APPLICATIONS_PATH, baseApp.appName, "code");
|
||||||
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
|
const zip = new AdmZip("./__test__/drop/zips/single-file.zip");
|
||||||
console.log(`Output Path: ${outputPath}`);
|
console.log(`Output Path: ${outputPath}`);
|
||||||
const zipBuffer = zip.toBuffer();
|
const zipBuffer = zip.toBuffer() as Buffer<ArrayBuffer>;
|
||||||
const file = new File([zipBuffer], "single.zip");
|
const file = new File([zipBuffer], "single.zip");
|
||||||
await unzipDrop(file, baseApp);
|
await unzipDrop(file, baseApp);
|
||||||
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
const files = await fs.readdir(outputPath, { withFileTypes: true });
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { parseRawConfig, processLogs } from "@dokploy/server";
|
import { parseRawConfig, processLogs } from "@dokploy/server";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
const sampleLogEntry = `{"ClientAddr":"172.19.0.1:56732","ClientHost":"172.19.0.1","ClientPort":"56732","ClientUsername":"-","DownstreamContentSize":0,"DownstreamStatus":304,"Duration":14729375,"OriginContentSize":0,"OriginDuration":14051833,"OriginStatus":304,"Overhead":677542,"RequestAddr":"s222-umami-c381af.traefik.me","RequestContentSize":0,"RequestCount":122,"RequestHost":"s222-umami-c381af.traefik.me","RequestMethod":"GET","RequestPath":"/dashboard?_rsc=1rugv","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-umami-60e104-47-web@docker","ServiceAddr":"10.0.1.15:3000","ServiceName":"s222-umami-60e104-47-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.15:3000","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T04:34:37.306691884Z","StartUTC":"2024-08-25T04:34:37.306691884Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T04:34:37Z"}`;
|
const sampleLogEntry = `{"ClientAddr":"172.19.0.1:56732","ClientHost":"172.19.0.1","ClientPort":"56732","ClientUsername":"-","DownstreamContentSize":0,"DownstreamStatus":304,"Duration":14729375,"OriginContentSize":0,"OriginDuration":14051833,"OriginStatus":304,"Overhead":677542,"RequestAddr":"s222-umami-c381af.traefik.me","RequestContentSize":0,"RequestCount":122,"RequestHost":"s222-umami-c381af.traefik.me","RequestMethod":"GET","RequestPath":"/dashboard?_rsc=1rugv","RequestPort":"-","RequestProtocol":"HTTP/1.1","RequestScheme":"http","RetryAttempts":0,"RouterName":"s222-umami-60e104-47-web@docker","ServiceAddr":"10.0.1.15:3000","ServiceName":"s222-umami-60e104-47-web@docker","ServiceURL":{"Scheme":"http","Opaque":"","User":null,"Host":"10.0.1.15:3000","Path":"","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":"","RawFragment":""},"StartLocal":"2024-08-25T04:34:37.306691884Z","StartUTC":"2024-08-25T04:34:37.306691884Z","entryPointName":"web","level":"info","msg":"","time":"2024-08-25T04:34:37Z"}`;
|
||||||
|
|
||||||
describe("processLogs", () => {
|
describe("processLogs", () => {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import type { Domain } from "@dokploy/server";
|
import type { ApplicationNested, Domain, Redirect } from "@dokploy/server";
|
||||||
import type { Redirect } from "@dokploy/server";
|
|
||||||
import type { ApplicationNested } from "@dokploy/server";
|
|
||||||
import { createRouterConfig } from "@dokploy/server";
|
import { createRouterConfig } from "@dokploy/server";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
const baseApp: ApplicationNested = {
|
const baseApp: ApplicationNested = {
|
||||||
|
railpackVersion: "0.2.2",
|
||||||
rollbackActive: false,
|
rollbackActive: false,
|
||||||
applicationId: "",
|
applicationId: "",
|
||||||
herokuVersion: "",
|
herokuVersion: "",
|
||||||
@@ -18,6 +17,7 @@ const baseApp: ApplicationNested = {
|
|||||||
appName: "",
|
appName: "",
|
||||||
autoDeploy: true,
|
autoDeploy: true,
|
||||||
enableSubmodules: false,
|
enableSubmodules: false,
|
||||||
|
previewRequireCollaboratorPermissions: false,
|
||||||
serverId: "",
|
serverId: "",
|
||||||
branch: null,
|
branch: null,
|
||||||
dockerBuildStage: "",
|
dockerBuildStage: "",
|
||||||
|
|||||||
@@ -270,8 +270,8 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
Swarm Settings
|
Swarm Settings
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-5xl p-0">
|
<DialogContent className="sm:max-w-5xl p-0">
|
||||||
<DialogHeader className="p-6">
|
<DialogHeader>
|
||||||
<DialogTitle>Swarm Settings</DialogTitle>
|
<DialogTitle>Swarm Settings</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Update certain settings using a json object.
|
Update certain settings using a json object.
|
||||||
@@ -753,7 +753,7 @@ export const AddSwarmSettings = ({ applicationId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2 m-0 sticky bottom-0 right-0 bg-muted border p-2 ">
|
<DialogFooter className="flex w-full flex-row justify-end md:col-span-2 m-0 sticky bottom-0 right-0 bg-muted border">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
form="hook-form-add-permissions"
|
form="hook-form-add-permissions"
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export const ShowImport = ({ composeId }: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Dialog open={showModal} onOpenChange={setShowModal}>
|
<Dialog open={showModal} onOpenChange={setShowModal}>
|
||||||
<DialogContent className="max-h-[80vh] max-w-[50vw] overflow-y-auto">
|
<DialogContent className="max-w-[50vw]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-2xl font-bold">
|
<DialogTitle className="text-2xl font-bold">
|
||||||
Template Information
|
Template Information
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export const HandlePorts = ({
|
|||||||
<Button>{children}</Button>
|
<Button>{children}</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Ports</DialogTitle>
|
<DialogTitle>Ports</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export const HandleRedirect = ({
|
|||||||
<Button>{children}</Button>
|
<Button>{children}</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Redirects</DialogTitle>
|
<DialogTitle>Redirects</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export const HandleSecurity = ({
|
|||||||
<Button>{children}</Button>
|
<Button>{children}</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Security</DialogTitle>
|
<DialogTitle>Security</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -151,7 +151,7 @@ export const HandleSecurity = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Password</FormLabel>
|
<FormLabel>Password</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="test" {...field} />
|
<Input placeholder="test" type="password" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { LockKeyhole, Trash2 } from "lucide-react";
|
import { LockKeyhole, Trash2 } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -58,19 +61,18 @@ export const ShowSecurity = ({ applicationId }: Props) => {
|
|||||||
<div className="flex flex-col gap-6 ">
|
<div className="flex flex-col gap-6 ">
|
||||||
{data?.security.map((security) => (
|
{data?.security.map((security) => (
|
||||||
<div key={security.securityId}>
|
<div key={security.securityId}>
|
||||||
<div className="flex w-full flex-col sm:flex-row justify-between sm:items-center gap-4 sm:gap-10 border rounded-lg p-4">
|
<div className="flex w-full flex-col md:flex-row justify-between md:items-center gap-4 md:gap-10 border rounded-lg p-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 flex-col gap-4 sm:gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 flex-col gap-4 md:gap-8">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-2">
|
||||||
<span className="font-medium">Username</span>
|
<Label>Username</Label>
|
||||||
<span className="text-sm text-muted-foreground">
|
<Input disabled value={security.username} />
|
||||||
{security.username}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-2">
|
||||||
<span className="font-medium">Password</span>
|
<Label>Password</Label>
|
||||||
<span className="text-sm text-muted-foreground">
|
<ToggleVisibilityInput
|
||||||
{security.password}
|
value={security.password}
|
||||||
</span>
|
disabled
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
|
|||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button isLoading={isLoading}>Modify</Button>
|
<Button isLoading={isLoading}>Modify</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl">
|
<DialogContent className="sm:max-w-4xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Update traefik config</DialogTitle>
|
<DialogTitle>Update traefik config</DialogTitle>
|
||||||
<DialogDescription>Update the traefik config</DialogDescription>
|
<DialogDescription>Update the traefik config</DialogDescription>
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export const AddVolumes = ({
|
|||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
<Button>{children}</Button>
|
<Button>{children}</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl">
|
<DialogContent className="sm:max-w-3xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Volumes / Mounts</DialogTitle>
|
<DialogTitle>Volumes / Mounts</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Package, Trash2 } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -9,11 +11,10 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Package, Trash2 } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import type { ServiceType } from "../show-resources";
|
import type { ServiceType } from "../show-resources";
|
||||||
import { AddVolumes } from "./add-volumes";
|
import { AddVolumes } from "./add-volumes";
|
||||||
import { UpdateVolume } from "./update-volume";
|
import { UpdateVolume } from "./update-volume";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
type: ServiceType | "compose";
|
type: ServiceType | "compose";
|
||||||
@@ -80,7 +81,7 @@ export const ShowVolumes = ({ id, type }: Props) => {
|
|||||||
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
className="flex w-full flex-col sm:flex-row sm:items-center justify-between gap-4 sm:gap-10 border rounded-lg p-4"
|
||||||
>
|
>
|
||||||
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
|
{/* <Package className="size-8 self-center text-muted-foreground" /> */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 flex-col gap-4 sm:gap-8">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 flex-col gap-4 sm:gap-8">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="font-medium">Mount Type</span>
|
<span className="font-medium">Mount Type</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
@@ -112,21 +113,21 @@ export const ShowVolumes = ({ id, type }: Props) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{mount.type === "file" ? (
|
{mount.type === "file" && (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="font-medium">File Path</span>
|
<span className="font-medium">File Path</span>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{mount.filePath}
|
{mount.filePath}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<span className="font-medium">Mount Path</span>
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{mount.mountPath}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<span className="font-medium">Mount Path</span>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{mount.mountPath}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row gap-1">
|
<div className="flex flex-row gap-1">
|
||||||
<UpdateVolume
|
<UpdateVolume
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export const UpdateVolume = ({
|
|||||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl">
|
<DialogContent className="sm:max-w-3xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Update</DialogTitle>
|
<DialogTitle>Update</DialogTitle>
|
||||||
<DialogDescription>Update the mount</DialogDescription>
|
<DialogDescription>Update the mount</DialogDescription>
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Cog } from "lucide-react";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -15,12 +21,6 @@ import {
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Cog } from "lucide-react";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export enum BuildType {
|
export enum BuildType {
|
||||||
dockerfile = "dockerfile",
|
dockerfile = "dockerfile",
|
||||||
@@ -65,6 +65,7 @@ const mySchema = z.discriminatedUnion("buildType", [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal(BuildType.railpack),
|
buildType: z.literal(BuildType.railpack),
|
||||||
|
railpackVersion: z.string().nullable().default("0.2.2"),
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
buildType: z.literal(BuildType.static),
|
buildType: z.literal(BuildType.static),
|
||||||
@@ -86,6 +87,7 @@ interface ApplicationData {
|
|||||||
herokuVersion?: string | null;
|
herokuVersion?: string | null;
|
||||||
publishDirectory?: string | null;
|
publishDirectory?: string | null;
|
||||||
isStaticSpa?: boolean | null;
|
isStaticSpa?: boolean | null;
|
||||||
|
railpackVersion?: string | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidBuildType(value: string): value is BuildType {
|
function isValidBuildType(value: string): value is BuildType {
|
||||||
@@ -123,6 +125,7 @@ const resetData = (data: ApplicationData): AddTemplate => {
|
|||||||
case BuildType.railpack:
|
case BuildType.railpack:
|
||||||
return {
|
return {
|
||||||
buildType: BuildType.railpack,
|
buildType: BuildType.railpack,
|
||||||
|
railpackVersion: data.railpackVersion || null,
|
||||||
};
|
};
|
||||||
default: {
|
default: {
|
||||||
const buildType = data.buildType as BuildType;
|
const buildType = data.buildType as BuildType;
|
||||||
@@ -181,6 +184,10 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
: null,
|
: null,
|
||||||
isStaticSpa:
|
isStaticSpa:
|
||||||
data.buildType === BuildType.static ? data.isStaticSpa : null,
|
data.buildType === BuildType.static ? data.isStaticSpa : null,
|
||||||
|
railpackVersion:
|
||||||
|
data.buildType === BuildType.railpack
|
||||||
|
? data.railpackVersion || "0.2.2"
|
||||||
|
: null,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
toast.success("Build type saved");
|
toast.success("Build type saved");
|
||||||
@@ -395,6 +402,25 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{buildType === BuildType.railpack && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="railpackVersion"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Railpack Version</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Railpack Version"
|
||||||
|
{...field}
|
||||||
|
value={field.value ?? ""}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex w-full justify-end">
|
<div className="flex w-full justify-end">
|
||||||
<Button isLoading={isLoading} type="submit">
|
<Button isLoading={isLoading} type="submit">
|
||||||
Save
|
Save
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export const ShowDeployment = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
|
<DialogContent className={"sm:max-w-5xl"}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Deployment</DialogTitle>
|
<DialogTitle>Deployment</DialogTitle>
|
||||||
<DialogDescription className="flex items-center gap-2">
|
<DialogDescription className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const ShowDeploymentsModal = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl p-0">
|
<DialogContent className="sm:max-w-5xl p-0">
|
||||||
<ShowDeployments
|
<ShowDeployments
|
||||||
id={id}
|
id={id}
|
||||||
type={type}
|
type={type}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||||
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
import { StatusTooltip } from "@/components/shared/status-tooltip";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -10,14 +11,13 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { type RouterOutputs, api } from "@/utils/api";
|
import { type RouterOutputs, api } from "@/utils/api";
|
||||||
import { Clock, Loader2, RocketIcon, Settings, RefreshCcw } from "lucide-react";
|
import { Clock, Loader2, RefreshCcw, RocketIcon, Settings } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
|
||||||
import { CancelQueues } from "./cancel-queues";
|
import { CancelQueues } from "./cancel-queues";
|
||||||
import { RefreshToken } from "./refresh-token";
|
import { RefreshToken } from "./refresh-token";
|
||||||
import { ShowDeployment } from "./show-deployment";
|
import { ShowDeployment } from "./show-deployment";
|
||||||
import { ShowRollbackSettings } from "../rollbacks/show-rollback-settings";
|
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const DnsHelperModal = ({ domain, serverIp }: Props) => {
|
|||||||
<HelpCircle className="size-4" />
|
<HelpCircle className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Server className="size-5" />
|
<Server className="size-5" />
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { DatabaseZap, Dices, RefreshCw } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import z from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -34,14 +41,6 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { DatabaseZap, Dices, RefreshCw } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import z from "zod";
|
|
||||||
|
|
||||||
export type CacheType = "fetch" | "cache";
|
export type CacheType = "fetch" | "cache";
|
||||||
|
|
||||||
@@ -123,6 +122,7 @@ interface Props {
|
|||||||
export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
const [cacheType, setCacheType] = useState<CacheType>("cache");
|
||||||
|
const [isManualInput, setIsManualInput] = useState(false);
|
||||||
|
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const { data, refetch } = api.domain.one.useQuery(
|
const { data, refetch } = api.domain.one.useQuery(
|
||||||
@@ -292,7 +292,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
{children}
|
{children}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
@@ -325,46 +325,126 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
<FormItem className="w-full">
|
<FormItem className="w-full">
|
||||||
<FormLabel>Service Name</FormLabel>
|
<FormLabel>Service Name</FormLabel>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Select
|
{isManualInput ? (
|
||||||
onValueChange={field.onChange}
|
|
||||||
defaultValue={field.value || ""}
|
|
||||||
>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<Input
|
||||||
<SelectValue placeholder="Select a service name" />
|
placeholder="Enter service name manually"
|
||||||
</SelectTrigger>
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
) : (
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value || ""}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a service name" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{services?.map((service, index) => (
|
{services?.map((service, index) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
value={service}
|
value={service}
|
||||||
key={`${service}-${index}`}
|
key={`${service}-${index}`}
|
||||||
>
|
>
|
||||||
{service}
|
{service}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value="none" disabled>
|
||||||
|
Empty
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
</SelectContent>
|
||||||
<SelectItem value="none" disabled>
|
</Select>
|
||||||
Empty
|
)}
|
||||||
</SelectItem>
|
{!isManualInput && (
|
||||||
</SelectContent>
|
<>
|
||||||
</Select>
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
type="button"
|
||||||
|
isLoading={isLoadingServices}
|
||||||
|
onClick={() => {
|
||||||
|
if (cacheType === "fetch") {
|
||||||
|
refetchServices();
|
||||||
|
} else {
|
||||||
|
setCacheType("fetch");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RefreshCw className="size-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent
|
||||||
|
side="left"
|
||||||
|
sideOffset={5}
|
||||||
|
className="max-w-[10rem]"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Fetch: Will clone the repository and
|
||||||
|
load the services
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
type="button"
|
||||||
|
isLoading={isLoadingServices}
|
||||||
|
onClick={() => {
|
||||||
|
if (cacheType === "cache") {
|
||||||
|
refetchServices();
|
||||||
|
} else {
|
||||||
|
setCacheType("cache");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DatabaseZap className="size-4 text-muted-foreground" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent
|
||||||
|
side="left"
|
||||||
|
sideOffset={5}
|
||||||
|
className="max-w-[10rem]"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Cache: If you previously deployed this
|
||||||
|
compose, it will read the services
|
||||||
|
from the last deployment/fetch from
|
||||||
|
the repository
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<TooltipProvider delayDuration={0}>
|
<TooltipProvider delayDuration={0}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
type="button"
|
type="button"
|
||||||
isLoading={isLoadingServices}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (cacheType === "fetch") {
|
setIsManualInput(!isManualInput);
|
||||||
refetchServices();
|
if (!isManualInput) {
|
||||||
} else {
|
field.onChange("");
|
||||||
setCacheType("fetch");
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RefreshCw className="size-4 text-muted-foreground" />
|
{isManualInput ? (
|
||||||
|
<RefreshCw className="size-4 text-muted-foreground" />
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Manual
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
@@ -373,40 +453,9 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
className="max-w-[10rem]"
|
className="max-w-[10rem]"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Fetch: Will clone the repository and load
|
{isManualInput
|
||||||
the services
|
? "Switch to service selection"
|
||||||
</p>
|
: "Enter service name manually"}
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
<TooltipProvider delayDuration={0}>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
type="button"
|
|
||||||
isLoading={isLoadingServices}
|
|
||||||
onClick={() => {
|
|
||||||
if (cacheType === "cache") {
|
|
||||||
refetchServices();
|
|
||||||
} else {
|
|
||||||
setCacheType("cache");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DatabaseZap className="size-4 text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent
|
|
||||||
side="left"
|
|
||||||
sideOffset={5}
|
|
||||||
className="max-w-[10rem]"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
Cache: If you previously deployed this
|
|
||||||
compose, it will read the services from
|
|
||||||
the last deployment/fetch from the
|
|
||||||
repository
|
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import { api } from "@/utils/api";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
import { CheckIcon, ChevronsUpDown, HelpCircle, Plus, X } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -96,6 +96,16 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
|||||||
const repository = form.watch("repository");
|
const repository = form.watch("repository");
|
||||||
const gitlabId = form.watch("gitlabId");
|
const gitlabId = form.watch("gitlabId");
|
||||||
|
|
||||||
|
const gitlabUrl = useMemo(() => {
|
||||||
|
const url = gitlabProviders?.find(
|
||||||
|
(provider) => provider.gitlabId === gitlabId,
|
||||||
|
)?.gitlabUrl;
|
||||||
|
|
||||||
|
const gitlabUrl = url?.replace(/\/$/, "");
|
||||||
|
|
||||||
|
return gitlabUrl || "https://gitlab.com";
|
||||||
|
}, [gitlabId, gitlabProviders]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: repositories,
|
data: repositories,
|
||||||
isLoading: isLoadingRepositories,
|
isLoading: isLoadingRepositories,
|
||||||
@@ -224,7 +234,7 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
|
|||||||
<FormLabel>Repository</FormLabel>
|
<FormLabel>Repository</FormLabel>
|
||||||
{field.value.owner && field.value.repo && (
|
{field.value.owner && field.value.repo && (
|
||||||
<Link
|
<Link
|
||||||
href={`https://gitlab.com/${field.value.owner}/${field.value.repo}`}
|
href={`${gitlabUrl}/${field.value.owner}/${field.value.repo}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
|
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-primary"
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
|||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4">
|
<div className="flex flex-row items-center justify-between w-full overflow-auto">
|
||||||
<TabsList className="md:grid md:w-fit md:grid-cols-7 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
|
<TabsList className="flex gap-4 justify-start bg-transparent">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="github"
|
value="github"
|
||||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export const AddPreviewDomain = ({
|
|||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
{children}
|
{children}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ const schema = z
|
|||||||
previewPath: z.string(),
|
previewPath: z.string(),
|
||||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
|
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]),
|
||||||
previewCustomCertResolver: z.string().optional(),
|
previewCustomCertResolver: z.string().optional(),
|
||||||
|
previewRequireCollaboratorPermissions: z.boolean(),
|
||||||
})
|
})
|
||||||
.superRefine((input, ctx) => {
|
.superRefine((input, ctx) => {
|
||||||
if (
|
if (
|
||||||
@@ -83,6 +84,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
previewHttps: false,
|
previewHttps: false,
|
||||||
previewPath: "/",
|
previewPath: "/",
|
||||||
previewCertificateType: "none",
|
previewCertificateType: "none",
|
||||||
|
previewRequireCollaboratorPermissions: true,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
});
|
});
|
||||||
@@ -105,6 +107,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
previewPath: data.previewPath || "/",
|
previewPath: data.previewPath || "/",
|
||||||
previewCertificateType: data.previewCertificateType || "none",
|
previewCertificateType: data.previewCertificateType || "none",
|
||||||
previewCustomCertResolver: data.previewCustomCertResolver || "",
|
previewCustomCertResolver: data.previewCustomCertResolver || "",
|
||||||
|
previewRequireCollaboratorPermissions:
|
||||||
|
data.previewRequireCollaboratorPermissions || true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
@@ -121,6 +125,8 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
previewPath: formData.previewPath,
|
previewPath: formData.previewPath,
|
||||||
previewCertificateType: formData.previewCertificateType,
|
previewCertificateType: formData.previewCertificateType,
|
||||||
previewCustomCertResolver: formData.previewCustomCertResolver,
|
previewCustomCertResolver: formData.previewCustomCertResolver,
|
||||||
|
previewRequireCollaboratorPermissions:
|
||||||
|
formData.previewRequireCollaboratorPermissions,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Preview Deployments settings updated");
|
toast.success("Preview Deployments settings updated");
|
||||||
@@ -138,7 +144,7 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
Configure
|
Configure
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl w-full">
|
<DialogContent className="sm:max-w-5xl w-full">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Preview Deployment Settings</DialogTitle>
|
<DialogTitle>Preview Deployment Settings</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -312,6 +318,37 @@ export const ShowPreviewSettings = ({ applicationId }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 lg:grid-cols-2">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="previewRequireCollaboratorPermissions"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm col-span-2">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>
|
||||||
|
Require Collaborator Permissions
|
||||||
|
</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Require collaborator permissions to preview
|
||||||
|
deployments, valid roles are:
|
||||||
|
<ul>
|
||||||
|
<li>Admin</li>
|
||||||
|
<li>Maintain</li>
|
||||||
|
<li>Write</li>
|
||||||
|
</ul>
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="env"
|
name="env"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from "@/components/ui/button";
|
|||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
@@ -232,14 +233,17 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-screen overflow-y-auto",
|
|
||||||
scheduleTypeForm === "dokploy-server" || scheduleTypeForm === "server"
|
scheduleTypeForm === "dokploy-server" || scheduleTypeForm === "server"
|
||||||
? "max-h-[95vh] sm:max-w-2xl"
|
? "sm:max-w-2xl"
|
||||||
: " sm:max-w-lg",
|
: "sm:max-w-lg",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{scheduleId ? "Edit" : "Create"} Schedule</DialogTitle>
|
<DialogTitle>{scheduleId ? "Edit" : "Create"} Schedule</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{scheduleId ? "Manage" : "Create"} a schedule to run a task at a
|
||||||
|
specific time or interval.
|
||||||
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={schedule.scheduleId}
|
key={schedule.scheduleId}
|
||||||
className=" flex items-center justify-between rounded-lg border p-3 transition-colors bg-muted/50"
|
className="flex items-center justify-between rounded-lg border p-3 transition-colors bg-muted/50"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">
|
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const UpdateApplication = ({ applicationId }: Props) => {
|
|||||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modify Application</DialogTitle>
|
<DialogTitle>Modify Application</DialogTitle>
|
||||||
<DialogDescription>Update the application data</DialogDescription>
|
<DialogDescription>Update the application data</DialogDescription>
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ export const HandleVolumeBackups = ({
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-screen overflow-y-auto",
|
"overflow-y-auto",
|
||||||
volumeBackupType === "compose" || volumeBackupType === "application"
|
volumeBackupType === "compose" || volumeBackupType === "application"
|
||||||
? "max-h-[95vh] sm:max-w-2xl"
|
? "max-h-[95vh] sm:max-w-2xl"
|
||||||
: " sm:max-w-lg",
|
: " sm:max-w-lg",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
import { DrawerLogs } from "@/components/shared/drawer-logs";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -42,9 +43,8 @@ import { useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
|
||||||
import { formatBytes } from "../../database/backups/restore-backup";
|
import { formatBytes } from "../../database/backups/restore-backup";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { type LogLine, parseLogs } from "../../docker/logs/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -161,7 +161,7 @@ export const RestoreVolumeBackups = ({ id, type, serverId }: Props) => {
|
|||||||
Restore Volume Backup
|
Restore Volume Backup
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">
|
<DialogTitle className="flex items-center">
|
||||||
<RotateCcw className="mr-2 size-4" />
|
<RotateCcw className="mr-2 size-4" />
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { HandleVolumeBackups } from "./handle-volume-backups";
|
|
||||||
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
import { ShowDeploymentsModal } from "../deployments/show-deployments-modal";
|
||||||
|
import { HandleVolumeBackups } from "./handle-volume-backups";
|
||||||
import { RestoreVolumeBackups } from "./restore-volume-backups";
|
import { RestoreVolumeBackups } from "./restore-volume-backups";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -113,7 +113,7 @@ export const ShowVolumeBackups = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={volumeBackup.volumeBackupId}
|
key={volumeBackup.volumeBackupId}
|
||||||
className=" flex items-center justify-between rounded-lg border p-3 transition-colors bg-muted/50"
|
className="flex items-center justify-between rounded-lg border p-3 transition-colors bg-muted/50"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">
|
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-primary/5">
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export const DeleteService = ({ id, type }: Props) => {
|
|||||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
|
||||||
import {
|
import {
|
||||||
BitbucketIcon,
|
BitbucketIcon,
|
||||||
GitIcon,
|
GitIcon,
|
||||||
@@ -11,6 +12,7 @@ import { api } from "@/utils/api";
|
|||||||
import { CodeIcon, GitBranch, Loader2 } from "lucide-react";
|
import { CodeIcon, GitBranch, Loader2 } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { ComposeFileEditor } from "../compose-file-editor";
|
import { ComposeFileEditor } from "../compose-file-editor";
|
||||||
import { ShowConvertedCompose } from "../show-converted-compose";
|
import { ShowConvertedCompose } from "../show-converted-compose";
|
||||||
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
|
import { SaveBitbucketProviderCompose } from "./save-bitbucket-provider-compose";
|
||||||
@@ -18,8 +20,6 @@ import { SaveGitProviderCompose } from "./save-git-provider-compose";
|
|||||||
import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose";
|
import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose";
|
||||||
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
||||||
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
|
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
|
||||||
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea";
|
type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea";
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -142,8 +142,8 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
|
|||||||
setSab(e as TabState);
|
setSab(e as TabState);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center justify-between w-full gap-4">
|
<div className="flex flex-row items-center justify-between w-full overflow-auto">
|
||||||
<TabsList className="md:grid md:w-fit md:grid-cols-6 max-md:overflow-x-scroll justify-start bg-transparent overflow-y-hidden">
|
<TabsList className="flex gap-4 justify-start bg-transparent">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
value="github"
|
value="github"
|
||||||
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
className="rounded-none border-b-2 gap-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const ShowConvertedCompose = ({ composeId }: Props) => {
|
|||||||
Preview Compose
|
Preview Compose
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-6xl max-h-[50rem] overflow-y-auto">
|
<DialogContent className="sm:max-w-6xl max-h-[50rem]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Converted Compose</DialogTitle>
|
<DialogTitle>Converted Compose</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const ShowUtilities = ({ composeId }: Props) => {
|
|||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="ghost">Show Utilities</Button>
|
<Button variant="ghost">Show Utilities</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-5xl">
|
<DialogContent className="sm:max-w-5xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Utilities </DialogTitle>
|
<DialogTitle>Utilities </DialogTitle>
|
||||||
<DialogDescription>Modify the application data</DialogDescription>
|
<DialogDescription>Modify the application data</DialogDescription>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const UpdateCompose = ({ composeId }: Props) => {
|
|||||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modify Compose</DialogTitle>
|
<DialogTitle>Modify Compose</DialogTitle>
|
||||||
<DialogDescription>Update the compose data</DialogDescription>
|
<DialogDescription>Update the compose data</DialogDescription>
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ export const HandleBackup = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-2xl max-h-screen overflow-y-auto">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{backupId ? "Update Backup" : "Create Backup"}
|
{backupId ? "Update Backup" : "Create Backup"}
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export const RestoreBackup = ({
|
|||||||
Restore Backup
|
Restore Backup
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">
|
<DialogTitle className="flex items-center">
|
||||||
<RotateCcw className="mr-2 size-4" />
|
<RotateCcw className="mr-2 size-4" />
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const ShowContainerConfig = ({ containerId, serverId }: Props) => {
|
|||||||
See in detail the config of this container
|
See in detail the config of this container
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="text-wrap rounded-lg border p-4 text-sm bg-card overflow-y-auto max-h-[80vh]">
|
<div className="text-wrap rounded-lg border p-4 overflow-y-auto text-sm bg-card max-h-[80vh]">
|
||||||
<code>
|
<code>
|
||||||
<pre className="whitespace-pre-wrap break-words">
|
<pre className="whitespace-pre-wrap break-words">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const ShowDockerModalLogs = ({
|
|||||||
{children}
|
{children}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl">
|
<DialogContent className="sm:max-w-7xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>View Logs</DialogTitle>
|
<DialogTitle>View Logs</DialogTitle>
|
||||||
<DialogDescription>View the logs for {containerId}</DialogDescription>
|
<DialogDescription>View the logs for {containerId}</DialogDescription>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const ShowDockerModalStackLogs = ({
|
|||||||
{children}
|
{children}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl">
|
<DialogContent className="sm:max-w-7xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>View Logs</DialogTitle>
|
<DialogTitle>View Logs</DialogTitle>
|
||||||
<DialogDescription>View the logs for {containerId}</DialogDescription>
|
<DialogDescription>View the logs for {containerId}</DialogDescription>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const DockerTerminalModal = ({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="max-h-screen overflow-y-auto sm:max-w-7xl"
|
className="sm:max-w-7xl"
|
||||||
onEscapeKeyDown={(event) => event.preventDefault()}
|
onEscapeKeyDown={(event) => event.preventDefault()}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
|
|||||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modify MariaDB</DialogTitle>
|
<DialogTitle>Modify MariaDB</DialogTitle>
|
||||||
<DialogDescription>Update the MariaDB data</DialogDescription>
|
<DialogDescription>Update the MariaDB data</DialogDescription>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const UpdateMongo = ({ mongoId }: Props) => {
|
|||||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modify MongoDB</DialogTitle>
|
<DialogTitle>Modify MongoDB</DialogTitle>
|
||||||
<DialogDescription>Update the MongoDB data</DialogDescription>
|
<DialogDescription>Update the MongoDB data</DialogDescription>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export const ContainerPaidMonitoring = ({ appName, baseUrl, token }: Props) => {
|
|||||||
? queryError.message
|
? queryError.message
|
||||||
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
|
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
|
||||||
</p>
|
</p>
|
||||||
<p className=" text-sm text-muted-foreground">URL: {baseUrl}</p>
|
<p className="text-sm text-muted-foreground">URL: {baseUrl}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export const ShowPaidMonitoring = ({
|
|||||||
? queryError.message
|
? queryError.message
|
||||||
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
|
: "Failed to fetch metrics, Please check your monitoring Instance is Configured correctly."}
|
||||||
</p>
|
</p>
|
||||||
<p className=" text-sm text-muted-foreground">URL: {BASE_URL}</p>
|
<p className="text-sm text-muted-foreground">URL: {BASE_URL}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
|
|||||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modify MySQL</DialogTitle>
|
<DialogTitle>Modify MySQL</DialogTitle>
|
||||||
<DialogDescription>Update the MySQL data</DialogDescription>
|
<DialogDescription>Update the MySQL data</DialogDescription>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export function AddOrganization({ organizationId }: Props) {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="logo"
|
name="logo"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className=" gap-4">
|
<FormItem className="gap-4">
|
||||||
<FormLabel className="text-right">Logo URL</FormLabel>
|
<FormLabel className="text-right">Logo URL</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@@ -169,7 +169,7 @@ export function AddOrganization({ organizationId }: Props) {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<DialogFooter className="mt-4">
|
<DialogFooter>
|
||||||
<Button type="submit" isLoading={isLoading}>
|
<Button type="submit" isLoading={isLoading}>
|
||||||
{organizationId ? "Update organization" : "Create organization"}
|
{organizationId ? "Update organization" : "Create organization"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
|
|||||||
<PenBox className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBox className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modify Postgres</DialogTitle>
|
<DialogTitle>Modify Postgres</DialogTitle>
|
||||||
<DialogDescription>Update the Postgres data</DialogDescription>
|
<DialogDescription>Update the Postgres data</DialogDescription>
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Folder, HelpCircle } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -37,12 +43,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { slugify } from "@/lib/slug";
|
import { slugify } from "@/lib/slug";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { Folder, HelpCircle } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const AddTemplateSchema = z.object({
|
const AddTemplateSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
@@ -75,6 +75,8 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
|||||||
const slug = slugify(projectName);
|
const slug = slugify(projectName);
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
|
|
||||||
|
const hasServers = servers && servers.length > 0;
|
||||||
|
|
||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
api.application.create.useMutation();
|
api.application.create.useMutation();
|
||||||
|
|
||||||
@@ -119,7 +121,7 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
|||||||
<span>Application</span>
|
<span>Application</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create</DialogTitle>
|
<DialogTitle>Create</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -155,68 +157,84 @@ export const AddApplication = ({ projectId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{hasServers && (
|
||||||
control={form.control}
|
<FormField
|
||||||
name="serverId"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="serverId"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<TooltipProvider delayDuration={0}>
|
<FormItem>
|
||||||
<Tooltip>
|
<TooltipProvider delayDuration={0}>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
<TooltipTrigger asChild>
|
||||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||||
<HelpCircle className="size-4 text-muted-foreground" />
|
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||||
</FormLabel>
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</FormLabel>
|
||||||
<TooltipContent
|
</TooltipTrigger>
|
||||||
className="z-[999] w-[300px]"
|
<TooltipContent
|
||||||
align="start"
|
className="z-[999] w-[300px]"
|
||||||
side="top"
|
align="start"
|
||||||
>
|
side="top"
|
||||||
<span>
|
>
|
||||||
If no server is selected, the application will be
|
<span>
|
||||||
deployed on the server where the user is logged in.
|
If no server is selected, the application will be
|
||||||
</span>
|
deployed on the server where the user is logged in.
|
||||||
</TooltipContent>
|
</span>
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectValue placeholder="Select a Server" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
value={server.serverId}
|
value={server.serverId}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span>{server.name}</span>
|
<span>{server.name}</span>
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
{server.ipAddress}
|
{server.ipAddress}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))}
|
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
</SelectGroup>
|
||||||
</SelectGroup>
|
</SelectContent>
|
||||||
</SelectContent>
|
</Select>
|
||||||
</Select>
|
<FormMessage />
|
||||||
<FormMessage />
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="appName"
|
name="appName"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>App Name</FormLabel>
|
<FormLabel className="flex items-center gap-2">
|
||||||
|
App Name
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">
|
||||||
|
<p>
|
||||||
|
This will be the name of the Docker Swarm service
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="my-app" {...field} />
|
<Input placeholder="my-app" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { CircuitBoard, HelpCircle } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -37,12 +43,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { slugify } from "@/lib/slug";
|
import { slugify } from "@/lib/slug";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { CircuitBoard, HelpCircle } from "lucide-react";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const AddComposeSchema = z.object({
|
const AddComposeSchema = z.object({
|
||||||
composeType: z.enum(["docker-compose", "stack"]).optional(),
|
composeType: z.enum(["docker-compose", "stack"]).optional(),
|
||||||
@@ -78,6 +78,8 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
|||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
api.compose.create.useMutation();
|
api.compose.create.useMutation();
|
||||||
|
|
||||||
|
const hasServers = servers && servers.length > 0;
|
||||||
|
|
||||||
const form = useForm<AddCompose>({
|
const form = useForm<AddCompose>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
@@ -124,7 +126,7 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
|||||||
<span>Compose</span>
|
<span>Compose</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-xl">
|
<DialogContent className="sm:max-w-xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create Compose</DialogTitle>
|
<DialogTitle>Create Compose</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -163,62 +165,64 @@ export const AddCompose = ({ projectId, projectName }: Props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<FormField
|
{hasServers && (
|
||||||
control={form.control}
|
<FormField
|
||||||
name="serverId"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="serverId"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<TooltipProvider delayDuration={0}>
|
<FormItem>
|
||||||
<Tooltip>
|
<TooltipProvider delayDuration={0}>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
<TooltipTrigger asChild>
|
||||||
Select a Server {!isCloud ? "(Optional)" : ""}
|
<FormLabel className="break-all w-fit flex flex-row gap-1 items-center">
|
||||||
<HelpCircle className="size-4 text-muted-foreground" />
|
Select a Server {!isCloud ? "(Optional)" : ""}
|
||||||
</FormLabel>
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</FormLabel>
|
||||||
<TooltipContent
|
</TooltipTrigger>
|
||||||
className="z-[999] w-[300px]"
|
<TooltipContent
|
||||||
align="start"
|
className="z-[999] w-[300px]"
|
||||||
side="top"
|
align="start"
|
||||||
>
|
side="top"
|
||||||
<span>
|
>
|
||||||
If no server is selected, the application will be
|
<span>
|
||||||
deployed on the server where the user is logged in.
|
If no server is selected, the application will be
|
||||||
</span>
|
deployed on the server where the user is logged in.
|
||||||
</TooltipContent>
|
</span>
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectValue placeholder="Select a Server" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
value={server.serverId}
|
value={server.serverId}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span>{server.name}</span>
|
<span>{server.name}</span>
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
{server.ipAddress}
|
{server.ipAddress}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))}
|
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
</SelectGroup>
|
||||||
</SelectGroup>
|
</SelectContent>
|
||||||
</SelectContent>
|
</Select>
|
||||||
</Select>
|
<FormMessage />
|
||||||
<FormMessage />
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="appName"
|
name="appName"
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { AlertTriangle, Database, HelpCircle } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
MariadbIcon,
|
MariadbIcon,
|
||||||
MongodbIcon,
|
MongodbIcon,
|
||||||
@@ -37,14 +43,14 @@ import {
|
|||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
import { slugify } from "@/lib/slug";
|
import { slugify } from "@/lib/slug";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { AlertTriangle, Database } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
type DbType = typeof mySchema._type.type;
|
type DbType = typeof mySchema._type.type;
|
||||||
|
|
||||||
@@ -163,6 +169,8 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
|||||||
const mariadbMutation = api.mariadb.create.useMutation();
|
const mariadbMutation = api.mariadb.create.useMutation();
|
||||||
const mysqlMutation = api.mysql.create.useMutation();
|
const mysqlMutation = api.mysql.create.useMutation();
|
||||||
|
|
||||||
|
const hasServers = servers && servers.length > 0;
|
||||||
|
|
||||||
const form = useForm<AddDatabase>({
|
const form = useForm<AddDatabase>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
type: "postgres",
|
type: "postgres",
|
||||||
@@ -283,7 +291,7 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
|||||||
<span>Database</span>
|
<span>Database</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen md:max-h-[90vh] overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="md:max-h-[90vh] sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Databases</DialogTitle>
|
<DialogTitle>Databases</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
@@ -374,45 +382,62 @@ export const AddDatabase = ({ projectId, projectName }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{hasServers && (
|
||||||
control={form.control}
|
<FormField
|
||||||
name="serverId"
|
control={form.control}
|
||||||
render={({ field }) => (
|
name="serverId"
|
||||||
<FormItem>
|
render={({ field }) => (
|
||||||
<FormLabel>Select a Server</FormLabel>
|
<FormItem>
|
||||||
<Select
|
<FormLabel>Select a Server</FormLabel>
|
||||||
onValueChange={field.onChange}
|
<Select
|
||||||
defaultValue={field.value || ""}
|
onValueChange={field.onChange}
|
||||||
>
|
defaultValue={field.value || ""}
|
||||||
<SelectTrigger>
|
>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select a Server" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectGroup>
|
<SelectContent>
|
||||||
{servers?.map((server) => (
|
<SelectGroup>
|
||||||
<SelectItem
|
{servers?.map((server) => (
|
||||||
key={server.serverId}
|
<SelectItem
|
||||||
value={server.serverId}
|
key={server.serverId}
|
||||||
>
|
value={server.serverId}
|
||||||
{server.name}
|
>
|
||||||
</SelectItem>
|
{server.name}
|
||||||
))}
|
</SelectItem>
|
||||||
<SelectLabel>
|
))}
|
||||||
Servers ({servers?.length})
|
<SelectLabel>
|
||||||
</SelectLabel>
|
Servers ({servers?.length})
|
||||||
</SelectGroup>
|
</SelectLabel>
|
||||||
</SelectContent>
|
</SelectGroup>
|
||||||
</Select>
|
</SelectContent>
|
||||||
<FormMessage />
|
</Select>
|
||||||
</FormItem>
|
<FormMessage />
|
||||||
)}
|
</FormItem>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="appName"
|
name="appName"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>App Name</FormLabel>
|
<FormLabel className="flex items-center gap-2">
|
||||||
|
App Name
|
||||||
|
<TooltipProvider delayDuration={0}>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="right">
|
||||||
|
<p>
|
||||||
|
This will be the name of the Docker Swarm
|
||||||
|
service
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="my-app" {...field} />
|
<Input placeholder="my-app" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
import {
|
||||||
|
BookText,
|
||||||
|
CheckIcon,
|
||||||
|
ChevronsUpDown,
|
||||||
|
Globe,
|
||||||
|
HelpCircle,
|
||||||
|
LayoutGrid,
|
||||||
|
List,
|
||||||
|
Loader2,
|
||||||
|
PuzzleIcon,
|
||||||
|
SearchIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
import { GithubIcon } from "@/components/icons/data-tools-icons";
|
||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
@@ -54,21 +69,6 @@ import {
|
|||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
|
||||||
BookText,
|
|
||||||
CheckIcon,
|
|
||||||
ChevronsUpDown,
|
|
||||||
Globe,
|
|
||||||
HelpCircle,
|
|
||||||
LayoutGrid,
|
|
||||||
List,
|
|
||||||
Loader2,
|
|
||||||
PuzzleIcon,
|
|
||||||
SearchIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url";
|
const TEMPLATE_BASE_URL_KEY = "dokploy_template_base_url";
|
||||||
|
|
||||||
@@ -137,6 +137,8 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
|||||||
return matchesTags && matchesQuery;
|
return matchesTags && matchesQuery;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
|
const hasServers = servers && servers.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger className="w-full">
|
<DialogTrigger className="w-full">
|
||||||
@@ -148,7 +150,7 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
|||||||
<span>Template</span>
|
<span>Template</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen sm:max-w-[90vw] p-0">
|
<DialogContent className="sm:max-w-[90vw] p-0">
|
||||||
<DialogHeader className="sticky top-0 z-10 bg-background p-6 border-b">
|
<DialogHeader className="sticky top-0 z-10 bg-background p-6 border-b">
|
||||||
<div className="flex flex-col space-y-6">
|
<div className="flex flex-col space-y-6">
|
||||||
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
|
<div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6">
|
||||||
@@ -425,60 +427,62 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
|||||||
project.
|
project.
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
|
|
||||||
<div>
|
{hasServers && (
|
||||||
<TooltipProvider delayDuration={0}>
|
<div>
|
||||||
<Tooltip>
|
<TooltipProvider delayDuration={0}>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<Label className="break-all w-fit flex flex-row gap-1 items-center pb-2 pt-3.5">
|
<TooltipTrigger asChild>
|
||||||
Select a Server{" "}
|
<Label className="break-all w-fit flex flex-row gap-1 items-center pb-2 pt-3.5">
|
||||||
{!isCloud ? "(Optional)" : ""}
|
Select a Server{" "}
|
||||||
<HelpCircle className="size-4 text-muted-foreground" />
|
{!isCloud ? "(Optional)" : ""}
|
||||||
</Label>
|
<HelpCircle className="size-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</Label>
|
||||||
<TooltipContent
|
</TooltipTrigger>
|
||||||
className="z-[999] w-[300px]"
|
<TooltipContent
|
||||||
align="start"
|
className="z-[999] w-[300px]"
|
||||||
side="top"
|
align="start"
|
||||||
>
|
side="top"
|
||||||
<span>
|
>
|
||||||
If no server is selected, the application
|
<span>
|
||||||
will be deployed on the server where the
|
If no server is selected, the
|
||||||
user is logged in.
|
application will be deployed on the
|
||||||
</span>
|
server where the user is logged in.
|
||||||
</TooltipContent>
|
</span>
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
setServerId(e);
|
setServerId(e);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select a Server" />
|
<SelectValue placeholder="Select a Server" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{servers?.map((server) => (
|
{servers?.map((server) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={server.serverId}
|
key={server.serverId}
|
||||||
value={server.serverId}
|
value={server.serverId}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2 justify-between w-full">
|
<span className="flex items-center gap-2 justify-between w-full">
|
||||||
<span>{server.name}</span>
|
<span>{server.name}</span>
|
||||||
<span className="text-muted-foreground text-xs self-center">
|
<span className="text-muted-foreground text-xs self-center">
|
||||||
{server.ipAddress}
|
{server.ipAddress}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))}
|
<SelectLabel>
|
||||||
<SelectLabel>
|
Servers ({servers?.length})
|
||||||
Servers ({servers?.length})
|
</SelectLabel>
|
||||||
</SelectLabel>
|
</SelectGroup>
|
||||||
</SelectGroup>
|
</SelectContent>
|
||||||
</SelectContent>
|
</Select>
|
||||||
</Select>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const examples = [
|
|||||||
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
||||||
// Get servers from the API
|
// Get servers from the API
|
||||||
const { data: servers } = api.server.withSSHKey.useQuery();
|
const { data: servers } = api.server.withSSHKey.useQuery();
|
||||||
|
const hasServers = servers && servers.length > 0;
|
||||||
|
|
||||||
const handleExampleClick = (example: string) => {
|
const handleExampleClick = (example: string) => {
|
||||||
setTemplateInfo({ ...templateInfo, userInput: example });
|
setTemplateInfo({ ...templateInfo, userInput: example });
|
||||||
@@ -47,37 +48,39 @@ export const StepOne = ({ setTemplateInfo, templateInfo }: any) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
{hasServers && (
|
||||||
<Label htmlFor="server-deploy">
|
<div className="space-y-2">
|
||||||
Select the server where you want to deploy (optional)
|
<Label htmlFor="server-deploy">
|
||||||
</Label>
|
Select the server where you want to deploy (optional)
|
||||||
<Select
|
</Label>
|
||||||
value={templateInfo.server?.serverId}
|
<Select
|
||||||
onValueChange={(value) => {
|
value={templateInfo.server?.serverId}
|
||||||
const server = servers?.find((s) => s.serverId === value);
|
onValueChange={(value) => {
|
||||||
if (server) {
|
const server = servers?.find((s) => s.serverId === value);
|
||||||
setTemplateInfo({
|
if (server) {
|
||||||
...templateInfo,
|
setTemplateInfo({
|
||||||
server: server,
|
...templateInfo,
|
||||||
});
|
server: server,
|
||||||
}
|
});
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
<SelectTrigger className="w-full">
|
>
|
||||||
<SelectValue placeholder="Select a server" />
|
<SelectTrigger className="w-full">
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select a server" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectGroup>
|
<SelectContent>
|
||||||
{servers?.map((server) => (
|
<SelectGroup>
|
||||||
<SelectItem key={server.serverId} value={server.serverId}>
|
{servers?.map((server) => (
|
||||||
{server.name}
|
<SelectItem key={server.serverId} value={server.serverId}>
|
||||||
</SelectItem>
|
{server.name}
|
||||||
))}
|
</SelectItem>
|
||||||
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
))}
|
||||||
</SelectGroup>
|
<SelectLabel>Servers ({servers?.length})</SelectLabel>
|
||||||
</SelectContent>
|
</SelectGroup>
|
||||||
</Select>
|
</SelectContent>
|
||||||
</div>
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Examples:</Label>
|
<Label>Examples:</Label>
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
|||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Generating template suggestions based on your input...
|
Generating template suggestions based on your input...
|
||||||
</p>
|
</p>
|
||||||
<pre>{templateInfo.userInput}</pre>
|
<pre className="whitespace-normal">{templateInfo.userInput}</pre>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
|||||||
<AccordionItem value="description">
|
<AccordionItem value="description">
|
||||||
<AccordionTrigger>Description</AccordionTrigger>
|
<AccordionTrigger>Description</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<ScrollArea className=" w-full rounded-md border p-4">
|
<ScrollArea className="w-full rounded-md border p-4">
|
||||||
<ReactMarkdown className="text-muted-foreground text-sm">
|
<ReactMarkdown className="text-muted-foreground text-sm">
|
||||||
{selectedVariant?.description}
|
{selectedVariant?.description}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
@@ -289,7 +289,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
|||||||
<AccordionItem value="env-variables">
|
<AccordionItem value="env-variables">
|
||||||
<AccordionTrigger>Environment Variables</AccordionTrigger>
|
<AccordionTrigger>Environment Variables</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<ScrollArea className=" w-full rounded-md border">
|
<ScrollArea className="w-full rounded-md border">
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
{selectedVariant?.envVariables.map((env, index) => (
|
{selectedVariant?.envVariables.map((env, index) => (
|
||||||
<div
|
<div
|
||||||
@@ -364,7 +364,7 @@ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
|
|||||||
<AccordionItem value="domains">
|
<AccordionItem value="domains">
|
||||||
<AccordionTrigger>Domains</AccordionTrigger>
|
<AccordionTrigger>Domains</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<ScrollArea className=" w-full rounded-md border">
|
<ScrollArea className="w-full rounded-md border">
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
{selectedVariant?.domains.map((domain, index) => (
|
{selectedVariant?.domains.map((domain, index) => (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export const TemplateGenerator = ({ projectId }: Props) => {
|
|||||||
<span>AI Assistant</span>
|
<span>AI Assistant</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl w-full flex flex-col">
|
<DialogContent className="sm:max-w-4xl w-full flex flex-col">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>AI Assistant</DialogTitle>
|
<DialogTitle>AI Assistant</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const ProjectEnvironment = ({ projectId, children }: Props) => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-6xl">
|
<DialogContent className="sm:max-w-6xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Project Environment</DialogTitle>
|
<DialogTitle>Project Environment</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
|
|||||||
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Modify Redis</DialogTitle>
|
<DialogTitle>Modify Redis</DialogTitle>
|
||||||
<DialogDescription>Update the redis data</DialogDescription>
|
<DialogDescription>Update the redis data</DialogDescription>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const columns: ColumnDef<LogEntry>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const log = row.original;
|
const log = row.original;
|
||||||
return (
|
return (
|
||||||
<div className=" flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex items-center flex-row gap-3 ">
|
<div className="flex items-center flex-row gap-3 ">
|
||||||
{log.RequestMethod}{" "}
|
{log.RequestMethod}{" "}
|
||||||
<div className="inline-flex items-center gap-2 bg-muted px-1.5 py-1 rounded-lg">
|
<div className="inline-flex items-center gap-2 bg-muted px-1.5 py-1 rounded-lg">
|
||||||
@@ -86,7 +86,7 @@ export const columns: ColumnDef<LogEntry>[] = [
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const log = row.original;
|
const log = row.original;
|
||||||
return (
|
return (
|
||||||
<div className=" flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex flex-row gap-3 w-full">
|
<div className="flex flex-row gap-3 w-full">
|
||||||
{format(new Date(log.StartUTC), "yyyy-MM-dd HH:mm:ss")}
|
{format(new Date(log.StartUTC), "yyyy-MM-dd HH:mm:ss")}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export const AddApiKey = () => {
|
|||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>Generate New Key</Button>
|
<Button>Generate New Key</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="sm:max-w-xl max-h-[90vh]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Generate API Key</DialogTitle>
|
<DialogTitle>Generate API Key</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ 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(
|
{calculatePrice(
|
||||||
serverQuantity,
|
serverQuantity,
|
||||||
@@ -180,7 +180,7 @@ export const ShowBilling = () => {
|
|||||||
USD
|
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
|
calculatePrice(serverQuantity, isAnnual) / 12
|
||||||
@@ -189,7 +189,7 @@ export const ShowBilling = () => {
|
|||||||
</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(
|
{calculatePrice(serverQuantity, isAnnual).toFixed(
|
||||||
2,
|
2,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const ShowWelcomeDokploy = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dialog open={open} onOpenChange={handleClose}>
|
<Dialog open={open} onOpenChange={handleClose}>
|
||||||
<DialogContent className="sm:max-w-xl max-h-screen overflow-y-auto">
|
<DialogContent className="sm:max-w-xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-2xl font-semibold text-center">
|
<DialogTitle className="text-2xl font-semibold text-center">
|
||||||
Welcome to Dokploy Cloud 🎉
|
Welcome to Dokploy Cloud 🎉
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export const AddCertificate = () => {
|
|||||||
Add Certificate
|
Add Certificate
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add New Certificate</DialogTitle>
|
<DialogTitle>Add New Certificate</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -222,7 +222,7 @@ export const AddCertificate = () => {
|
|||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<DialogFooter className="flex w-full flex-row !justify-end pt-3">
|
<DialogFooter className="flex w-full flex-row !justify-end">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
form="hook-form-add-certificate"
|
form="hook-form-add-certificate"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const AddNode = ({ serverId }: Props) => {
|
|||||||
Add Node
|
Add Node
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl">
|
<DialogContent className="sm:max-w-4xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add Node</DialogTitle>
|
<DialogTitle>Add Node</DialogTitle>
|
||||||
<DialogDescription className="flex flex-col gap-2">
|
<DialogDescription className="flex flex-col gap-2">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const ShowNodeData = ({ data }: Props) => {
|
|||||||
View Config
|
View Config
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
|
<DialogContent className={"sm:max-w-5xl"}>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Node Config</DialogTitle>
|
<DialogTitle>Node Config</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const ShowNodesModal = ({ serverId }: Props) => {
|
|||||||
Show Swarm Nodes
|
Show Swarm Nodes
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="min-w-[70vw] overflow-y-auto max-h-screen">
|
<DialogContent className="min-w-[70vw]">
|
||||||
<div className="grid w-full gap-1">
|
<div className="grid w-full gap-1">
|
||||||
<ShowNodes serverId={serverId} />
|
<ShowNodes serverId={serverId} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export const HandleRegistry = ({ registryId }: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-2xl max-h-screen overflow-y-auto">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Add a external registry</DialogTitle>
|
<DialogTitle>Add a external registry</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -316,7 +316,7 @@ export const HandleRegistry = ({ registryId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col col-span-2 mt-6">
|
<DialogFooter className="flex flex-col w-full sm:justify-between gap-4 flex-wrap sm:flex-col col-span-2">
|
||||||
<div className="flex flex-row gap-2 justify-between">
|
<div className="flex flex-row gap-2 justify-between">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!destinationId,
|
enabled: !!destinationId,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
@@ -204,7 +205,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{destinationId ? "Update" : "Add"} Destination
|
{destinationId ? "Update" : "Add"} Destination
|
||||||
@@ -359,7 +360,7 @@ export const HandleDestinations = ({ destinationId }: Props) => {
|
|||||||
<DialogFooter
|
<DialogFooter
|
||||||
className={cn(
|
className={cn(
|
||||||
isCloud ? "!flex-col" : "flex-row",
|
isCloud ? "!flex-col" : "flex-row",
|
||||||
"flex w-full !justify-between pt-3 gap-4",
|
"flex w-full !justify-between gap-4",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isCloud ? (
|
{isCloud ? (
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user