Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c8721406a | ||
|
|
5db5336ec8 | ||
|
|
a6e7edd4d9 | ||
|
|
ddbb414225 | ||
|
|
b73889dd4f | ||
|
|
ce2dce3401 | ||
|
|
173110a415 | ||
|
|
2307346ae3 | ||
|
|
2f175f0e44 | ||
|
|
7003fe77c9 | ||
|
|
04235fb6c9 | ||
|
|
f138b0917f | ||
|
|
82367213ea | ||
|
|
3c490ba2d0 | ||
|
|
4bf5e5ca06 | ||
|
|
6af5742702 | ||
|
|
3015d69adc | ||
|
|
f7fa8e74af | ||
|
|
2835c997e9 | ||
|
|
bd55e3751f | ||
|
|
74374bd643 | ||
|
|
4e929c12f2 | ||
|
|
56ea356723 | ||
|
|
b1f7d05743 | ||
|
|
58338380ab | ||
|
|
2487e3e062 | ||
|
|
01a882497f | ||
|
|
036313e3c3 | ||
|
|
d3304052b0 | ||
|
|
9efd2e3d5c | ||
|
|
1e7d9fc3aa | ||
|
|
70ca219c0a | ||
|
|
3a07d8de2e | ||
|
|
f4f1fc28a0 | ||
|
|
4c71d3a95f | ||
|
|
af84942d22 | ||
|
|
1aaff0594d | ||
|
|
69a4a87079 | ||
|
|
3eef4aa016 | ||
|
|
2492581bde | ||
|
|
be934065d9 | ||
|
|
82fc9897d2 | ||
|
|
8162dcfb71 | ||
|
|
b6ab653ef3 | ||
|
|
17e9a1a497 | ||
|
|
46f7d43595 | ||
|
|
6961ee1fc0 | ||
|
|
8e532d5a60 | ||
|
|
fa791706a0 | ||
|
|
a5c1f8ef49 | ||
|
|
6866c3b116 | ||
|
|
444302e7b9 | ||
|
|
9b77573269 | ||
|
|
8157dd9eaa | ||
|
|
f1fc3f161a | ||
|
|
06af2042ee | ||
|
|
8900e30ae7 | ||
|
|
b5fa411093 | ||
|
|
cab9443d25 | ||
|
|
de315124c3 | ||
|
|
7bef3a0c29 | ||
|
|
82afd486da | ||
|
|
69c9e86a13 | ||
|
|
3d59e289be | ||
|
|
59bb59ee24 | ||
|
|
8d33ff5fb5 | ||
|
|
46219e1b3d | ||
|
|
3457de4f36 | ||
|
|
e57efa2e31 | ||
|
|
fb0308fd60 | ||
|
|
b376ead7b5 | ||
|
|
d081d477ef | ||
|
|
cf73f1f764 | ||
|
|
deeea11428 | ||
|
|
5c17797749 | ||
|
|
7b06fd47b8 | ||
|
|
faceed12b0 | ||
|
|
814580ff2c | ||
|
|
4f092b2fb3 | ||
|
|
0799f8e04c | ||
|
|
59c050b519 | ||
|
|
88f969917f | ||
|
|
ed470ee827 | ||
|
|
96584e5b32 | ||
|
|
a7165bef20 | ||
|
|
b0f5e7dad3 | ||
|
|
fa083257f1 | ||
|
|
5cf12e51d1 | ||
|
|
b6fd410af2 | ||
|
|
c5c3ca39cd | ||
|
|
0e433a3d36 | ||
|
|
b08a2f54f0 | ||
|
|
29ffdf2c71 | ||
|
|
58b185f6dd | ||
|
|
5f6d041248 | ||
|
|
b817b4b6ee | ||
|
|
819de5a32e | ||
|
|
e0fe4e4995 | ||
|
|
461d30fc26 | ||
|
|
3fee18d06e | ||
|
|
046923aeb3 | ||
|
|
4646b7d0ec | ||
|
|
a02d51e504 | ||
|
|
9728c49edd | ||
|
|
ed5b01c78d | ||
|
|
000091cfb9 | ||
|
|
2774701895 | ||
|
|
e238dd8510 | ||
|
|
c09ff25360 | ||
|
|
56b565b512 | ||
|
|
046f0a5c20 | ||
|
|
66c4d8f118 | ||
|
|
7f0a92f224 | ||
|
|
0ca8ee17be | ||
|
|
c765d7d9eb | ||
|
|
237106428b | ||
|
|
06e1e1ba76 | ||
|
|
9eb4c3e77d | ||
|
|
f3d8351208 | ||
|
|
a331020bf8 | ||
|
|
2e6d9c34c0 | ||
|
|
1e1409e651 | ||
|
|
ceaa32fd00 | ||
|
|
f466e697dd | ||
|
|
476057663b | ||
|
|
b53da82204 | ||
|
|
3b5e8921d0 | ||
|
|
1b1d0597fe | ||
|
|
dfa73a3d7c | ||
|
|
2a24e1d7e8 | ||
|
|
5cd624c7ea | ||
|
|
34b12a0315 | ||
|
|
e9e6064eb6 | ||
|
|
6b7712e35f | ||
|
|
7306d8c513 | ||
|
|
84aac40410 | ||
|
|
e40a0fdc50 | ||
|
|
e3677995b9 | ||
|
|
0468c25bf8 | ||
|
|
bc097c7667 | ||
|
|
89c7e96df0 | ||
|
|
8af5afbb6c | ||
|
|
ed7150fac1 | ||
|
|
ef9f16a3c3 | ||
|
|
d15b6387a3 | ||
|
|
2e9e39dcf5 | ||
|
|
d901b02e92 | ||
|
|
766a25ccad | ||
|
|
1b6d8d803b | ||
|
|
fae97b1817 | ||
|
|
b2cc5e58a3 | ||
|
|
2f8b89c8a6 | ||
|
|
2da650610c | ||
|
|
980024c9d2 | ||
|
|
1b56a6b400 | ||
|
|
996d449f0f | ||
|
|
3b197f3624 | ||
|
|
bba7d0c828 | ||
|
|
a04b69d0fd | ||
|
|
0e136ffb8f | ||
|
|
95e53169b1 | ||
|
|
de02a00ce9 | ||
|
|
25803f371c | ||
|
|
a4eb5c07e6 | ||
|
|
bad11f13f5 | ||
|
|
02d52d63b9 | ||
|
|
2821e43cdd | ||
|
|
adea440931 | ||
|
|
bbef99c3c2 | ||
|
|
527c01e7dc | ||
|
|
2a5a67e63c | ||
|
|
15051a1bc2 | ||
|
|
b7d45341bc | ||
|
|
1695c7cc81 | ||
|
|
3e467959c9 | ||
|
|
fbec26fc31 | ||
|
|
b3092691b7 | ||
|
|
5a440d934d | ||
|
|
96c5176984 | ||
|
|
433430118f | ||
|
|
4431f5601a | ||
|
|
bf48aa03a2 | ||
|
|
6d3ea8df59 | ||
|
|
e52a0fc9d4 | ||
|
|
15a76d2639 | ||
|
|
059c21c990 | ||
|
|
d833623ebf | ||
|
|
8b855d7ee4 | ||
|
|
706cde4ffd | ||
|
|
a39a7a276d | ||
|
|
0327334fcd | ||
|
|
3e0d4ebbd6 |
@@ -18,8 +18,10 @@ jobs:
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
TAG="latest"
|
||||
else
|
||||
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||
TAG="canary"
|
||||
else
|
||||
TAG="feature"
|
||||
fi
|
||||
docker build --platform linux/amd64 -t dokploy/dokploy:${TAG}-amd64 .
|
||||
docker push dokploy/dokploy:${TAG}-amd64
|
||||
@@ -41,8 +43,10 @@ jobs:
|
||||
docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN
|
||||
if [ "${CIRCLE_BRANCH}" == "main" ]; then
|
||||
TAG="latest"
|
||||
else
|
||||
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||
TAG="canary"
|
||||
else
|
||||
TAG="feature"
|
||||
fi
|
||||
docker build --platform linux/arm64 -t dokploy/dokploy:${TAG}-arm64 .
|
||||
docker push dokploy/dokploy:${TAG}-arm64
|
||||
@@ -72,12 +76,18 @@ jobs:
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${VERSION}
|
||||
else
|
||||
elif [ "${CIRCLE_BRANCH}" == "canary" ]; then
|
||||
TAG="canary"
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
else
|
||||
TAG="feature"
|
||||
docker manifest create dokploy/dokploy:${TAG} \
|
||||
dokploy/dokploy:${TAG}-amd64 \
|
||||
dokploy/dokploy:${TAG}-arm64
|
||||
docker manifest push dokploy/dokploy:${TAG}
|
||||
fi
|
||||
|
||||
workflows:
|
||||
@@ -89,12 +99,14 @@ workflows:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- fix/build-i18n
|
||||
- build-arm64:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- fix/build-i18n
|
||||
- combine-manifests:
|
||||
requires:
|
||||
- build-amd64
|
||||
@@ -104,3 +116,4 @@ workflows:
|
||||
only:
|
||||
- main
|
||||
- canary
|
||||
- fix/build-i18n
|
||||
|
||||
BIN
.github/sponsors/mandarin.png
vendored
Normal file
|
After Width: | Height: | Size: 22 KiB |
@@ -14,10 +14,12 @@ We have a few guidelines to follow when contributing to this project:
|
||||
|
||||
## Commit Convention
|
||||
|
||||
|
||||
Before you create a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
@@ -235,7 +237,7 @@ export function generate(schema: Schema): Template {
|
||||
|
||||
5. Add the logo or image of the template to `public/templates/plausible.svg`
|
||||
|
||||
### Recomendations
|
||||
### Recommendations
|
||||
|
||||
- Use the same name of the folder as the id of the template.
|
||||
- The logo should be in the public folder.
|
||||
|
||||
@@ -35,6 +35,7 @@ RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var
|
||||
COPY --from=build /prod/dokploy/.next ./.next
|
||||
COPY --from=build /prod/dokploy/dist ./dist
|
||||
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
|
||||
COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs
|
||||
COPY --from=build /prod/dokploy/public ./public
|
||||
COPY --from=build /prod/dokploy/package.json ./package.json
|
||||
COPY --from=build /prod/dokploy/drizzle ./drizzle
|
||||
|
||||
@@ -44,6 +44,7 @@ RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var
|
||||
COPY --from=build /prod/dokploy/.next ./.next
|
||||
COPY --from=build /prod/dokploy/dist ./dist
|
||||
COPY --from=build /prod/dokploy/next.config.mjs ./next.config.mjs
|
||||
COPY --from=build /prod/dokploy/next-i18next.config.cjs ./next-i18next.config.cjs
|
||||
COPY --from=build /prod/dokploy/public ./public
|
||||
COPY --from=build /prod/dokploy/package.json ./package.json
|
||||
COPY --from=build /prod/dokploy/drizzle ./drizzle
|
||||
|
||||
@@ -39,6 +39,8 @@ Dokploy includes multiple features to make your life easier.
|
||||
|
||||
To get started, run the following command on a VPS:
|
||||
|
||||
Want to skip the installation process? [Try the Dokploy Cloud](https://app.dokploy.com).
|
||||
|
||||
```bash
|
||||
curl -sSL https://dokploy.com/install.sh | sh
|
||||
```
|
||||
@@ -60,12 +62,15 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
|
||||
### Hero Sponsors 🎖
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 20px;">
|
||||
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" style="display: inline-block;">
|
||||
<a href="https://www.hostinger.com/vps-hosting?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
||||
<img src=".github/sponsors/hostinger.jpg" alt="Hostinger" height="50"/>
|
||||
</a>
|
||||
<a href="https://www.lxaer.com/?ref=dokploy" target="_blank" style="display: inline-block;">
|
||||
<a href="https://www.lxaer.com/?ref=dokploy" target="_blank" style="display: inline-block; margin-right: 10px;">
|
||||
<img src=".github/sponsors/lxaer.png" alt="LX Aer" height="50"/>
|
||||
</a>
|
||||
<a href="https://mandarin3d.com/?ref=dokploy" target="_blank" style="display: inline-block;">
|
||||
<img src=".github/sponsors/mandarin.png" alt="Mandarin" height="50"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Premium Supporters 🥇
|
||||
|
||||
@@ -39,6 +39,24 @@ describe("createDomainLabels", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should add the path prefix if is different than / empty", async () => {
|
||||
const labels = await createDomainLabels(
|
||||
appName,
|
||||
{
|
||||
...baseDomain,
|
||||
path: "/hello",
|
||||
},
|
||||
"websecure",
|
||||
);
|
||||
|
||||
expect(labels).toEqual([
|
||||
"traefik.http.routers.test-app-1-websecure.rule=Host(`example.com`) && PathPrefix(`/hello`)",
|
||||
"traefik.http.routers.test-app-1-websecure.entrypoints=websecure",
|
||||
"traefik.http.services.test-app-1-websecure.loadbalancer.server.port=8080",
|
||||
"traefik.http.routers.test-app-1-websecure.service=test-app-1-websecure",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should add redirect middleware for https on web entrypoint", async () => {
|
||||
const httpsBaseDomain = { ...baseDomain, https: true };
|
||||
const labels = await createDomainLabels(appName, httpsBaseDomain, "web");
|
||||
|
||||
@@ -32,6 +32,14 @@ const baseApp: ApplicationNested = {
|
||||
serverId: "",
|
||||
branch: null,
|
||||
dockerBuildStage: "",
|
||||
project: {
|
||||
env: "",
|
||||
adminId: "",
|
||||
name: "",
|
||||
description: "",
|
||||
createdAt: "",
|
||||
projectId: "",
|
||||
},
|
||||
buildArgs: null,
|
||||
buildPath: "/",
|
||||
gitlabPathNamespace: "",
|
||||
|
||||
179
apps/dokploy/__test__/env/shared.test.ts
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
import { prepareEnvironmentVariables } from "@dokploy/server/index";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const projectEnv = `
|
||||
ENVIRONMENT=staging
|
||||
DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db
|
||||
PORT=3000
|
||||
`;
|
||||
const serviceEnv = `
|
||||
ENVIRONMENT=\${{project.ENVIRONMENT}}
|
||||
DATABASE_URL=\${{project.DATABASE_URL}}
|
||||
SERVICE_PORT=4000
|
||||
`;
|
||||
|
||||
describe("prepareEnvironmentVariables", () => {
|
||||
it("resolves project variables correctly", () => {
|
||||
const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"ENVIRONMENT=staging",
|
||||
"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
|
||||
"SERVICE_PORT=4000",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles undefined project variables", () => {
|
||||
const incompleteProjectEnv = `
|
||||
NODE_ENV=production
|
||||
`;
|
||||
|
||||
const invalidServiceEnv = `
|
||||
UNDEFINED_VAR=\${{project.UNDEFINED_VAR}}
|
||||
`;
|
||||
|
||||
expect(
|
||||
() =>
|
||||
prepareEnvironmentVariables(invalidServiceEnv, incompleteProjectEnv), // Cambiado el orden
|
||||
).toThrow("Invalid project environment variable: project.UNDEFINED_VAR");
|
||||
});
|
||||
it("allows service-specific variables to override project variables", () => {
|
||||
const serviceSpecificEnv = `
|
||||
ENVIRONMENT=production
|
||||
DATABASE_URL=\${{project.DATABASE_URL}}
|
||||
`;
|
||||
|
||||
const resolved = prepareEnvironmentVariables(
|
||||
serviceSpecificEnv,
|
||||
projectEnv,
|
||||
);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"ENVIRONMENT=production", // Overrides project variable
|
||||
"DATABASE_URL=postgres://postgres:postgres@localhost:5432/project_db",
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves complex references for dynamic endpoints", () => {
|
||||
const projectEnv = `
|
||||
BASE_URL=https://api.example.com
|
||||
API_VERSION=v1
|
||||
PORT=8000
|
||||
`;
|
||||
const serviceEnv = `
|
||||
API_ENDPOINT=\${{project.BASE_URL}}/\${{project.API_VERSION}}/endpoint
|
||||
SERVICE_PORT=9000
|
||||
`;
|
||||
const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"API_ENDPOINT=https://api.example.com/v1/endpoint",
|
||||
"SERVICE_PORT=9000",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles missing project variables gracefully", () => {
|
||||
const projectEnv = `
|
||||
PORT=8080
|
||||
`;
|
||||
const serviceEnv = `
|
||||
MISSING_VAR=\${{project.MISSING_KEY}}
|
||||
SERVICE_PORT=3000
|
||||
`;
|
||||
|
||||
expect(() => prepareEnvironmentVariables(serviceEnv, projectEnv)).toThrow(
|
||||
"Invalid project environment variable: project.MISSING_KEY",
|
||||
);
|
||||
});
|
||||
|
||||
it("overrides project variables with service-specific values", () => {
|
||||
const projectEnv = `
|
||||
ENVIRONMENT=staging
|
||||
DATABASE_URL=postgres://project:project@localhost:5432/project_db
|
||||
`;
|
||||
const serviceEnv = `
|
||||
ENVIRONMENT=\${{project.ENVIRONMENT}}
|
||||
DATABASE_URL=postgres://service:service@localhost:5432/service_db
|
||||
SERVICE_NAME=my-service
|
||||
`;
|
||||
const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"ENVIRONMENT=staging",
|
||||
"DATABASE_URL=postgres://service:service@localhost:5432/service_db",
|
||||
"SERVICE_NAME=my-service",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles project variables with normal and unusual characters", () => {
|
||||
const projectEnv = `
|
||||
ENVIRONMENT=PRODUCTION
|
||||
`;
|
||||
|
||||
// Needs to be in quotes
|
||||
const serviceEnv = `
|
||||
NODE_ENV=\${{project.ENVIRONMENT}}
|
||||
SPECIAL_VAR="$^@$^@#$^@!#$@#$-\${{project.ENVIRONMENT}}"
|
||||
`;
|
||||
|
||||
const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"NODE_ENV=PRODUCTION",
|
||||
"SPECIAL_VAR=$^@$^@#$^@!#$@#$-PRODUCTION",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles complex cases with multiple references, special characters, and spaces", () => {
|
||||
const projectEnv = `
|
||||
ENVIRONMENT=STAGING
|
||||
APP_NAME=MyApp
|
||||
`;
|
||||
|
||||
const serviceEnv = `
|
||||
NODE_ENV=\${{project.ENVIRONMENT}}
|
||||
COMPLEX_VAR="Prefix-$#^!@-\${{project.ENVIRONMENT}}--\${{project.APP_NAME}} Suffix "
|
||||
`;
|
||||
const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"NODE_ENV=STAGING",
|
||||
"COMPLEX_VAR=Prefix-$#^!@-STAGING--MyApp Suffix ",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles references enclosed in single quotes", () => {
|
||||
const projectEnv = `
|
||||
ENVIRONMENT=STAGING
|
||||
APP_NAME=MyApp
|
||||
`;
|
||||
|
||||
const serviceEnv = `
|
||||
NODE_ENV='\${{project.ENVIRONMENT}}'
|
||||
COMPLEX_VAR='Prefix-$#^!@-\${{project.ENVIRONMENT}}--\${{project.APP_NAME}} Suffix'
|
||||
`;
|
||||
const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"NODE_ENV=STAGING",
|
||||
"COMPLEX_VAR=Prefix-$#^!@-STAGING--MyApp Suffix",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles double and single quotes combined", () => {
|
||||
const projectEnv = `
|
||||
ENVIRONMENT=PRODUCTION
|
||||
APP_NAME=MyApp
|
||||
`;
|
||||
const serviceEnv = `
|
||||
NODE_ENV="'\${{project.ENVIRONMENT}}'"
|
||||
COMPLEX_VAR="'Prefix \"DoubleQuoted\" and \${{project.APP_NAME}}'"
|
||||
`;
|
||||
const resolved = prepareEnvironmentVariables(serviceEnv, projectEnv);
|
||||
|
||||
expect(resolved).toEqual([
|
||||
"NODE_ENV='PRODUCTION'",
|
||||
"COMPLEX_VAR='Prefix \"DoubleQuoted\" and MyApp'",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,14 @@ const baseApp: ApplicationNested = {
|
||||
branch: null,
|
||||
dockerBuildStage: "",
|
||||
buildArgs: null,
|
||||
project: {
|
||||
env: "",
|
||||
adminId: "",
|
||||
name: "",
|
||||
description: "",
|
||||
createdAt: "",
|
||||
projectId: "",
|
||||
},
|
||||
buildPath: "/",
|
||||
gitlabPathNamespace: "",
|
||||
buildType: "nixpacks",
|
||||
|
||||
@@ -140,7 +140,7 @@ export const UpdatePort = ({ portId }: Props) => {
|
||||
<FormItem>
|
||||
<FormLabel>Target Port</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="1-65535" {...field} />
|
||||
<NumberInput placeholder="1-65535" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
@@ -61,7 +61,7 @@ const redirectPresets = [
|
||||
redirect: {
|
||||
regex: "^https?://(?:www.)?(.+)",
|
||||
permanent: true,
|
||||
replacement: "https://www.$${1}",
|
||||
replacement: "https://www.${1}",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,7 +70,7 @@ const redirectPresets = [
|
||||
redirect: {
|
||||
regex: "^https?://www.(.+)",
|
||||
permanent: true,
|
||||
replacement: "https://$${1}",
|
||||
replacement: "https://${1}",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -119,7 +119,7 @@ export const UpdateVolume = ({
|
||||
} else if (typeForm === "file") {
|
||||
form.reset({
|
||||
content: data.content || "",
|
||||
mountPath: data.mountPath,
|
||||
mountPath: "/",
|
||||
filePath: data.filePath || "",
|
||||
type: "file",
|
||||
});
|
||||
@@ -296,15 +296,13 @@ export const UpdateVolume = ({
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
form="hook-form-update-volume"
|
||||
type="submit"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
// form="hook-form-update-volume"
|
||||
type="submit"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
162
apps/dokploy/components/dashboard/projects/add-env.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle, FileIcon, SquarePen } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const updateProjectSchema = z.object({
|
||||
env: z.string().optional(),
|
||||
});
|
||||
|
||||
type UpdateProject = z.infer<typeof updateProjectSchema>;
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const AddEnv = ({ projectId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const utils = api.useUtils();
|
||||
const { mutateAsync, error, isError, isLoading } =
|
||||
api.project.update.useMutation();
|
||||
const { data } = api.project.one.useQuery(
|
||||
{
|
||||
projectId,
|
||||
},
|
||||
{
|
||||
enabled: !!projectId,
|
||||
},
|
||||
);
|
||||
|
||||
console.log(data);
|
||||
const form = useForm<UpdateProject>({
|
||||
defaultValues: {
|
||||
env: data?.env ?? "",
|
||||
},
|
||||
resolver: zodResolver(updateProjectSchema),
|
||||
});
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
env: data.env ?? "",
|
||||
});
|
||||
}
|
||||
}, [data, form, form.reset]);
|
||||
|
||||
const onSubmit = async (formData: UpdateProject) => {
|
||||
await mutateAsync({
|
||||
env: formData.env || "",
|
||||
projectId: projectId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Project env updated succesfully");
|
||||
utils.project.all.invalidate();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update the env");
|
||||
})
|
||||
.finally(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<FileIcon className="size-4" />
|
||||
<span>Add Env</span>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-6xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Modify Shared Env</DialogTitle>
|
||||
<DialogDescription>Update the env variables</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
<AlertBlock type="info">
|
||||
To use a shared env, in one of your services, you need to use like
|
||||
this: Let's say you have a shared env ENVIROMENT="development" and you
|
||||
want to use it in your service, you need to use like this:
|
||||
<ul>
|
||||
<li>
|
||||
<code>ENVIRONMENT=${"{{project.ENVIRONMENT}}"}</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>DATABASE_URL=${"{{project.DATABASE_URL}}"}</code>
|
||||
</li>
|
||||
</ul>{" "}
|
||||
This allows the service to inherit and use the shared variables from
|
||||
the project level, ensuring consistency across services.
|
||||
</AlertBlock>
|
||||
<div className="grid gap-4">
|
||||
<div className="grid items-center gap-4">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="grid w-full gap-4 "
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="env"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Enviroment variables</FormLabel>
|
||||
<FormControl>
|
||||
<CodeEditor
|
||||
lineWrapping
|
||||
language="properties"
|
||||
wrapperClassName="h-[35rem] font-mono"
|
||||
placeholder={`NODE_ENV=production
|
||||
PORT=3000
|
||||
|
||||
`}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<pre>
|
||||
<FormMessage />
|
||||
</pre>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button isLoading={isLoading} type="submit">
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
import Link from "next/link";
|
||||
import { Fragment } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { AddEnv } from "./add-env";
|
||||
import { UpdateProject } from "./update";
|
||||
|
||||
export const ShowProjects = () => {
|
||||
@@ -190,7 +191,9 @@ export const ShowProjects = () => {
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
Actions
|
||||
</DropdownMenuLabel>
|
||||
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<AddEnv projectId={project.projectId} />
|
||||
</div>
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<UpdateProject projectId={project.projectId} />
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,15 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import useLocale from "@/utils/hooks/use-locale";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect } from "react";
|
||||
import { toast } from "sonner";
|
||||
@@ -28,6 +37,9 @@ const appearanceFormSchema = z.object({
|
||||
theme: z.enum(["light", "dark", "system"], {
|
||||
required_error: "Please select a theme.",
|
||||
}),
|
||||
language: z.enum(["en", "pl", "zh-Hans"], {
|
||||
required_error: "Please select a language.",
|
||||
}),
|
||||
});
|
||||
|
||||
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||
@@ -35,10 +47,14 @@ type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<AppearanceFormValues> = {
|
||||
theme: "system",
|
||||
language: "en",
|
||||
};
|
||||
|
||||
export function AppearanceForm() {
|
||||
const { setTheme, theme } = useTheme();
|
||||
const { locale, setLocale } = useLocale();
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const form = useForm<AppearanceFormValues>({
|
||||
resolver: zodResolver(appearanceFormSchema),
|
||||
defaultValues,
|
||||
@@ -47,19 +63,23 @@ export function AppearanceForm() {
|
||||
useEffect(() => {
|
||||
form.reset({
|
||||
theme: (theme ?? "system") as AppearanceFormValues["theme"],
|
||||
language: locale,
|
||||
});
|
||||
}, [form, theme]);
|
||||
}, [form, theme, locale]);
|
||||
function onSubmit(data: AppearanceFormValues) {
|
||||
setTheme(data.theme);
|
||||
setLocale(data.language);
|
||||
toast.success("Preferences Updated");
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Appearance</CardTitle>
|
||||
<CardTitle className="text-xl">
|
||||
{t("settings.appearance.title")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Customize the theme of your dashboard.
|
||||
{t("settings.appearance.description")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@@ -72,9 +92,9 @@ export function AppearanceForm() {
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="space-y-1 ">
|
||||
<FormLabel>Theme</FormLabel>
|
||||
<FormLabel>{t("settings.appearance.theme")}</FormLabel>
|
||||
<FormDescription>
|
||||
Select a theme for your dashboard
|
||||
{t("settings.appearance.themeDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<RadioGroup
|
||||
@@ -92,7 +112,7 @@ export function AppearanceForm() {
|
||||
<img src="/images/theme-light.svg" alt="light" />
|
||||
</div>
|
||||
<span className="block w-full p-2 text-center font-normal">
|
||||
Light
|
||||
{t("settings.appearance.themes.light")}
|
||||
</span>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
@@ -105,7 +125,7 @@ export function AppearanceForm() {
|
||||
<img src="/images/theme-dark.svg" alt="dark" />
|
||||
</div>
|
||||
<span className="block w-full p-2 text-center font-normal">
|
||||
Dark
|
||||
{t("settings.appearance.themes.dark")}
|
||||
</span>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
@@ -121,7 +141,7 @@ export function AppearanceForm() {
|
||||
<img src="/images/theme-system.svg" alt="system" />
|
||||
</div>
|
||||
<span className="block w-full p-2 text-center font-normal">
|
||||
System
|
||||
{t("settings.appearance.themes.system")}
|
||||
</span>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
@@ -131,7 +151,44 @@ export function AppearanceForm() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button type="submit">Save</Button>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="language"
|
||||
defaultValue={form.control._defaultValues.language}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="space-y-1">
|
||||
<FormLabel>{t("settings.appearance.language")}</FormLabel>
|
||||
<FormDescription>
|
||||
{t("settings.appearance.languageDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="No preset selected" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "Polski", value: "pl" },
|
||||
{ label: "简体中文", value: "zh-Hans" },
|
||||
].map((preset) => (
|
||||
<SelectItem key={preset.label} value={preset.value}>
|
||||
{preset.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button type="submit">{t("settings.common.save")}</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
|
||||
@@ -53,7 +53,7 @@ export const AddGithubProvider = () => {
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="secondary" className="flex items-center space-x-1">
|
||||
<GithubIcon />
|
||||
<GithubIcon className="text-current fill-current" />
|
||||
<span>Github</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -51,6 +52,7 @@ const randomImages = [
|
||||
export const ProfileForm = () => {
|
||||
const { data, refetch } = api.auth.get.useQuery();
|
||||
const { mutateAsync, isLoading } = api.auth.update.useMutation();
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const form = useForm<Profile>({
|
||||
defaultValues: {
|
||||
@@ -91,10 +93,10 @@ export const ProfileForm = () => {
|
||||
<Card className="bg-transparent">
|
||||
<CardHeader className="flex flex-row gap-2 flex-wrap justify-between items-center">
|
||||
<div>
|
||||
<CardTitle className="text-xl">Account</CardTitle>
|
||||
<CardDescription>
|
||||
Change the details of your profile here.
|
||||
</CardDescription>
|
||||
<CardTitle className="text-xl">
|
||||
{t("settings.profile.title")}
|
||||
</CardTitle>
|
||||
<CardDescription>{t("settings.profile.description")}</CardDescription>
|
||||
</div>
|
||||
{!data?.is2FAEnabled ? <Enable2FA /> : <Disable2FA />}
|
||||
</CardHeader>
|
||||
@@ -107,9 +109,12 @@ export const ProfileForm = () => {
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormLabel>{t("settings.profile.email")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Email" {...field} />
|
||||
<Input
|
||||
placeholder={t("settings.profile.email")}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -120,11 +125,11 @@ export const ProfileForm = () => {
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>{t("settings.profile.password")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
placeholder={t("settings.profile.password")}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
/>
|
||||
@@ -139,7 +144,7 @@ export const ProfileForm = () => {
|
||||
name="image"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Avatar</FormLabel>
|
||||
<FormLabel>{t("settings.profile.avatar")}</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={(e) => {
|
||||
@@ -177,7 +182,7 @@ export const ProfileForm = () => {
|
||||
</div>
|
||||
<div>
|
||||
<Button type="submit" isLoading={isLoading}>
|
||||
Save
|
||||
{t("settings.common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import React from "react";
|
||||
|
||||
import { UpdateServerIp } from "@/components/dashboard/settings/web-server/update-server-ip";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -11,10 +12,13 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { api } from "@/utils/api";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { ShowModalLogs } from "../../web-server/show-modal-logs";
|
||||
import { GPUSupportModal } from "../gpu-support-modal";
|
||||
|
||||
export const ShowDokployActions = () => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { mutateAsync: reloadServer, isLoading } =
|
||||
api.settings.reloadServer.useMutation();
|
||||
|
||||
@@ -22,11 +26,13 @@ export const ShowDokployActions = () => {
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild disabled={isLoading}>
|
||||
<Button isLoading={isLoading} variant="outline">
|
||||
Server
|
||||
{t("settings.server.webServer.server.label")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="start">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{t("settings.server.webServer.actions")}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
@@ -39,12 +45,27 @@ export const ShowDokployActions = () => {
|
||||
toast.success("Server Reloaded");
|
||||
});
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>Reload</span>
|
||||
<span>{t("settings.server.webServer.reload")}</span>
|
||||
</DropdownMenuItem>
|
||||
<ShowModalLogs appName="dokploy">
|
||||
<span>Watch logs</span>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{t("settings.server.webServer.watchLogs")}
|
||||
</DropdownMenuItem>
|
||||
</ShowModalLogs>
|
||||
<GPUSupportModal />
|
||||
<UpdateServerIp>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{t("settings.server.webServer.updateServerIp")}
|
||||
</DropdownMenuItem>
|
||||
</UpdateServerIp>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CardDescription, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -21,13 +20,13 @@ export const ShowServerActions = ({ serverId }: Props) => {
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer "
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
View Actions
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl overflow-y-auto max-h-screen ">
|
||||
<DialogContent className="sm:max-w-xl overflow-y-auto max-h-screen">
|
||||
<div className="flex flex-col gap-1">
|
||||
<DialogTitle className="text-xl">Web server settings</DialogTitle>
|
||||
<DialogDescription>Reload or clean the web server.</DialogDescription>
|
||||
|
||||
@@ -11,12 +11,14 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { api } from "@/utils/api";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
serverId?: string;
|
||||
}
|
||||
export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { mutateAsync: cleanAll, isLoading: cleanAllIsLoading } =
|
||||
api.settings.cleanAll.useMutation();
|
||||
|
||||
@@ -64,11 +66,13 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
}
|
||||
variant="outline"
|
||||
>
|
||||
Space
|
||||
{t("settings.server.webServer.storage.label")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-64" align="start">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{t("settings.server.webServer.actions")}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
@@ -85,7 +89,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>Clean unused images</span>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanUnusedImages")}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
@@ -101,7 +107,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>Clean unused volumes</span>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanUnusedVolumes")}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
@@ -118,7 +126,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>Clean stopped containers</span>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanStoppedContainers")}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
@@ -135,7 +145,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>Clean Docker Builder & System</span>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanDockerBuilder")}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
{!serverId && (
|
||||
<DropdownMenuItem
|
||||
@@ -150,7 +162,9 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>Clean Monitoring </span>
|
||||
<span>
|
||||
{t("settings.server.webServer.storage.cleanMonitoring")}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
@@ -168,7 +182,7 @@ export const ShowStorageActions = ({ serverId }: Props) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>Clean all</span>
|
||||
<span>{t("settings.server.webServer.storage.cleanAll")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { api } from "@/utils/api";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { EditTraefikEnv } from "../../web-server/edit-traefik-env";
|
||||
import { ShowModalLogs } from "../../web-server/show-modal-logs";
|
||||
|
||||
@@ -30,6 +31,7 @@ interface Props {
|
||||
serverId?: string;
|
||||
}
|
||||
export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { mutateAsync: reloadTraefik, isLoading: reloadTraefikIsLoading } =
|
||||
api.settings.reloadTraefik.useMutation();
|
||||
|
||||
@@ -51,11 +53,13 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
isLoading={reloadTraefikIsLoading || toggleDashboardIsLoading}
|
||||
variant="outline"
|
||||
>
|
||||
Traefik
|
||||
{t("settings.server.webServer.traefik.label")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="start">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
{t("settings.server.webServer.actions")}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
@@ -70,18 +74,24 @@ export const ShowTraefikActions = ({ serverId }: Props) => {
|
||||
toast.error("Error to reload the traefik");
|
||||
});
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>Reload</span>
|
||||
<span>{t("settings.server.webServer.reload")}</span>
|
||||
</DropdownMenuItem>
|
||||
<ShowModalLogs appName="dokploy-traefik" serverId={serverId}>
|
||||
<span>Watch logs</span>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{t("settings.server.webServer.watchLogs")}
|
||||
</DropdownMenuItem>
|
||||
</ShowModalLogs>
|
||||
<EditTraefikEnv serverId={serverId}>
|
||||
<DropdownMenuItem
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span>Modify Env</span>
|
||||
<span>{t("settings.server.webServer.traefik.modifyEnv")}</span>
|
||||
</DropdownMenuItem>
|
||||
</EditTraefikEnv>
|
||||
|
||||
|
||||
@@ -23,29 +23,27 @@ export const ToggleDockerCleanup = ({ serverId }: Props) => {
|
||||
const enabled = data?.enableDockerCleanup || server?.enableDockerCleanup;
|
||||
|
||||
const { mutateAsync } = api.settings.updateDockerCleanup.useMutation();
|
||||
|
||||
const handleToggle = async (checked: boolean) => {
|
||||
try {
|
||||
await mutateAsync({
|
||||
enableDockerCleanup: checked,
|
||||
serverId: serverId,
|
||||
});
|
||||
if (serverId) {
|
||||
await refetchServer();
|
||||
} else {
|
||||
await refetch();
|
||||
}
|
||||
toast.success("Docker Cleanup updated");
|
||||
} catch (error) {
|
||||
toast.error("Docker Cleanup Error");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onCheckedChange={async (e) => {
|
||||
await mutateAsync({
|
||||
enableDockerCleanup: e,
|
||||
serverId: serverId,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Docker Cleanup Enabled");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Docker Cleanup Error");
|
||||
});
|
||||
|
||||
if (serverId) {
|
||||
refetchServer();
|
||||
} else {
|
||||
refetch();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Switch checked={!!enabled} onCheckedChange={handleToggle} />
|
||||
<Label className="text-primary">Daily Docker Cleanup</Label>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { useState } from "react";
|
||||
import { GPUSupport } from "./gpu-support";
|
||||
|
||||
export const GPUSupportModal = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
<span>GPU Setup</span>
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-4xl overflow-y-auto max-h-screen">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Dokploy Server GPU Setup
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<GPUSupport serverId="" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,282 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { TRPCClientError } from "@trpc/client";
|
||||
import { CheckCircle2, Cpu, Loader2, RefreshCw, XCircle } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface GPUSupportProps {
|
||||
serverId?: string;
|
||||
}
|
||||
|
||||
export function GPUSupport({ serverId }: GPUSupportProps) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const utils = api.useContext();
|
||||
|
||||
const {
|
||||
data: gpuStatus,
|
||||
isLoading: isChecking,
|
||||
refetch,
|
||||
} = api.settings.checkGPUStatus.useQuery(
|
||||
{ serverId },
|
||||
{
|
||||
enabled: serverId !== undefined,
|
||||
},
|
||||
);
|
||||
|
||||
const setupGPU = api.settings.setupGPU.useMutation({
|
||||
onMutate: () => {
|
||||
setIsLoading(true);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
toast.success("GPU support enabled successfully");
|
||||
setIsLoading(false);
|
||||
await utils.settings.checkGPUStatus.invalidate({ serverId });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(
|
||||
error.message ||
|
||||
"Failed to enable GPU support. Please check server logs.",
|
||||
);
|
||||
setIsLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await utils.settings.checkGPUStatus.invalidate({ serverId });
|
||||
await refetch();
|
||||
} catch (error) {
|
||||
toast.error("Failed to refresh GPU status");
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
handleRefresh();
|
||||
}, []);
|
||||
|
||||
const handleEnableGPU = async () => {
|
||||
if (serverId === undefined) {
|
||||
toast.error("No server selected");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await setupGPU.mutateAsync({ serverId });
|
||||
} catch (error) {
|
||||
// Error handling is done in mutation's onError
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CardContent className="p-0">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Card className="bg-background">
|
||||
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
||||
<div className="flex flex-row gap-2 justify-between w-full items-end max-sm:flex-col">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cpu className="size-5" />
|
||||
<CardTitle className="text-xl">GPU Configuration</CardTitle>
|
||||
</div>
|
||||
<CardDescription>
|
||||
Configure and monitor GPU support
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<DialogAction
|
||||
title="Enable GPU Support?"
|
||||
description="This will enable GPU support for Docker Swarm on this server. Make sure you have the required hardware and drivers installed."
|
||||
onClick={handleEnableGPU}
|
||||
>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading || serverId === undefined || isChecking}
|
||||
>
|
||||
{isLoading
|
||||
? "Enabling GPU..."
|
||||
: gpuStatus?.swarmEnabled
|
||||
? "Reconfigure GPU"
|
||||
: "Enable GPU"}
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={handleRefresh}
|
||||
disabled={isChecking || isRefreshing}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`h-5 w-5 ${isChecking || isRefreshing ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<AlertBlock type="info">
|
||||
<div className="font-medium mb-2">System Requirements:</div>
|
||||
<ul className="list-disc list-inside text-sm space-y-1">
|
||||
<li>NVIDIA GPU hardware must be physically installed</li>
|
||||
<li>
|
||||
NVIDIA drivers must be installed and running (check with
|
||||
nvidia-smi)
|
||||
</li>
|
||||
<li>
|
||||
NVIDIA Container Runtime must be installed
|
||||
(nvidia-container-runtime)
|
||||
</li>
|
||||
<li>User must have sudo/administrative privileges</li>
|
||||
<li>System must support CUDA for GPU acceleration</li>
|
||||
</ul>
|
||||
</AlertBlock>
|
||||
|
||||
{isChecking ? (
|
||||
<div className="flex items-center justify-center text-muted-foreground py-4">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<span>Checking GPU status...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-4">
|
||||
{/* Prerequisites Section */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<h3 className="text-lg font-semibold mb-1">Prerequisites</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Shows all software checks and available hardware
|
||||
</p>
|
||||
<div className="grid gap-2.5">
|
||||
<StatusRow
|
||||
label="NVIDIA Driver"
|
||||
isEnabled={gpuStatus?.driverInstalled}
|
||||
description={
|
||||
gpuStatus?.driverVersion
|
||||
? `Installed (v${gpuStatus.driverVersion})`
|
||||
: "Not Installed"
|
||||
}
|
||||
/>
|
||||
<StatusRow
|
||||
label="GPU Model"
|
||||
value={gpuStatus?.gpuModel || "Not Detected"}
|
||||
showIcon={false}
|
||||
/>
|
||||
<StatusRow
|
||||
label="GPU Memory"
|
||||
value={gpuStatus?.memoryInfo || "Not Available"}
|
||||
showIcon={false}
|
||||
/>
|
||||
<StatusRow
|
||||
label="Available GPUs"
|
||||
value={gpuStatus?.availableGPUs || 0}
|
||||
showIcon={false}
|
||||
/>
|
||||
<StatusRow
|
||||
label="CUDA Support"
|
||||
isEnabled={gpuStatus?.cudaSupport}
|
||||
description={
|
||||
gpuStatus?.cudaVersion
|
||||
? `Available (v${gpuStatus.cudaVersion})`
|
||||
: "Not Available"
|
||||
}
|
||||
/>
|
||||
<StatusRow
|
||||
label="NVIDIA Container Runtime"
|
||||
isEnabled={gpuStatus?.runtimeInstalled}
|
||||
description={
|
||||
gpuStatus?.runtimeInstalled
|
||||
? "Installed"
|
||||
: "Not Installed"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Configuration Status */}
|
||||
<div className="border rounded-lg p-4">
|
||||
<h3 className="text-lg font-semibold mb-1">
|
||||
Docker Swarm GPU Status
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Shows the configuration state that changes with the Enable
|
||||
GPU
|
||||
</p>
|
||||
<div className="grid gap-2.5">
|
||||
<StatusRow
|
||||
label="Runtime Configuration"
|
||||
isEnabled={gpuStatus?.runtimeConfigured}
|
||||
description={
|
||||
gpuStatus?.runtimeConfigured
|
||||
? "Default Runtime"
|
||||
: "Not Default Runtime"
|
||||
}
|
||||
/>
|
||||
<StatusRow
|
||||
label="Swarm GPU Support"
|
||||
isEnabled={gpuStatus?.swarmEnabled}
|
||||
description={
|
||||
gpuStatus?.swarmEnabled
|
||||
? `Enabled (${gpuStatus.gpuResources} GPU${gpuStatus.gpuResources !== 1 ? "s" : ""})`
|
||||
: "Not Enabled"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
||||
|
||||
interface StatusRowProps {
|
||||
label: string;
|
||||
isEnabled?: boolean;
|
||||
description?: string;
|
||||
value?: string | number;
|
||||
showIcon?: boolean;
|
||||
}
|
||||
|
||||
export function StatusRow({
|
||||
label,
|
||||
isEnabled,
|
||||
description,
|
||||
value,
|
||||
showIcon = true,
|
||||
}: StatusRowProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">{label}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{showIcon ? (
|
||||
<>
|
||||
{isEnabled ? (
|
||||
<CheckCircle2 className="size-4 text-green-500" />
|
||||
) : (
|
||||
<XCircle className="size-4 text-red-500" />
|
||||
)}
|
||||
<span
|
||||
className={`text-sm ${isEnabled ? "text-green-500" : "text-red-500"}`}
|
||||
>
|
||||
{description || (isEnabled ? "Installed" : "Not Installed")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">{value}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { ShowDeployment } from "../../application/deployments/show-deployment";
|
||||
import { GPUSupport } from "./gpu-support";
|
||||
|
||||
interface Props {
|
||||
serverId: string;
|
||||
@@ -89,9 +90,10 @@ export const SetupServer = ({ serverId }: Props) => {
|
||||
) : (
|
||||
<div id="hook-form-add-gitlab" className="grid w-full gap-1">
|
||||
<Tabs defaultValue="ssh-keys">
|
||||
<TabsList className="grid grid-cols-2 w-[400px]">
|
||||
<TabsList className="grid grid-cols-3 w-[400px]">
|
||||
<TabsTrigger value="ssh-keys">SSH Keys</TabsTrigger>
|
||||
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
||||
<TabsTrigger value="gpu-setup">GPU Setup</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent
|
||||
value="ssh-keys"
|
||||
@@ -291,6 +293,14 @@ export const SetupServer = ({ serverId }: Props) => {
|
||||
</div>
|
||||
</CardContent>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="gpu-setup"
|
||||
className="outline-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
||||
>
|
||||
<div className="flex flex-col gap-2 text-sm text-muted-foreground pt-3">
|
||||
<GPUSupport serverId={serverId} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -49,6 +50,7 @@ const addServerDomain = z
|
||||
type AddServerDomain = z.infer<typeof addServerDomain>;
|
||||
|
||||
export const WebDomain = () => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { data: user, refetch } = api.admin.one.useQuery();
|
||||
const { mutateAsync, isLoading } =
|
||||
api.settings.assignDomainServer.useMutation();
|
||||
@@ -89,9 +91,11 @@ export const WebDomain = () => {
|
||||
<div className="w-full">
|
||||
<Card className="bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Server Domain</CardTitle>
|
||||
<CardTitle className="text-xl">
|
||||
{t("settings.server.domain.title")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Add a domain to your server application.
|
||||
{t("settings.server.domain.description")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex w-full flex-col gap-4">
|
||||
@@ -106,7 +110,9 @@ export const WebDomain = () => {
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Domain</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.server.domain.form.domain")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-full"
|
||||
@@ -126,7 +132,9 @@ export const WebDomain = () => {
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Letsencrypt Email</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.server.domain.form.letsEncryptEmail")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-full"
|
||||
@@ -145,20 +153,32 @@ export const WebDomain = () => {
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className="md:col-span-2">
|
||||
<FormLabel>Certificate</FormLabel>
|
||||
<FormLabel>
|
||||
{t("settings.server.domain.form.certificate.label")}
|
||||
</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a certificate" />
|
||||
<SelectValue
|
||||
placeholder={t(
|
||||
"settings.server.domain.form.certificate.placeholder",
|
||||
)}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value={"none"}>None</SelectItem>
|
||||
<SelectItem value={"none"}>
|
||||
{t(
|
||||
"settings.server.domain.form.certificateOptions.none",
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value={"letsencrypt"}>
|
||||
Letsencrypt (Default)
|
||||
{t(
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt",
|
||||
)}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -169,7 +189,7 @@ export const WebDomain = () => {
|
||||
/>
|
||||
<div>
|
||||
<Button isLoading={isLoading} type="submit">
|
||||
Save
|
||||
{t("settings.common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import React from "react";
|
||||
import { ShowDokployActions } from "./servers/actions/show-dokploy-actions";
|
||||
import { ShowStorageActions } from "./servers/actions/show-storage-actions";
|
||||
@@ -18,6 +19,7 @@ interface Props {
|
||||
className?: string;
|
||||
}
|
||||
export const WebServer = ({ className }: Props) => {
|
||||
const { t } = useTranslation("settings");
|
||||
const { data } = api.admin.one.useQuery();
|
||||
|
||||
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
|
||||
@@ -25,8 +27,12 @@ export const WebServer = ({ className }: Props) => {
|
||||
return (
|
||||
<Card className={cn("rounded-lg w-full bg-transparent p-0", className)}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl">Web server settings</CardTitle>
|
||||
<CardDescription>Reload or clean the web server.</CardDescription>
|
||||
<CardTitle className="text-xl">
|
||||
{t("settings.server.webServer.title")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t("settings.server.webServer.description")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4 ">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
|
||||
@@ -58,14 +58,7 @@ export const ShowModalLogs = ({ appName, children, serverId }: Props) => {
|
||||
}, [data]);
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-7xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>View Logs</DialogTitle>
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
|
||||
const schema = z.object({
|
||||
serverIp: z.string(),
|
||||
});
|
||||
|
||||
type Schema = z.infer<typeof schema>;
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
serverId?: string;
|
||||
}
|
||||
|
||||
export const UpdateServerIp = ({ children, serverId }: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const { data } = api.admin.one.useQuery();
|
||||
const { data: ip } = api.server.publicIp.useQuery();
|
||||
|
||||
const { mutateAsync, isLoading, error, isError } =
|
||||
api.admin.update.useMutation();
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
serverIp: data?.serverIp || "",
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
form.reset({
|
||||
serverIp: data.serverIp || "",
|
||||
});
|
||||
}
|
||||
}, [form, form.reset, data]);
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const setCurrentIp = () => {
|
||||
if (!ip) return;
|
||||
form.setValue("serverIp", ip);
|
||||
};
|
||||
|
||||
const onSubmit = async (data: Schema) => {
|
||||
await mutateAsync({
|
||||
serverIp: data.serverIp,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Server IP Updated");
|
||||
await utils.admin.one.invalidate();
|
||||
setIsOpen(false);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update the IP of the server");
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update Server IP</DialogTitle>
|
||||
<DialogDescription>Update the IP of the server</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
id="hook-form-update-server-ip"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="serverIp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server IP</FormLabel>
|
||||
<FormControl className="flex gap-2">
|
||||
<div>
|
||||
<Input {...field} />
|
||||
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="secondary"
|
||||
type="button"
|
||||
onClick={setCurrentIp}
|
||||
>
|
||||
<RefreshCw className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
sideOffset={5}
|
||||
className="max-w-[11rem]"
|
||||
>
|
||||
<p>Set current public IP</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</FormControl>
|
||||
<pre>
|
||||
<FormMessage />
|
||||
</pre>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading}
|
||||
form="hook-form-update-server-ip"
|
||||
type="submit"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -87,7 +87,7 @@ export const UpdateServer = () => {
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Check updates
|
||||
Check Updates
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -161,29 +161,27 @@ export const GitlabIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg
|
||||
aria-label="gitlab"
|
||||
height="14"
|
||||
viewBox="0 0 24 22"
|
||||
width="14"
|
||||
className={cn("fill-white text-white", className)}
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={cn("text-white", className)}
|
||||
>
|
||||
<path
|
||||
d="M1.279 8.29L.044 12.294c-.117.367 0 .78.325 1.014l11.323 8.23-.009-.012-.03-.039L1.279 8.29zM22.992 13.308a.905.905 0 00.325-1.014L22.085 8.29 11.693 21.52l11.299-8.212z"
|
||||
fill="currentColor"
|
||||
d="m13.767 5.854-.02-.05L11.842.83a.5.5 0 0 0-.493-.312.5.5 0 0 0-.287.107.5.5 0 0 0-.169.257L9.607 4.819h-5.21L3.11.883A.5.5 0 0 0 2.162.83L.252 5.801l-.018.05a3.54 3.54 0 0 0 1.173 4.09l.007.005.017.012 2.903 2.174L5.77 13.22l.875.66a.59.59 0 0 0 .711 0l.875-.66 1.436-1.087 2.92-2.187.008-.006a3.54 3.54 0 0 0 1.172-4.085"
|
||||
fill="#E24329"
|
||||
/>
|
||||
<path
|
||||
d="M1.279 8.29l10.374 13.197.03.039.01-.006L22.085 8.29H1.28z"
|
||||
fill="currentColor"
|
||||
opacity="0.4"
|
||||
d="m13.767 5.854-.02-.05a6.4 6.4 0 0 0-2.562 1.152L7 10.12l2.666 2.015 2.92-2.187.007-.006a3.54 3.54 0 0 0 1.174-4.088"
|
||||
fill="#FC6D26"
|
||||
/>
|
||||
<path
|
||||
d="M15.982 8.29l-4.299 13.236-.004.011.014-.017L22.085 8.29h-6.103zM7.376 8.29H1.279l10.374 13.197L7.376 8.29z"
|
||||
fill="currentColor"
|
||||
opacity="0.6"
|
||||
d="m4.334 12.135 1.436 1.087.875.66a.59.59 0 0 0 .711 0l.875-.66 1.436-1.087S8.425 11.195 7 10.12c-1.425 1.075-2.666 2.015-2.666 2.015"
|
||||
fill="#FCA326"
|
||||
/>
|
||||
<path
|
||||
d="M18.582.308l-2.6 7.982h6.103L19.48.308c-.133-.41-.764-.41-.897 0zM1.279 8.29L3.88.308c.133-.41.764-.41.897 0l2.6 7.982H1.279z"
|
||||
fill="currentColor"
|
||||
opacity="0.4"
|
||||
d="M2.814 6.956A6.4 6.4 0 0 0 .253 5.8l-.02.05a3.54 3.54 0 0 0 1.174 4.09l.007.005.017.012 2.903 2.174L7 10.117z"
|
||||
fill="#FC6D26"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -200,7 +198,7 @@ export const GithubIcon = ({ className }: Props) => {
|
||||
>
|
||||
<path
|
||||
d="M7 .175c-3.872 0-7 3.128-7 7 0 3.084 2.013 5.71 4.79 6.65.35.066.482-.153.482-.328v-1.181c-1.947.415-2.363-.941-2.363-.941-.328-.81-.787-1.028-.787-1.028-.634-.438.044-.416.044-.416.7.044 1.071.722 1.071.722.635 1.072 1.641.766 2.035.59.066-.459.24-.765.437-.94-1.553-.175-3.193-.787-3.193-3.456 0-.766.262-1.378.721-1.881-.065-.175-.306-.897.066-1.86 0 0 .59-.197 1.925.722a6.754 6.754 0 0 1 1.75-.24c.59 0 1.203.087 1.75.24 1.335-.897 1.925-.722 1.925-.722.372.963.131 1.685.066 1.86.46.48.722 1.115.722 1.88 0 2.691-1.641 3.282-3.194 3.457.24.219.481.634.481 1.29v1.926c0 .197.131.415.481.328C11.988 12.884 14 10.259 14 7.175c0-3.872-3.128-7-7-7z"
|
||||
fill="#fff"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
@@ -209,30 +207,34 @@ export const GithubIcon = ({ className }: Props) => {
|
||||
|
||||
export const BitbucketIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg height="14" viewBox="-2 -2 65 59" width="14" className={className}>
|
||||
<svg
|
||||
width="14"
|
||||
height="13"
|
||||
viewBox="0 0 14 13"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path
|
||||
d="M.454 0a.448.448 0 0 0-.448.52l1.903 11.556a.61.61 0 0 0 .597.509h9.132a.45.45 0 0 0 .448-.377L13.994.525a.448.448 0 0 0-.448-.52zM8.47 8.352H5.555l-.79-4.121h4.411z"
|
||||
fill="#2684FF"
|
||||
/>
|
||||
<path
|
||||
d="M13.384 4.23H9.176L8.47 8.353H5.555L2.113 12.44c.11.095.248.147.393.148h9.134a.45.45 0 0 0 .448-.377z"
|
||||
fill="url(#a)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="bitbucket-:R7aq37rqjt7rrrmpjtuj7l9qjtsr:"
|
||||
x1="104.953%"
|
||||
x2="46.569%"
|
||||
y1="21.921%"
|
||||
y2="75.234%"
|
||||
id="a"
|
||||
x1="14.357"
|
||||
y1="5.383"
|
||||
x2="7.402"
|
||||
y2="10.814"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="7%" stopColor="currentColor" stopOpacity=".4" />
|
||||
<stop offset="100%" stopColor="currentColor" />
|
||||
<stop offset=".18" stop-color="#0052CC" />
|
||||
<stop offset="1" stop-color="#2684FF" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M59.696 18.86h-18.77l-3.15 18.39h-13L9.426 55.47a2.71 2.71 0 001.75.66h40.74a2 2 0 002-1.68l5.78-35.59z"
|
||||
fill="url(#bitbucket-:R7aq37rqjt7rrrmpjtuj7l9qjtsr:)"
|
||||
fillRule="nonzero"
|
||||
transform="translate(-.026 .82)"
|
||||
/>
|
||||
<path
|
||||
d="M2 .82a2 2 0 00-2 2.32l8.49 51.54a2.7 2.7 0 00.91 1.61 2.71 2.71 0 001.75.66l15.76-18.88H24.7l-3.47-18.39h38.44l2.7-16.53a2 2 0 00-2-2.32L2 .82z"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
2
apps/dokploy/drizzle/0043_closed_naoko.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "admin" ADD COLUMN "env" text DEFAULT '' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "project" ADD COLUMN "env" text DEFAULT '' NOT NULL;
|
||||
1
apps/dokploy/drizzle/0044_sour_true_believers.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "admin" DROP COLUMN IF EXISTS "env";
|
||||
3982
apps/dokploy/drizzle/meta/0043_snapshot.json
Normal file
3975
apps/dokploy/drizzle/meta/0044_snapshot.json
Normal file
@@ -302,6 +302,20 @@
|
||||
"when": 1729984439862,
|
||||
"tag": "0042_fancy_havok",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 43,
|
||||
"version": "6",
|
||||
"when": 1731873965888,
|
||||
"tag": "0043_closed_naoko",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 44,
|
||||
"version": "6",
|
||||
"when": 1731875539532,
|
||||
"tag": "0044_sour_true_believers",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
10
apps/dokploy/next-i18next.config.cjs
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('next-i18next').UserConfig} */
|
||||
module.exports = {
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en", "pl", "zh-Hans"],
|
||||
localeDetection: false,
|
||||
},
|
||||
fallbackLng: "en",
|
||||
keySeparator: false,
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.10.6",
|
||||
"version": "v0.12.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
@@ -84,13 +84,16 @@
|
||||
"dotenv": "16.4.5",
|
||||
"drizzle-orm": "^0.30.8",
|
||||
"drizzle-zod": "0.5.1",
|
||||
"i18next": "^23.16.4",
|
||||
"input-otp": "^1.2.4",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-yaml": "4.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"lucia": "^3.0.1",
|
||||
"lucide-react": "^0.312.0",
|
||||
"nanoid": "3",
|
||||
"next": "^15.0.1",
|
||||
"next-i18next": "^15.3.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"node-pty": "1.0.0",
|
||||
"node-schedule": "2.1.1",
|
||||
@@ -100,6 +103,7 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-i18next": "^15.1.0",
|
||||
"recharts": "^2.12.7",
|
||||
"slugify": "^1.6.6",
|
||||
"sonner": "^1.4.0",
|
||||
@@ -119,6 +123,7 @@
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/bcrypt": "5.0.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/lodash": "4.17.4",
|
||||
"@types/node": "^18.17.0",
|
||||
|
||||
@@ -3,6 +3,7 @@ import "@/styles/globals.css";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { api } from "@/utils/api";
|
||||
import type { NextPage } from "next";
|
||||
import { appWithTranslation } from "next-i18next";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import type { AppProps } from "next/app";
|
||||
import { Inter } from "next/font/google";
|
||||
@@ -27,6 +28,7 @@ const MyApp = ({
|
||||
pageProps: { ...pageProps },
|
||||
}: AppPropsWithLayout) => {
|
||||
const getLayout = Component.getLayout ?? ((page) => page);
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx global>{`
|
||||
@@ -59,4 +61,21 @@ const MyApp = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default api.withTRPC(MyApp);
|
||||
export default api.withTRPC(
|
||||
appWithTranslation(
|
||||
MyApp,
|
||||
// keep this in sync with next-i18next.config.js
|
||||
// if you want to know why don't just import the config file, this because next-i18next.config.js must be a CJS, but the rest of the code is ESM.
|
||||
// Add the config here is due to the issue: https://github.com/i18next/next-i18next/issues/2259
|
||||
// if one day every page is translated, we can safely remove this config.
|
||||
{
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en", "pl", "zh-Hans"],
|
||||
localeDetection: false,
|
||||
},
|
||||
fallbackLng: "en",
|
||||
keySeparator: false,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -88,7 +88,6 @@ export default async function handler(
|
||||
.update(admins)
|
||||
.set({
|
||||
stripeSubscriptionId: newSubscription.id,
|
||||
serversQuantity: 0,
|
||||
stripeCustomerId: newSubscription.customer as string,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, newSubscription.customer as string))
|
||||
@@ -121,12 +120,6 @@ export default async function handler(
|
||||
}
|
||||
case "customer.subscription.updated": {
|
||||
const newSubscription = event.data.object as Stripe.Subscription;
|
||||
await db
|
||||
.update(admins)
|
||||
.set({
|
||||
serversQuantity: newSubscription?.items?.data?.[0]?.quantity ?? 0,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, newSubscription.customer as string));
|
||||
|
||||
const admin = await findAdminByStripeCustomerId(
|
||||
newSubscription.customer as string,
|
||||
@@ -136,8 +129,27 @@ export default async function handler(
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
}
|
||||
|
||||
const newServersQuantity = admin.serversQuantity;
|
||||
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
|
||||
if (newSubscription.status === "active") {
|
||||
await db
|
||||
.update(admins)
|
||||
.set({
|
||||
serversQuantity: newSubscription?.items?.data?.[0]?.quantity ?? 0,
|
||||
})
|
||||
.where(
|
||||
eq(admins.stripeCustomerId, newSubscription.customer as string),
|
||||
);
|
||||
|
||||
const newServersQuantity = admin.serversQuantity;
|
||||
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
|
||||
} else {
|
||||
await disableServers(admin.adminId);
|
||||
await db
|
||||
.update(admins)
|
||||
.set({ serversQuantity: 0 })
|
||||
.where(
|
||||
eq(admins.stripeCustomerId, newSubscription.customer as string),
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -148,6 +160,13 @@ export default async function handler(
|
||||
newInvoice.subscription as string,
|
||||
);
|
||||
|
||||
if (suscription.status !== "active") {
|
||||
console.log(
|
||||
`Skipping invoice.payment_succeeded for subscription ${suscription.id} with status ${suscription.status}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
await db
|
||||
.update(admins)
|
||||
.set({
|
||||
@@ -168,22 +187,29 @@ export default async function handler(
|
||||
}
|
||||
case "invoice.payment_failed": {
|
||||
const newInvoice = event.data.object as Stripe.Invoice;
|
||||
await db
|
||||
.update(admins)
|
||||
.set({
|
||||
serversQuantity: 0,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, newInvoice.customer as string));
|
||||
|
||||
const admin = await findAdminByStripeCustomerId(
|
||||
newInvoice.customer as string,
|
||||
const subscription = await stripe.subscriptions.retrieve(
|
||||
newInvoice.subscription as string,
|
||||
);
|
||||
|
||||
if (!admin) {
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
if (subscription.status !== "active") {
|
||||
const admin = await findAdminByStripeCustomerId(
|
||||
newInvoice.customer as string,
|
||||
);
|
||||
|
||||
if (!admin) {
|
||||
return res.status(400).send("Webhook Error: Admin not found");
|
||||
}
|
||||
await db
|
||||
.update(admins)
|
||||
.set({
|
||||
serversQuantity: 0,
|
||||
})
|
||||
.where(eq(admins.stripeCustomerId, newInvoice.customer as string));
|
||||
|
||||
await disableServers(admin.adminId);
|
||||
}
|
||||
|
||||
await disableServers(admin.adminId);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ import { AppearanceForm } from "@/components/dashboard/settings/appearance-form"
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
import React, { type ReactElement } from "react";
|
||||
import superjson from "superjson";
|
||||
import nextI18NextConfig from "../../../next-i18next.config.cjs";
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
@@ -30,6 +32,7 @@ export async function getServerSideProps(
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
const locale = getLocale(req.cookies);
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
router: appRouter,
|
||||
@@ -63,6 +66,7 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { api } from "@/utils/api";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
import { validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
@@ -41,6 +42,7 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = getLocale(req.cookies);
|
||||
const { user, session } = await validateRequest(req, res);
|
||||
|
||||
const helpers = createServerSideHelpers({
|
||||
@@ -75,6 +77,7 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { WebServer } from "@/components/dashboard/settings/web-server";
|
||||
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { getLocale, serverSideTranslations } from "@/utils/i18n";
|
||||
import { IS_CLOUD, validateRequest } from "@dokploy/server";
|
||||
import { createServerSideHelpers } from "@trpc/react-query/server";
|
||||
import type { GetServerSidePropsContext } from "next";
|
||||
@@ -31,6 +32,7 @@ export async function getServerSideProps(
|
||||
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||
) {
|
||||
const { req, res } = ctx;
|
||||
const locale = await getLocale(req.cookies);
|
||||
if (IS_CLOUD) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -73,6 +75,7 @@ export async function getServerSideProps(
|
||||
return {
|
||||
props: {
|
||||
trpcState: helpers.dehydrate(),
|
||||
...(await serverSideTranslations(locale, ["settings"])),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ export default function Home({ IS_CLOUD }: Props) {
|
||||
) : (
|
||||
<Link
|
||||
className="hover:underline text-muted-foreground"
|
||||
href="https://docs.dokploy.com/docs/core/get-started/reset-password"
|
||||
href="https://docs.dokploy.com/docs/core/reset-password"
|
||||
target="_blank"
|
||||
>
|
||||
Lost your password?
|
||||
|
||||
1
apps/dokploy/public/locales/en/common.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
44
apps/dokploy/public/locales/en/settings.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"settings.common.save": "Save",
|
||||
"settings.server.domain.title": "Server Domain",
|
||||
"settings.server.domain.description": "Add a domain to your server application.",
|
||||
"settings.server.domain.form.domain": "Domain",
|
||||
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt Email",
|
||||
"settings.server.domain.form.certificate.label": "Certificate",
|
||||
"settings.server.domain.form.certificate.placeholder": "Select a certificate",
|
||||
"settings.server.domain.form.certificateOptions.none": "None",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Default)",
|
||||
|
||||
"settings.server.webServer.title": "Web Server",
|
||||
"settings.server.webServer.description": "Reload or clean the web server.",
|
||||
"settings.server.webServer.actions": "Actions",
|
||||
"settings.server.webServer.reload": "Reload",
|
||||
"settings.server.webServer.watchLogs": "Watch logs",
|
||||
"settings.server.webServer.updateServerIp": "Update Server IP",
|
||||
"settings.server.webServer.server.label": "Server",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.traefik.modifyEnv": "Modify Env",
|
||||
"settings.server.webServer.storage.label": "Space",
|
||||
"settings.server.webServer.storage.cleanUnusedImages": "Clean unused images",
|
||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Clean unused volumes",
|
||||
"settings.server.webServer.storage.cleanStoppedContainers": "Clean stopped containers",
|
||||
"settings.server.webServer.storage.cleanDockerBuilder": "Clean Docker Builder & System",
|
||||
"settings.server.webServer.storage.cleanMonitoring": "Clean Monitoring",
|
||||
"settings.server.webServer.storage.cleanAll": "Clean all",
|
||||
|
||||
"settings.profile.title": "Account",
|
||||
"settings.profile.description": "Change the details of your profile here.",
|
||||
"settings.profile.email": "Email",
|
||||
"settings.profile.password": "Password",
|
||||
"settings.profile.avatar": "Avatar",
|
||||
|
||||
"settings.appearance.title": "Appearance",
|
||||
"settings.appearance.description": "Customize the theme of your dashboard.",
|
||||
"settings.appearance.theme": "Theme",
|
||||
"settings.appearance.themeDescription": "Select a theme for your dashboard",
|
||||
"settings.appearance.themes.light": "Light",
|
||||
"settings.appearance.themes.dark": "Dark",
|
||||
"settings.appearance.themes.system": "System",
|
||||
"settings.appearance.language": "Language",
|
||||
"settings.appearance.languageDescription": "Select a language for your dashboard"
|
||||
}
|
||||
1
apps/dokploy/public/locales/pl/common.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
44
apps/dokploy/public/locales/pl/settings.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"settings.common.save": "Zapisz",
|
||||
"settings.server.domain.title": "Domena",
|
||||
"settings.server.domain.description": "Dodaj domenę do aplikacji",
|
||||
"settings.server.domain.form.domain": "Domena",
|
||||
"settings.server.domain.form.letsEncryptEmail": "Email Let's Encrypt",
|
||||
"settings.server.domain.form.certificate.label": "Certyfikat",
|
||||
"settings.server.domain.form.certificate.placeholder": "Wybierz certyfikat",
|
||||
"settings.server.domain.form.certificateOptions.none": "Brak",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Domyślny)",
|
||||
|
||||
"settings.server.webServer.title": "Serwer",
|
||||
"settings.server.webServer.description": "Przeładuj lub wyczyść serwer",
|
||||
"settings.server.webServer.actions": "Akcje",
|
||||
"settings.server.webServer.reload": "Przeładuj",
|
||||
"settings.server.webServer.watchLogs": "Obserwuj logi",
|
||||
"settings.server.webServer.updateServerIp": "Zaktualizuj IP serwera",
|
||||
"settings.server.webServer.server.label": "Serwer",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.traefik.modifyEnv": "Zmodyfikuj środowisko",
|
||||
"settings.server.webServer.storage.label": "Przestrzeń",
|
||||
"settings.server.webServer.storage.cleanUnusedImages": "Wyczyść nieużywane obrazy",
|
||||
"settings.server.webServer.storage.cleanUnusedVolumes": "Wyczyść nieużywane wolumeny",
|
||||
"settings.server.webServer.storage.cleanStoppedContainers": "Wyczyść zatrzymane kontenery",
|
||||
"settings.server.webServer.storage.cleanDockerBuilder": "Wyczyść Docker Builder i System",
|
||||
"settings.server.webServer.storage.cleanMonitoring": "Wyczyść monitorowanie",
|
||||
"settings.server.webServer.storage.cleanAll": "Wyczyść wszystko",
|
||||
|
||||
"settings.profile.title": "Konto",
|
||||
"settings.profile.description": "Zmień szczegóły swojego profilu",
|
||||
"settings.profile.email": "Email",
|
||||
"settings.profile.password": "Hasło",
|
||||
"settings.profile.avatar": "Avatar",
|
||||
|
||||
"settings.appearance.title": "Wygląd",
|
||||
"settings.appearance.description": "Dostosuj motyw swojego pulpitu",
|
||||
"settings.appearance.theme": "Motyw",
|
||||
"settings.appearance.themeDescription": "Wybierz motyw swojego pulpitu",
|
||||
"settings.appearance.themes.light": "Jasny",
|
||||
"settings.appearance.themes.dark": "Ciemny",
|
||||
"settings.appearance.themes.system": "System",
|
||||
"settings.appearance.language": "Język",
|
||||
"settings.appearance.languageDescription": "Wybierz język swojego pulpitu"
|
||||
}
|
||||
1
apps/dokploy/public/locales/zh-Hans/common.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
40
apps/dokploy/public/locales/zh-Hans/settings.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"settings.common.save": "保存",
|
||||
"settings.server.domain.title": "服务器域名",
|
||||
"settings.server.domain.description": "添加一个域名到您的服务器。",
|
||||
"settings.server.domain.form.domain": "域名",
|
||||
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 邮箱",
|
||||
"settings.server.domain.form.certificate.label": "证书",
|
||||
"settings.server.domain.form.certificate.placeholder": "选择一个证书",
|
||||
"settings.server.domain.form.certificateOptions.none": "无",
|
||||
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (默认)",
|
||||
"settings.server.webServer.title": "Web 服务器",
|
||||
"settings.server.webServer.description": "管理 Web 服务器。",
|
||||
"settings.server.webServer.actions": "操作",
|
||||
"settings.server.webServer.reload": "重新加载",
|
||||
"settings.server.webServer.watchLogs": "查看日志",
|
||||
"settings.server.webServer.server.label": "服务器",
|
||||
"settings.server.webServer.traefik.label": "Traefik",
|
||||
"settings.server.webServer.traefik.modifyEnv": "修改环境变量",
|
||||
"settings.server.webServer.storage.label": "磁盘空间",
|
||||
"settings.server.webServer.storage.cleanUnusedImages": "清理未使用的镜像",
|
||||
"settings.server.webServer.storage.cleanUnusedVolumes": "清理未使用的卷",
|
||||
"settings.server.webServer.storage.cleanStoppedContainers": "清理停止的容器",
|
||||
"settings.server.webServer.storage.cleanDockerBuilder": "清理 Docker Builder 和系统缓存",
|
||||
"settings.server.webServer.storage.cleanMonitoring": "Clean Monitoring",
|
||||
"settings.server.webServer.storage.cleanAll": "清理所有",
|
||||
"settings.profile.title": "账户偏好",
|
||||
"settings.profile.description": "更改您的个人资料详情。",
|
||||
"settings.profile.email": "电子邮件",
|
||||
"settings.profile.password": "密码",
|
||||
"settings.profile.avatar": "头像",
|
||||
"settings.appearance.title": "外观",
|
||||
"settings.appearance.description": "自定义仪表板主题。",
|
||||
"settings.appearance.theme": "主题",
|
||||
"settings.appearance.themeDescription": "选择仪表板主题",
|
||||
"settings.appearance.themes.light": "亮",
|
||||
"settings.appearance.themes.dark": "暗",
|
||||
"settings.appearance.themes.system": "系统",
|
||||
"settings.appearance.language": "语言",
|
||||
"settings.appearance.languageDescription": "选择仪表板语言"
|
||||
}
|
||||
4
apps/dokploy/public/templates/activepieces.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M 175.034 156.727 C 154.522 121.333 162.546 73.285 192.958 49.41 C 223.367 25.535 264.651 34.874 285.163 70.271 L 423.708 309.332 C 444.22 344.732 436.198 392.78 405.783 416.655 C 375.371 440.532 334.094 431.191 313.579 395.794 L 253.513 292.145 C 245.791 280.823 230.072 282.584 220.633 293.569 C 212.808 302.678 210.245 325.982 208.027 346.159 C 207.703 349.123 207.386 352.011 207.057 354.782 C 205.853 367.988 201.934 381.052 195.111 392.832 C 172.809 431.313 127.916 441.458 94.849 415.502 C 61.788 389.543 53.051 337.299 75.353 298.811 C 86.917 278.851 104.563 266.513 123.48 262.884 L 123.455 262.852 C 178.116 253.627 188.248 181.826 178.247 162.266 L 175.034 156.727 Z" fill="#8142E3" style=""/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 824 B |
153
apps/dokploy/public/templates/blender.svg
Normal file
@@ -0,0 +1,153 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="11.567343mm"
|
||||
height="15.032981mm"
|
||||
viewBox="0 0 11.567343 15.03298"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="community_logo_black.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#c8c8c8"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
showgrid="false"
|
||||
showguides="true"
|
||||
borderlayer="true"
|
||||
fit-margin-top="1"
|
||||
fit-margin-left="1"
|
||||
fit-margin-right="1"
|
||||
fit-margin-bottom="1"/>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-115.93625,-150.07138)">
|
||||
<g
|
||||
transform="translate(-3.8788837,214.53487)"
|
||||
id="g1369">
|
||||
<path
|
||||
style="opacity:1;fill:#000000;fill-opacity:0.07058824;stroke:none;stroke-width:0.31555739;stroke-miterlimit:1.41420996;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
|
||||
d="m 121.59341,-62.933898 c -0.43151,0 -0.77882,0.347312 -0.77882,0.778817 v 7.918777 c 0,0.04214 0.004,0.08316 0.0106,0.12345 7.5e-4,0.0053 10e-4,0.01041 0.002,0.01567 0.001,0.0073 0.002,0.01466 0.004,0.02186 0.10284,0.693169 0.73757,1.119278 2.19888,2.190555 2.64127,1.936306 2.45943,1.935512 5.11716,0.02186 1.68877,-1.215962 2.28048,-1.590346 2.23197,-2.501308 v -7.790874 c 0,-0.431505 -0.34751,-0.778817 -0.77902,-0.778817 z"
|
||||
id="path1373"/>
|
||||
<path
|
||||
id="path1323"
|
||||
d="m 121.59341,-63.463065 c -0.43151,0 -0.77882,0.347312 -0.77882,0.778817 v 7.918777 c 0,0.04214 0.004,0.08316 0.0106,0.12345 7.5e-4,0.0053 10e-4,0.01041 0.002,0.01567 0.001,0.0073 0.002,0.01466 0.004,0.02186 0.10284,0.693169 0.73757,1.119278 2.19888,2.190555 2.64127,1.936306 2.45943,1.935512 5.11716,0.02186 1.68877,-1.215962 2.28048,-1.590346 2.23197,-2.501308 v -7.790874 c 0,-0.431505 -0.34751,-0.778817 -0.77902,-0.778817 z"
|
||||
style="opacity:1;fill:#363636;fill-opacity:1;stroke:none;stroke-width:0.31555739;stroke-miterlimit:1.41420996;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill" />
|
||||
<g
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996"
|
||||
id="g1353"
|
||||
transform="matrix(0.02054188,0,0,0.02054188,97.15326,-61.563495)">
|
||||
<g
|
||||
id="g1327"
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996">
|
||||
<path
|
||||
id="path1325"
|
||||
style="fill:#d8d8d8;fill-opacity:1;fill-rule:nonzero"
|
||||
d="m 364.467,-333.746 c 0.171,-1.908 1.646,-3.118 3.899,-3.118 2.256,0 3.73,1.21 3.901,3.118 z m 7.569,4.711 c -0.577,1.414 -1.937,2.251 -3.784,2.251 -2.313,0 -3.87,-1.444 -3.933,-3.725 h 13.297 c 0,-0.237 0,-0.435 0,-0.671 0,-5.714 -3.354,-8.925 -9.364,-8.925 -5.836,0 -9.365,3.241 -9.365,8.324 0,5.114 3.584,8.35 9.365,8.35 3.469,0 6.159,-1.189 7.817,-3.279 z"/>
|
||||
</g>
|
||||
<g
|
||||
id="g1331"
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996">
|
||||
<path
|
||||
id="path1329"
|
||||
style="fill:#d8d8d8;fill-opacity:1;fill-rule:nonzero"
|
||||
d="m 305.468,-333.737 c 0.176,-1.908 1.651,-3.118 3.906,-3.118 2.252,0 3.726,1.21 3.899,3.118 z m 7.574,4.711 c -0.578,1.418 -1.937,2.255 -3.788,2.255 -2.309,0 -3.87,-1.448 -3.931,-3.73 h 13.294 c 0,-0.234 0,-0.431 0,-0.667 0,-5.717 -3.353,-8.929 -9.363,-8.929 -5.839,0 -9.361,3.242 -9.361,8.325 0,5.114 3.582,8.35 9.361,8.35 3.468,0 6.16,-1.185 7.821,-3.278 z"/>
|
||||
</g>
|
||||
<g
|
||||
id="g1335"
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996">
|
||||
<rect
|
||||
id="rect1333"
|
||||
style="fill:#d8d8d8;fill-opacity:1;fill-rule:nonzero"
|
||||
height="19.617001"
|
||||
width="4.7950001"
|
||||
y="-343.56"
|
||||
x="293.90701" />
|
||||
</g>
|
||||
<g
|
||||
id="g1339"
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996">
|
||||
<path
|
||||
id="path1337"
|
||||
style="fill:#d8d8d8;fill-opacity:1;fill-rule:nonzero"
|
||||
d="m 319.81,-338.348 h 4.822 v 1.168 c 1.707,-1.822 3.757,-2.743 6.069,-2.743 2.663,0 4.679,0.921 5.72,2.489 0.869,1.295 0.926,2.858 0.926,4.912 v 8.579 h -4.829 v -7.538 c 0,-3.128 -0.629,-4.572 -3.375,-4.572 -2.775,0 -4.511,1.653 -4.511,4.428 v 7.682 h -4.822 z"/>
|
||||
</g>
|
||||
<g
|
||||
id="g1343"
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996">
|
||||
<path
|
||||
id="path1341"
|
||||
style="fill:#d8d8d8;fill-opacity:1;fill-rule:nonzero"
|
||||
d="m 352.876,-331.538 c 0,2.685 -1.794,4.446 -4.57,4.446 -2.778,0 -4.572,-1.701 -4.572,-4.415 0,-2.754 1.77,-4.454 4.572,-4.454 2.776,0 4.57,1.73 4.57,4.423 z m 0,-6.157 c -1.219,-1.307 -2.983,-2.024 -5.435,-2.024 -5.29,0 -8.902,3.262 -8.902,8.151 0,4.793 3.587,8.146 8.815,8.146 2.397,0 4.157,-0.606 5.522,-1.965 v 1.444 h 4.825 v -20.861 l -4.825,1.244 z"/>
|
||||
</g>
|
||||
<g
|
||||
id="g1347"
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996">
|
||||
<path
|
||||
id="path1345"
|
||||
style="fill:#d8d8d8;fill-opacity:1;fill-rule:nonzero"
|
||||
d="m 282.947,-335.961 c 2.804,0 4.567,1.7 4.567,4.454 0,2.714 -1.791,4.415 -4.567,4.415 -2.774,0 -4.566,-1.761 -4.566,-4.446 0,-2.693 1.792,-4.423 4.566,-4.423 z m -4.566,-7.599 -4.827,-1.244 v 20.861 h 4.827 v -1.444 c 1.358,1.359 3.121,1.965 5.52,1.965 5.231,0 8.813,-3.353 8.813,-8.146 0,-4.889 -3.613,-8.151 -8.9,-8.151 -2.457,0 -4.22,0.717 -5.433,2.024 z"/>
|
||||
</g>
|
||||
<g
|
||||
id="g1351"
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
style="clip-rule:evenodd;fill:#d8d8d8;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996">
|
||||
<path
|
||||
id="path1349"
|
||||
style="fill:#d8d8d8;fill-opacity:1;fill-rule:nonzero"
|
||||
d="m 378.806,-323.943 v -14.405 h 4.825 v 0.89 c 1.445,-1.74 2.974,-2.606 4.713,-2.606 0.345,0 0.779,0.056 1.356,0.113 v 4.107 c -0.465,-0.061 -0.983,-0.061 -1.533,-0.061 -2.805,0 -4.536,1.85 -4.536,4.996 v 6.966 z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.04039667,0,0,0.04039667,81.604348,-55.892386)"
|
||||
style="clip-rule:evenodd;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996"
|
||||
id="g1367">
|
||||
<g
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
id="g1361"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
d="m 243.13,-333.715 c 0.106,-1.891 1.032,-3.557 2.429,-4.738 1.37,-1.16 3.214,-1.869 5.226,-1.869 2.01,0 3.854,0.709 5.225,1.869 1.396,1.181 2.322,2.847 2.429,4.736 0.106,1.943 -0.675,3.748 -2.045,5.086 -1.397,1.361 -3.384,2.215 -5.609,2.215 -2.225,0 -4.216,-0.854 -5.612,-2.215 -1.371,-1.338 -2.15,-3.143 -2.043,-5.084 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero"
|
||||
id="path1359" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(3.3451117,0,0,3.3451075,277.7359,1100.2048)"
|
||||
id="g1365"
|
||||
style="fill:#ffffff;fill-opacity:1">
|
||||
<path
|
||||
d="m 230.94,-329.894 c 0.013,0.74 0.249,2.178 0.603,3.301 0.744,2.377 2.006,4.576 3.762,6.514 1.802,1.992 4.021,3.592 6.584,4.728 2.694,1.193 5.613,1.801 8.645,1.796 3.027,-0.004 5.946,-0.624 8.64,-1.826 2.563,-1.147 4.78,-2.754 6.579,-4.747 1.755,-1.946 3.015,-4.149 3.761,-6.526 0.375,-1.201 0.612,-2.42 0.707,-3.643 0.093,-1.205 0.054,-2.412 -0.117,-3.618 -0.334,-2.35 -1.147,-4.555 -2.399,-6.565 -1.145,-1.847 -2.621,-3.464 -4.376,-4.825 l 0.004,-0.003 -17.711,-13.599 c -0.016,-0.012 -0.029,-0.025 -0.046,-0.036 -1.162,-0.892 -3.116,-0.889 -4.394,0.005 -1.292,0.904 -1.44,2.399 -0.29,3.342 l -0.005,0.005 7.387,6.007 -22.515,0.024 c -0.011,0 -0.022,0 -0.03,0 -1.861,0.002 -3.65,1.223 -4.004,2.766 -0.364,1.572 0.9,2.876 2.835,2.883 l -0.003,0.007 11.412,-0.022 -20.364,15.631 c -0.026,0.019 -0.054,0.039 -0.078,0.058 -1.921,1.471 -2.542,3.917 -1.332,5.465 1.228,1.574 3.839,1.577 5.78,0.009 l 11.114,-9.096 c 0,0 -0.162,1.228 -0.149,1.965 z m 28.559,4.112 c -2.29,2.333 -5.496,3.656 -8.965,3.663 -3.474,0.006 -6.68,-1.305 -8.97,-3.634 -1.119,-1.135 -1.941,-2.441 -2.448,-3.832 -0.497,-1.367 -0.69,-2.818 -0.562,-4.282 0.121,-1.431 0.547,-2.796 1.227,-4.031 0.668,-1.214 1.588,-2.311 2.724,-3.239 2.226,-1.814 5.06,-2.796 8.024,-2.8 2.967,-0.004 5.799,0.969 8.027,2.777 1.134,0.924 2.053,2.017 2.721,3.229 0.683,1.234 1.106,2.594 1.232,4.029 0.126,1.462 -0.067,2.911 -0.564,4.279 -0.508,1.395 -1.327,2.701 -2.446,3.841 z"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero"
|
||||
id="path1363" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
1
apps/dokploy/public/templates/coder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 341 75" fill="#fff" xmlns="http://www.w3.org/2000/svg"><path d="M176.753 32.758h-11.199q-.306-2.173-1.253-3.86a9.8 9.8 0 0 0-2.429-2.915q-1.482-1.202-3.426-1.841-1.917-.64-4.167-.64-4.066 0-7.083 2.02-3.017 1.995-4.679 5.83-1.662 3.81-1.662 9.256 0 5.6 1.662 9.409 1.687 3.81 4.705 5.753t6.98 1.943q2.224 0 4.116-.588 1.918-.588 3.401-1.713a9.9 9.9 0 0 0 2.454-2.787q.997-1.636 1.381-3.733l11.199.051q-.435 3.605-2.173 6.955-1.713 3.323-4.628 5.957-2.889 2.609-6.904 4.142-3.988 1.508-9.025 1.508-7.006 0-12.529-3.17-5.496-3.17-8.693-9.179-3.17-6.009-3.17-14.548 0-8.565 3.221-14.574t8.745-9.153q5.523-3.17 12.426-3.17 4.55 0 8.437 1.277 3.912 1.28 6.929 3.733 3.018 2.43 4.909 5.958 1.918 3.528 2.455 8.08m25.279 34.798q-5.958 0-10.304-2.532-4.321-2.556-6.674-7.108-2.352-4.576-2.352-10.61 0-6.086 2.352-10.637 2.353-4.575 6.674-7.108 4.346-2.556 10.304-2.556 5.956 0 10.278 2.556 4.347 2.532 6.699 7.108 2.352 4.551 2.352 10.637 0 6.034-2.352 10.61-2.352 4.552-6.699 7.108-4.322 2.532-10.278 2.532m.051-8.438q2.71 0 4.525-1.534 1.817-1.56 2.736-4.244.946-2.685.946-6.111t-.946-6.11q-.92-2.685-2.736-4.245-1.815-1.56-4.525-1.56-2.736 0-4.602 1.56-1.842 1.56-2.787 4.244-.921 2.685-.921 6.111t.921 6.11q.945 2.685 2.787 4.245 1.866 1.534 4.602 1.534m40.632 8.31q-4.474 0-8.105-2.301-3.605-2.327-5.727-6.827-2.097-4.526-2.097-11.097 0-6.75 2.174-11.224 2.173-4.5 5.778-6.724 3.63-2.25 7.952-2.25 3.298 0 5.497 1.125 2.224 1.099 3.579 2.76 1.381 1.638 2.097 3.223h.332V14.426h10.867V66.79h-10.739V60.5h-.46q-.767 1.636-2.173 3.247-1.381 1.586-3.605 2.633-2.199 1.05-5.37 1.049m3.452-8.668q2.634 0 4.449-1.432 1.841-1.456 2.812-4.065.997-2.609.997-6.11 0-3.503-.971-6.086-.972-2.583-2.813-3.989-1.84-1.406-4.474-1.406-2.684 0-4.526 1.457-1.84 1.458-2.787 4.04-.946 2.583-.946 5.983 0 3.426.946 6.06.972 2.608 2.787 4.09 1.841 1.458 4.526 1.458m45.548 8.796q-6.06 0-10.432-2.455-4.347-2.48-6.699-7.006-2.352-4.551-2.352-10.764 0-6.06 2.352-10.636 2.352-4.577 6.622-7.134 4.296-2.556 10.074-2.556 3.887 0 7.236 1.252 3.375 1.228 5.881 3.708 2.531 2.48 3.937 6.238 1.406 3.733 1.406 8.745v2.991h-33.162v-6.75h22.91q0-2.352-1.023-4.167a7.33 7.33 0 0 0-2.838-2.839q-1.79-1.047-4.168-1.048-2.48 0-4.398 1.15a8.07 8.07 0 0 0-2.966 3.043q-1.073 1.893-1.099 4.22v6.417q0 2.914 1.074 5.037 1.1 2.121 3.094 3.272t4.73 1.151q1.815 0 3.324-.511 1.508-.512 2.582-1.535t1.636-2.505l10.074.665q-.766 3.63-3.145 6.34-2.352 2.685-6.085 4.194-3.707 1.483-8.565 1.483m24.933-.767V27.517h10.56v6.852h.409q1.074-3.655 3.605-5.523 2.532-1.891 5.83-1.892.817 0 1.764.103.946.102 1.662.28v9.666q-.767-.23-2.122-.41-1.356-.178-2.48-.178-2.404 0-4.296 1.048a7.7 7.7 0 0 0-2.966 2.864q-1.074 1.84-1.074 4.244V66.79zM102.464 32.547c-2.093 0-3.487-1.227-3.487-3.745V14.336C98.977 5.102 95.172 0 85.344 0H80.78v9.751h1.395c3.868 0 5.706 2.131 5.706 5.941V28.48c0 5.553 1.649 7.814 5.263 8.976-3.614 1.098-5.263 3.423-5.263 8.976v9.493c0 2.648 0 5.231-.697 7.879-.697 2.454-1.839 4.779-3.424 6.78-.888 1.163-1.902 2.132-3.043 3.036v1.291h4.565c9.828 0 13.632-5.102 13.632-14.336V46.108c0-2.583 1.332-3.745 3.488-3.745H105v-9.751h-2.536zM71.395 14.726H57.319a.574.574 0 0 1-.571-.582v-1.097c0-.323.254-.582.57-.582h14.14c.317 0 .57.259.57.582v1.098c0 .322-.316.58-.633.58m2.409 13.949H63.533a.574.574 0 0 1-.571-.581v-1.098c0-.323.254-.582.57-.582h10.272c.317 0 .571.259.571.582v1.098c0 .258-.254.58-.57.58m4.057-6.973H57.319a.574.574 0 0 1-.571-.581V20.02c0-.323.254-.581.57-.581h20.48c.318 0 .571.258.571.58v1.099c0 .258-.19.58-.507.58m-36.838-3.81c1.394 0 2.79.13 4.12.452v-2.648c0-3.745 1.903-5.94 5.707-5.94h1.395V0h-4.565c-9.828 0-13.632 5.102-13.632 14.336v4.779c2.219-.775 4.565-1.227 6.974-1.227"/><path d="M82.174 53.017c-1.015-8.202-7.228-15.047-15.218-16.597-2.219-.452-4.438-.516-6.594-.129-.063 0-.063-.065-.127-.065C56.749 28.8 49.267 23.892 41.15 23.892s-15.534 4.779-19.085 12.205c-.063 0-.063.065-.127.065a18.9 18.9 0 0 0-6.847.452C7.228 38.551 1.268 45.267.19 53.404A17 17 0 0 0 0 55.858c0 2.454 1.649 4.714 4.058 5.037a4.78 4.78 0 0 0 5.516-4.843c0-.452 0-.969.064-1.42.507-4.134 3.614-7.621 7.672-8.59a9.2 9.2 0 0 1 3.74-.193c3.869.516 7.673-1.486 9.321-4.973 1.205-2.583 3.107-4.843 5.643-6.07 2.79-1.356 5.96-1.55 8.877-.517 3.044 1.098 5.326 3.423 6.721 6.329 1.459 2.841 2.156 4.843 5.263 5.23 1.268.194 4.819.13 6.15.065 2.6 0 5.2.904 7.038 2.777a9.9 9.9 0 0 1 2.473 4.714c.57 2.906-.127 5.812-1.839 8.008-1.204 1.55-2.853 2.712-4.692 3.229-.887.258-1.775.322-2.663.322H49.33c-2.79 0-5.01-2.26-5.01-5.101V40.94c0-.774-.633-1.42-1.394-1.42H40.96c-3.868.064-6.974 4.456-6.974 9.105V65.61c0 5.037 3.994 9.106 8.94 9.106l22.319-.065c5.072-.517 9.764-3.164 12.934-7.233 3.17-3.939 4.629-9.105 3.995-14.4"/></svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
BIN
apps/dokploy/public/templates/discord-tickets.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
apps/dokploy/public/templates/discordtickets.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
apps/dokploy/public/templates/docmost.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
39
apps/dokploy/public/templates/hi-events.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
apps/dokploy/public/templates/infisical.jpg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
apps/dokploy/public/templates/influxdb.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
apps/dokploy/public/templates/invoiceshelf.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
apps/dokploy/public/templates/macos.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
1
apps/dokploy/public/templates/nextcloud-aio.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="256" height="128" version="1.1" viewBox="0 0 256 128" xmlns="http://www.w3.org/2000/svg"><path d="m128 7c-25.871 0-47.817 17.485-54.713 41.209-5.9795-12.461-18.642-21.209-33.287-21.209-20.304 0-37 16.696-37 37s16.696 37 37 37c14.645 0 27.308-8.7481 33.287-21.209 6.8957 23.724 28.842 41.209 54.713 41.209s47.817-17.485 54.713-41.209c5.9795 12.461 18.642 21.209 33.287 21.209 20.304 0 37-16.696 37-37s-16.696-37-37-37c-14.645 0-27.308 8.7481-33.287 21.209-6.8957-23.724-28.842-41.209-54.713-41.209zm0 22c19.46 0 35 15.54 35 35s-15.54 35-35 35-35-15.54-35-35 15.54-35 35-35zm-88 20c8.4146 0 15 6.5854 15 15s-6.5854 15-15 15-15-6.5854-15-15 6.5854-15 15-15zm176 0c8.4146 0 15 6.5854 15 15s-6.5854 15-15 15-15-6.5854-15-15 6.5854-15 15-15z" color="#000000" fill="#fff" style="-inkscape-stroke:none"/></svg>
|
||||
|
After Width: | Height: | Size: 814 B |
13
apps/dokploy/public/templates/peppermint.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
apps/dokploy/public/templates/postiz.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
apps/dokploy/public/templates/slash.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
19
apps/dokploy/public/templates/vaultwarden.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="276" height="280" viewBox="0 0 276 280" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#a)">
|
||||
<mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-21" width="322" height="322">
|
||||
<path d="M-18.7-18.7h317.883v317.883H-18.699z" fill="#fff" stroke="#fff" stroke-width="2.671"/>
|
||||
<path d="M146.92 33.39a6.678 6.678 0 1 1-13.357 0 6.678 6.678 0 0 1 13.357 0Zm97.007 80.184a6.678 6.678 0 1 1-4.127-12.702 6.678 6.678 0 1 1 4.127 12.702Zm-46.282 117.038a6.678 6.678 0 1 1 10.805-7.851 6.678 6.678 0 1 1-10.805 7.851Zm-125.612-7.851a6.678 6.678 0 1 1 10.806 7.851 6.678 6.678 0 0 1-10.806-7.851ZM40.684 100.872a6.678 6.678 0 1 1-4.127 12.702 6.678 6.678 0 1 1 4.127-12.702Z" fill="#000" stroke="#fff" stroke-width="2.671"/>
|
||||
</mask>
|
||||
<g mask="url(#b)" stroke="#fff">
|
||||
<path d="m56.974 49.917 70.778 198.18h24.981l70.778-198.18H197.42l-44.687 127.124q-4.441 12.213-7.495 23.038-3.052 10.546-4.996 20.262-1.942-9.715-4.996-20.54-3.052-10.825-7.494-23.315L83.342 49.917z" fill="#fff" stroke-width="12.052"/>
|
||||
<path d="M140.242 255.107c-63.438 0-114.865-51.427-114.865-114.865S76.804 25.377 140.242 25.377c63.439 0 114.865 51.427 114.865 114.865s-51.426 114.865-114.865 114.865Z" stroke-width="24.042"/>
|
||||
<path d="M17.363 132.228v16.028l-13.356-8.014zm.798 24.127 3.127 15.719-14.663-5.254zm5.489 23.507 6.133 14.808-15.407-2.293zm9.97 21.985 8.905 13.326-15.558.757zm14.067 19.617 11.333 11.333-15.111 3.778zM65.31 237.96l13.327 8.904-14.084 6.654zm20.505 12.741 14.807 6.133-12.515 9.273zm22.595 8.496 15.72 3.126-10.466 11.537zm23.819 3.924h16.027l-8.014 13.357zm24.125-.798 15.72-3.126-5.254 14.663zm23.508-5.489 14.808-6.133-2.293 15.406zm21.985-9.969 13.326-8.905.757 15.558zm19.617-14.068 11.333-11.333 3.778 15.111zm16.496-17.624 8.904-13.326 6.654 14.083zm12.741-20.503 6.134-14.808 9.273 12.515zm8.495-22.596 3.127-15.719 11.536 10.465zm3.925-23.818v-16.028l13.357 8.014zm-.798-24.127-3.127-15.719 14.664 5.254zm-5.489-23.507L250.7 85.814l15.407 2.293zm-9.97-21.985-8.904-13.326 15.557-.757zM232.797 59.02l-11.333-11.333 15.111-3.778zm-17.624-16.496-13.327-8.904 14.084-6.654zM194.67 29.783l-14.808-6.133 12.515-9.273zm-22.596-8.495-15.72-3.127L166.82 6.624zm-23.818-3.925h-16.028l8.014-13.356zm-24.126.797-15.72 3.128 5.254-14.664zm-23.508 5.49-14.808 6.133 2.293-15.406zm-21.984 9.97L65.31 42.524l-.757-15.558zM59.02 47.687 47.688 59.02 43.91 43.91zM42.524 65.31 33.62 78.638l-6.653-14.083zM29.783 85.814l-6.133 14.808-9.273-12.515zm-8.496 22.596-3.127 15.719-11.536-10.465z" fill="#fff" stroke-width="8.014" stroke-linejoin="round"/>
|
||||
<path d="M121.543 28.048h37.398l-18.699 18.7zm-93.781 95.308 11.556-35.568 12.006 23.562zM89.425 242l-30.256-21.982 26.119-4.137zm131.891-21.982L191.061 242l4.136-26.119zm19.851-132.23 11.557 35.568-23.562-12.006z" fill="#fff" stroke-width="16.028" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h276v280H0z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
29
apps/dokploy/public/templates/windmill.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{opacity:0.4;fill:#FFFFFF;}
|
||||
.st2{fill:#BCD4FC;}
|
||||
.st3{fill:#3B82F6;}
|
||||
.st4{fill:#B3B3B3;}
|
||||
.st5{fill:url(#SVGID_1_);}
|
||||
.st6{fill:url(#SVGID_00000021089067129159788970000008246765442136188072_);}
|
||||
.st7{fill:url(#SVGID_00000117639240116366130650000015074833605515028638_);}
|
||||
.st8{opacity:0.4;fill:url(#SVGID_00000101781798616409025840000016567063639337360777_);}
|
||||
.st9{opacity:0.4;fill:url(#SVGID_00000052086836598721292040000002033117744178971046_);}
|
||||
.st10{opacity:0.4;fill:url(#SVGID_00000159460939004760751800000002448009281983951536_);}
|
||||
.st11{opacity:0.4;fill:url(#SVGID_00000013177830667419993080000017721442101626521532_);}
|
||||
.st12{opacity:0.4;fill:url(#SVGID_00000152235521444854938490000006526001119318383285_);}
|
||||
.st13{opacity:0.4;fill:url(#SVGID_00000119823135212293698520000012774889010992664993_);}
|
||||
</style>
|
||||
<g>
|
||||
<polygon class="st2" points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22 "/>
|
||||
<polygon class="st3" points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02 "/>
|
||||
<polygon class="st3" points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46
|
||||
"/>
|
||||
<polygon class="st3" points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22 "/>
|
||||
<polygon class="st2" points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21 "/>
|
||||
<polygon class="st2" points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
BIN
apps/dokploy/public/templates/windows.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
@@ -4,6 +4,7 @@ import {
|
||||
apiCreateUserInvitation,
|
||||
apiFindOneToken,
|
||||
apiRemoveUser,
|
||||
apiUpdateAdmin,
|
||||
users,
|
||||
} from "@/server/db/schema";
|
||||
import {
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
findUserById,
|
||||
getUserByToken,
|
||||
removeUserByAuthId,
|
||||
updateAdmin,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
@@ -26,6 +28,18 @@ export const adminRouter = createTRPCRouter({
|
||||
...rest,
|
||||
};
|
||||
}),
|
||||
update: adminProcedure
|
||||
.input(apiUpdateAdmin)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
if (ctx.user.rol === "user") {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not allowed to update this admin",
|
||||
});
|
||||
}
|
||||
const { authId } = await findAdminById(ctx.user.adminId);
|
||||
return updateAdmin(authId, input);
|
||||
}),
|
||||
createUserInvitation: adminProcedure
|
||||
.input(apiCreateUserInvitation)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -132,16 +132,20 @@ export const projectRouter = createTRPCRouter({
|
||||
if (accesedProjects.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = await db.query.projects.findMany({
|
||||
where: sql`${projects.projectId} IN (${sql.join(
|
||||
accesedProjects.map((projectId) => sql`${projectId}`),
|
||||
sql`, `,
|
||||
)})`,
|
||||
where: and(
|
||||
sql`${projects.projectId} IN (${sql.join(
|
||||
accesedProjects.map((projectId) => sql`${projectId}`),
|
||||
sql`, `,
|
||||
)})`,
|
||||
eq(projects.adminId, ctx.user.adminId),
|
||||
),
|
||||
with: {
|
||||
applications: {
|
||||
where: and(
|
||||
buildServiceFilter(applications.applicationId, accesedServices),
|
||||
eq(projects.adminId, ctx.user.adminId),
|
||||
where: buildServiceFilter(
|
||||
applications.applicationId,
|
||||
accesedServices,
|
||||
),
|
||||
with: { domains: true },
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
findAdminById,
|
||||
findServerById,
|
||||
findServersByAdminId,
|
||||
getPublicIpWithFallback,
|
||||
haveActiveServices,
|
||||
removeDeploymentsByServerId,
|
||||
serverSetup,
|
||||
@@ -181,4 +182,11 @@ export const serverRouter = createTRPCRouter({
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
publicIp: protectedProcedure.query(async ({ ctx }) => {
|
||||
if (IS_CLOUD) {
|
||||
return "";
|
||||
}
|
||||
const ip = await getPublicIpWithFallback();
|
||||
return ip;
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
writeMainConfig,
|
||||
writeTraefikConfigInPath,
|
||||
} from "@dokploy/server";
|
||||
import { checkGPUStatus, setupGPUSupport } from "@dokploy/server";
|
||||
import { generateOpenApiDocument } from "@dokploy/trpc-openapi";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { sql } from "drizzle-orm";
|
||||
@@ -242,7 +243,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
await cleanUpUnusedImages(server.serverId);
|
||||
await cleanUpDockerBuilder(server.serverId);
|
||||
await cleanUpSystemPrune(server.serverId);
|
||||
await sendDockerCleanupNotifications();
|
||||
await sendDockerCleanupNotifications(server.adminId);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -278,7 +279,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
await cleanUpUnusedImages();
|
||||
await cleanUpDockerBuilder();
|
||||
await cleanUpSystemPrune();
|
||||
await sendDockerCleanupNotifications();
|
||||
await sendDockerCleanupNotifications(admin.adminId);
|
||||
});
|
||||
} else {
|
||||
const currentJob = scheduledJobs["docker-cleanup"];
|
||||
@@ -657,6 +658,54 @@ export const settingsRouter = createTRPCRouter({
|
||||
}
|
||||
return { status: "not_cloud" };
|
||||
}),
|
||||
setupGPU: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
throw new Error("Select a server to enable the GPU Setup");
|
||||
}
|
||||
|
||||
try {
|
||||
await setupGPUSupport(input.serverId);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("GPU Setup Error:", error);
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
checkGPUStatus: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
serverId: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
if (IS_CLOUD && !input.serverId) {
|
||||
return {
|
||||
driverInstalled: false,
|
||||
driverVersion: undefined,
|
||||
gpuModel: undefined,
|
||||
runtimeInstalled: false,
|
||||
runtimeConfigured: false,
|
||||
cudaSupport: undefined,
|
||||
cudaVersion: undefined,
|
||||
memoryInfo: undefined,
|
||||
availableGPUs: 0,
|
||||
swarmEnabled: false,
|
||||
gpuResources: 0,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return await checkGPUStatus(input.serverId || "");
|
||||
} catch (error) {
|
||||
throw new Error("Failed to check GPU status");
|
||||
}
|
||||
}),
|
||||
});
|
||||
// {
|
||||
// "Parallelism": 1,
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
import type { Session, User } from "lucia";
|
||||
import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
/**
|
||||
* 1. CONTEXT
|
||||
*
|
||||
|
||||
@@ -63,7 +63,6 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
}
|
||||
stream
|
||||
.on("close", () => {
|
||||
console.log("Connection closed ✅ Container Logs");
|
||||
client.end();
|
||||
ws.close();
|
||||
})
|
||||
@@ -88,7 +87,6 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
});
|
||||
ws.on("close", () => {
|
||||
console.log("Connection closed ✅, From Container Logs WS");
|
||||
client.end();
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -113,7 +113,6 @@ export const setupTerminalWebSocketServer = (
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("Connection closed ✅");
|
||||
stream.end();
|
||||
});
|
||||
},
|
||||
|
||||
67
apps/dokploy/templates/activepieces/docker-compose.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
activepieces:
|
||||
image: activepieces/activepieces:0.35.0
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
AP_ENGINE_EXECUTABLE_PATH: dist/packages/engine/main.js
|
||||
AP_API_KEY: ${AP_API_KEY}
|
||||
AP_ENCRYPTION_KEY: ${AP_ENCRYPTION_KEY}
|
||||
AP_JWT_SECRET: ${AP_JWT_SECRET}
|
||||
AP_ENVIRONMENT: prod
|
||||
AP_FRONTEND_URL: https://${AP_HOST}
|
||||
AP_WEBHOOK_TIMEOUT_SECONDS: 30
|
||||
AP_TRIGGER_DEFAULT_POLL_INTERVAL: 5
|
||||
AP_POSTGRES_DATABASE: activepieces
|
||||
AP_POSTGRES_HOST: postgres
|
||||
AP_POSTGRES_PORT: 5432
|
||||
AP_POSTGRES_USERNAME: activepieces
|
||||
AP_POSTGRES_PASSWORD: ${AP_POSTGRES_PASSWORD}
|
||||
AP_EXECUTION_MODE: UNSANDBOXED
|
||||
AP_REDIS_HOST: redis
|
||||
AP_REDIS_PORT: 6379
|
||||
AP_SANDBOX_RUN_TIME_SECONDS: 600
|
||||
AP_TELEMETRY_ENABLED: "false"
|
||||
AP_TEMPLATES_SOURCE_URL: https://cloud.activepieces.com/api/v1/flow-templates
|
||||
|
||||
postgres:
|
||||
image: postgres:14
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
environment:
|
||||
POSTGRES_DB: activepieces
|
||||
POSTGRES_PASSWORD: ${AP_POSTGRES_PASSWORD}
|
||||
POSTGRES_USER: activepieces
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U activepieces -d activepieces"]
|
||||
interval: 30s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
44
apps/dokploy/templates/activepieces/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
|
||||
const apiKey = Array.from({ length: 32 }, () =>
|
||||
Math.floor(Math.random() * 16).toString(16),
|
||||
).join("");
|
||||
const encryptionKey = Array.from({ length: 32 }, () =>
|
||||
Math.floor(Math.random() * 16).toString(16),
|
||||
).join("");
|
||||
const jwtSecret = Array.from({ length: 32 }, () =>
|
||||
Math.floor(Math.random() * 16).toString(16),
|
||||
).join("");
|
||||
const postgresPassword = Array.from({ length: 32 }, () =>
|
||||
Math.floor(Math.random() * 16).toString(16),
|
||||
).join("");
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 80,
|
||||
serviceName: "activepieces",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`AP_HOST=${mainDomain}`,
|
||||
`AP_API_KEY=${apiKey}`,
|
||||
`AP_ENCRYPTION_KEY=${encryptionKey}`,
|
||||
`AP_JWT_SECRET=${jwtSecret}`,
|
||||
`AP_POSTGRES_PASSWORD=${postgresPassword}`,
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
26
apps/dokploy/templates/blender/docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
blender:
|
||||
image: lscr.io/linuxserver/blender:latest
|
||||
runtime: nvidia
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities:
|
||||
- gpu
|
||||
environment:
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
- NVIDIA_DRIVER_CAPABILITIES=all
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Etc/UTC
|
||||
- SUBFOLDER=/ #optional
|
||||
ports:
|
||||
- 3000
|
||||
- 3001
|
||||
restart: unless-stopped
|
||||
shm_size: 1gb
|
||||
34
apps/dokploy/templates/blender/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateHash,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainServiceHash = generateHash(schema.projectName);
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 3000,
|
||||
serviceName: "blender",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
"PUID=1000",
|
||||
"PGID=1000",
|
||||
"TZ=Etc/UTC",
|
||||
"SUBFOLDER=/",
|
||||
"NVIDIA_VISIBLE_DEVICES=all",
|
||||
"NVIDIA_DRIVER_CAPABILITIES=all",
|
||||
];
|
||||
|
||||
return {
|
||||
envs,
|
||||
domains,
|
||||
};
|
||||
}
|
||||
39
apps/dokploy/templates/coder/docker-compose.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
coder:
|
||||
image: ghcr.io/coder/coder:v2.15.3
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
group_add:
|
||||
- "998"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- CODER_ACCESS_URL
|
||||
- CODER_HTTP_ADDRESS
|
||||
- CODER_PG_CONNECTION_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db/${POSTGRES_DB}?sslmode=disable
|
||||
|
||||
db:
|
||||
image: postgres:17
|
||||
networks:
|
||||
- dokploy-network
|
||||
environment:
|
||||
- POSTGRES_PASSWORD
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_DB
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}",
|
||||
]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
- db_coder_data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
db_coder_data:
|
||||
30
apps/dokploy/templates/coder/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 7080,
|
||||
serviceName: "coder",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
"CODER_ACCESS_URL=",
|
||||
"CODER_HTTP_ADDRESS=0.0.0.0:7080",
|
||||
"",
|
||||
"POSTGRES_DB=coder",
|
||||
"POSTGRES_USER=coder",
|
||||
"POSTGRES_PASSWORD=VERY_STRONG_PASSWORD",
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
54
apps/dokploy/templates/discord-tickets/docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8
|
||||
restart: unless-stopped
|
||||
hostname: mysql
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- tickets-mysql:/var/lib/mysql
|
||||
environment:
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
MYSQL_USER: ${MYSQL_USER}
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u${MYSQL_USER}", "-p${MYSQL_PASSWORD}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
bot:
|
||||
image: eartharoid/discord-tickets:4.0.21
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
hostname: bot
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- tickets-bot:/home/container/user
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
tty: true
|
||||
stdin_open: true
|
||||
environment:
|
||||
DB_CONNECTION_URL: mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql/${MYSQL_DATABASE}
|
||||
DISCORD_SECRET: ${DISCORD_SECRET}
|
||||
DISCORD_TOKEN: ${DISCORD_TOKEN}
|
||||
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||
DB_PROVIDER: mysql
|
||||
HTTP_EXTERNAL: https://${TICKETS_HOST}
|
||||
HTTP_HOST: 0.0.0.0
|
||||
HTTP_PORT: 8169
|
||||
HTTP_TRUST_PROXY: "true"
|
||||
PUBLIC_BOT: "false"
|
||||
PUBLISH_COMMANDS: "true"
|
||||
SUPER: ${SUPER_USERS}
|
||||
|
||||
volumes:
|
||||
tickets-mysql:
|
||||
tickets-bot:
|
||||
47
apps/dokploy/templates/discord-tickets/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const mysqlPassword = generatePassword();
|
||||
const mysqlRootPassword = generatePassword();
|
||||
const mysqlUser = "tickets";
|
||||
const mysqlDatabase = "tickets";
|
||||
|
||||
// Generate encryption key in the format they use
|
||||
const encryptionKey = Array.from({ length: 48 }, () =>
|
||||
Math.floor(Math.random() * 16).toString(16),
|
||||
).join("");
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 8169,
|
||||
serviceName: "bot",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`TICKETS_HOST=${mainDomain}`,
|
||||
`MYSQL_DATABASE=${mysqlDatabase}`,
|
||||
`MYSQL_PASSWORD=${mysqlPassword}`,
|
||||
`MYSQL_ROOT_PASSWORD=${mysqlRootPassword}`,
|
||||
`MYSQL_USER=${mysqlUser}`,
|
||||
`ENCRYPTION_KEY=${encryptionKey}`,
|
||||
// These need to be set by the user through the UI
|
||||
"# Follow the guide at: https://discordtickets.app/self-hosting/installation/docker/#creating-the-discord-application",
|
||||
"DISCORD_SECRET=",
|
||||
"DISCORD_TOKEN=",
|
||||
"SUPER_USERS=YOUR_DISCORD_USER_ID", // Default super user
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
47
apps/dokploy/templates/docmost/docker-compose.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
docmost:
|
||||
image: docmost/docmost:0.4.1
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
environment:
|
||||
- APP_URL
|
||||
- APP_SECRET
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
|
||||
- REDIS_URL=redis://redis:6379
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- docmost:/app/data/storage
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
- POSTGRES_DB
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_PASSWORD
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- db_docmost_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7.2-alpine
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- redis_docmost_data:/data
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
docmost:
|
||||
db_docmost_data:
|
||||
redis_docmost_data:
|
||||
29
apps/dokploy/templates/docmost/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 3000,
|
||||
serviceName: "docmost",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
"POSTGRES_DB=docmost",
|
||||
"POSTGRES_USER=docmost",
|
||||
"POSTGRES_PASSWORD=STRONG_DB_PASSWORD",
|
||||
"APP_URL=http://localhost:3000",
|
||||
"APP_SECRET=VERY_STRONG_SECRET",
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:1.22.2
|
||||
image: gitea/gitea:1.22.3
|
||||
environment:
|
||||
- USER_UID=${USER_UID}
|
||||
- USER_GID=${USER_GID}
|
||||
@@ -21,7 +21,7 @@ services:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: postgres:16
|
||||
image: postgres:17
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=gitea
|
||||
|
||||
45
apps/dokploy/templates/hi-events/docker-compose.yml
Normal file
@@ -0,0 +1,45 @@
|
||||
services:
|
||||
all-in-one:
|
||||
image: daveearley/hi.events-all-in-one:v0.8.0-beta.1
|
||||
restart: always
|
||||
environment:
|
||||
- VITE_FRONTEND_URL=https://${DOMAIN}
|
||||
- APP_FRONTEND_URL=https://${DOMAIN}
|
||||
- VITE_API_URL_CLIENT=https://${DOMAIN}/api
|
||||
- VITE_API_URL_SERVER=http://localhost:80/api
|
||||
- VITE_STRIPE_PUBLISHABLE_KEY
|
||||
- LOG_CHANNEL=stderr
|
||||
- QUEUE_CONNECTION=sync
|
||||
- MAIL_MAILER=array
|
||||
- APP_KEY
|
||||
- JWT_SECRET
|
||||
- FILESYSTEM_PUBLIC_DISK=public
|
||||
- FILESYSTEM_PRIVATE_DISK=local
|
||||
- APP_CDN_URL=https://${DOMAIN}/storage
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
||||
- MAIL_MAILER
|
||||
- MAIL_HOST
|
||||
- MAIL_PORT
|
||||
- MAIL_FROM_ADDRESS
|
||||
- MAIL_FROM_NAME
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
postgres:
|
||||
image: elestio/postgres:16
|
||||
restart: always
|
||||
networks:
|
||||
- dokploy-network
|
||||
environment:
|
||||
- POSTGRES_DB
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_PASSWORD
|
||||
volumes:
|
||||
- pg_hi-events_data:/var/lib/postgresql/data
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
pg_hi-events_data:
|
||||
41
apps/dokploy/templates/hi-events/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 80,
|
||||
serviceName: "all-in-one",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
"# change domain here",
|
||||
"DOMAIN=my-events.com",
|
||||
"",
|
||||
"POSTGRES_DB=hievents",
|
||||
"POSTGRES_USER=hievents",
|
||||
"POSTGRES_PASSWORD=VERY_STRONG_PASSWORD",
|
||||
"",
|
||||
"VITE_STRIPE_PUBLISHABLE_KEY=",
|
||||
"",
|
||||
"APP_KEY=my-app-key",
|
||||
"JWT_SECRET=STRONG_JWT_SECRET",
|
||||
"",
|
||||
"MAIL_MAILER=",
|
||||
"MAIL_HOST=",
|
||||
"MAIL_PORT=",
|
||||
"MAIL_FROM_ADDRESS=",
|
||||
"MAIL_FROM_NAME=",
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
87
apps/dokploy/templates/infisical/docker-compose.yml
Normal file
@@ -0,0 +1,87 @@
|
||||
services:
|
||||
db-migration:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
image: infisical/infisical:v0.90.1-postgres
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ENCRYPTION_KEY
|
||||
- AUTH_SECRET
|
||||
- SITE_URL
|
||||
- DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- SMTP_HOST
|
||||
- SMTP_PORT
|
||||
- SMTP_FROM_NAME
|
||||
- SMTP_USERNAME
|
||||
- SMTP_PASSWORD
|
||||
- SMTP_SECURE=true
|
||||
command: npm run migration:latest
|
||||
pull_policy: always
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
backend:
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
db-migration:
|
||||
condition: service_completed_successfully
|
||||
image: infisical/infisical:v0.90.1-postgres
|
||||
pull_policy: always
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- ENCRYPTION_KEY
|
||||
- AUTH_SECRET
|
||||
- SITE_URL
|
||||
- DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- SMTP_HOST
|
||||
- SMTP_PORT
|
||||
- SMTP_FROM_NAME
|
||||
- SMTP_USERNAME
|
||||
- SMTP_PASSWORD
|
||||
- SMTP_SECURE=true
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||
redis:
|
||||
image: redis:7.4.1
|
||||
env_file: .env
|
||||
restart: always
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- redis_infisical_data:/data
|
||||
|
||||
db:
|
||||
image: postgres:14-alpine
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_PASSWORD
|
||||
- POSTGRES_USER
|
||||
- POSTGRES_DB
|
||||
volumes:
|
||||
- pg_infisical_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- dokploy-network
|
||||
healthcheck:
|
||||
test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
pg_infisical_data:
|
||||
redis_infisical_data:
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
|
||||
93
apps/dokploy/templates/infisical/index.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 8080,
|
||||
serviceName: "backend",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
"# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION",
|
||||
"ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218",
|
||||
"",
|
||||
"# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION",
|
||||
"AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=",
|
||||
"# Postgres creds",
|
||||
"POSTGRES_PASSWORD=infisical",
|
||||
"POSTGRES_USER=infisical",
|
||||
"POSTGRES_DB=infisical",
|
||||
"",
|
||||
"# Website URL",
|
||||
"# Required",
|
||||
"SITE_URL=http://localhost:8080",
|
||||
"",
|
||||
"# Mail/SMTP",
|
||||
"SMTP_HOST=",
|
||||
"SMTP_PORT=",
|
||||
"SMTP_NAME=",
|
||||
"SMTP_USERNAME=",
|
||||
"SMTP_PASSWORD=",
|
||||
"",
|
||||
"# Integration",
|
||||
"# Optional only if integration is used",
|
||||
"CLIENT_ID_HEROKU=",
|
||||
"CLIENT_ID_VERCEL=",
|
||||
"CLIENT_ID_NETLIFY=",
|
||||
"CLIENT_ID_GITHUB=",
|
||||
"CLIENT_ID_GITHUB_APP=",
|
||||
"CLIENT_SLUG_GITHUB_APP=",
|
||||
"CLIENT_ID_GITLAB=",
|
||||
"CLIENT_ID_BITBUCKET=",
|
||||
"CLIENT_SECRET_HEROKU=",
|
||||
"CLIENT_SECRET_VERCEL=",
|
||||
"CLIENT_SECRET_NETLIFY=",
|
||||
"CLIENT_SECRET_GITHUB=",
|
||||
"CLIENT_SECRET_GITHUB_APP=",
|
||||
"CLIENT_SECRET_GITLAB=",
|
||||
"CLIENT_SECRET_BITBUCKET=",
|
||||
"CLIENT_SLUG_VERCEL=",
|
||||
"",
|
||||
"CLIENT_PRIVATE_KEY_GITHUB_APP=",
|
||||
"CLIENT_APP_ID_GITHUB_APP=",
|
||||
"",
|
||||
"# Sentry (optional) for monitoring errors",
|
||||
"SENTRY_DSN=",
|
||||
"",
|
||||
"# Infisical Cloud-specific configs",
|
||||
"# Ignore - Not applicable for self-hosted version",
|
||||
"POSTHOG_HOST=",
|
||||
"POSTHOG_PROJECT_API_KEY=",
|
||||
"",
|
||||
"# SSO-specific variables",
|
||||
"CLIENT_ID_GOOGLE_LOGIN=",
|
||||
"CLIENT_SECRET_GOOGLE_LOGIN=",
|
||||
"",
|
||||
"CLIENT_ID_GITHUB_LOGIN=",
|
||||
"CLIENT_SECRET_GITHUB_LOGIN=",
|
||||
"",
|
||||
"CLIENT_ID_GITLAB_LOGIN=",
|
||||
"CLIENT_SECRET_GITLAB_LOGIN=",
|
||||
"",
|
||||
"CAPTCHA_SECRET=",
|
||||
"",
|
||||
"NEXT_PUBLIC_CAPTCHA_SITE_KEY=",
|
||||
"",
|
||||
"PLAIN_API_KEY=",
|
||||
"PLAIN_WISH_LABEL_IDS=",
|
||||
"",
|
||||
"SSL_CLIENT_CERTIFICATE_HEADER_KEY=",
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
11
apps/dokploy/templates/influxdb/docker-compose.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
services:
|
||||
influxdb:
|
||||
image: influxdb:2.7.10
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- influxdb2-data:/var/lib/influxdb2
|
||||
- influxdb2-config:/etc/influxdb2
|
||||
|
||||
volumes:
|
||||
influxdb2-data:
|
||||
influxdb2-config:
|
||||
19
apps/dokploy/templates/influxdb/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 8086,
|
||||
serviceName: "influxdb",
|
||||
},
|
||||
];
|
||||
return {
|
||||
domains,
|
||||
};
|
||||
}
|
||||
55
apps/dokploy/templates/invoiceshelf/docker-compose.yml
Normal file
@@ -0,0 +1,55 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
invoiceshelf_db:
|
||||
image: postgres:15
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
- POSTGRES_USER=${DB_USERNAME}
|
||||
- POSTGRES_DB=${DB_DATABASE}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
invoiceshelf:
|
||||
image: invoiceshelf/invoiceshelf:latest
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- app_data:/data
|
||||
- app_conf:/conf
|
||||
environment:
|
||||
- PHP_TZ=UTC
|
||||
- TIMEZONE=UTC
|
||||
- APP_NAME=InvoiceShelf
|
||||
- APP_ENV=production
|
||||
- APP_DEBUG=false
|
||||
- APP_URL=http://${INVOICESHELF_HOST}
|
||||
- DB_CONNECTION=pgsql
|
||||
- DB_HOST=invoiceshelf_db
|
||||
- DB_PORT=5432
|
||||
- DB_DATABASE=${DB_DATABASE}
|
||||
- DB_USERNAME=${DB_USERNAME}
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- CACHE_STORE=file
|
||||
- SESSION_DRIVER=file
|
||||
- SESSION_LIFETIME=120
|
||||
- SESSION_ENCRYPT=true
|
||||
- SESSION_PATH=/
|
||||
- SESSION_DOMAIN=${INVOICESHELF_HOST}
|
||||
- SANCTUM_STATEFUL_DOMAINS=${INVOICESHELF_HOST}
|
||||
- STARTUP_DELAY=10
|
||||
depends_on:
|
||||
invoiceshelf_db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
app_data:
|
||||
app_conf:
|
||||
34
apps/dokploy/templates/invoiceshelf/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const mainDomain = generateRandomDomain(schema);
|
||||
const dbPassword = generatePassword();
|
||||
const dbUsername = "invoiceshelf";
|
||||
const dbDatabase = "invoiceshelf";
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: mainDomain,
|
||||
port: 80,
|
||||
serviceName: "invoiceshelf",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
`INVOICESHELF_HOST=${mainDomain}`,
|
||||
`DB_PASSWORD=${dbPassword}`,
|
||||
`DB_USERNAME=${dbUsername}`,
|
||||
`DB_DATABASE=${dbDatabase}`,
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
16
apps/dokploy/templates/macos/docker-compose.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
services:
|
||||
macos:
|
||||
image: dockurr/macos:1.14
|
||||
volumes:
|
||||
- macos-storage:/storage
|
||||
environment:
|
||||
- VERSION
|
||||
devices:
|
||||
# If in .env string 'KVM=N' is not commented, you need to comment line below
|
||||
- /dev/kvm
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
stop_grace_period: 2m
|
||||
|
||||
volumes:
|
||||
macos-storage:
|
||||
33
apps/dokploy/templates/macos/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: generateRandomDomain(schema),
|
||||
port: 8006,
|
||||
serviceName: "macos",
|
||||
},
|
||||
];
|
||||
|
||||
const envs = [
|
||||
"# https://github.com/dockur/macos?tab=readme-ov-file#how-do-i-select-the-macos-version",
|
||||
"VERSION=15",
|
||||
"",
|
||||
"# Uncomment this if your PC/VM or etc does not support virtualization technology",
|
||||
"# KVM=N",
|
||||
"",
|
||||
"DISK_SIZE=64G",
|
||||
"RAM_SIZE=4G",
|
||||
"CPU_CORES=2",
|
||||
];
|
||||
|
||||
return {
|
||||
domains,
|
||||
envs,
|
||||
};
|
||||
}
|
||||
38
apps/dokploy/templates/nextcloud-aio/docker-compose.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
services:
|
||||
nextcloud:
|
||||
image: nextcloud:30.0.2
|
||||
restart: always
|
||||
networks:
|
||||
- dokploy-network
|
||||
ports:
|
||||
- 80
|
||||
volumes:
|
||||
- nextcloud_data:/var/www/html
|
||||
environment:
|
||||
- NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN}
|
||||
- MYSQL_HOST=nextcloud_db
|
||||
- MYSQL_DATABASE=nextcloud
|
||||
- MYSQL_USER=nextcloud
|
||||
- MYSQL_PASSWORD=${MYSQL_SECRET_PASSWORD}
|
||||
- OVERWRITEPROTOCOL=https
|
||||
|
||||
nextcloud_db:
|
||||
image: mariadb
|
||||
restart: always
|
||||
networks:
|
||||
- dokploy-network
|
||||
volumes:
|
||||
- nextcloud_db_data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_SECRET_PASSWORD_ROOT}
|
||||
- MYSQL_DATABASE=nextcloud
|
||||
- MYSQL_USER=nextcloud
|
||||
- MYSQL_PASSWORD=${MYSQL_SECRET_PASSWORD}
|
||||
|
||||
volumes:
|
||||
nextcloud_data:
|
||||
nextcloud_db_data:
|
||||
|
||||
networks:
|
||||
dokploy-network:
|
||||
external: true
|
||||
28
apps/dokploy/templates/nextcloud-aio/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
type DomainSchema,
|
||||
type Schema,
|
||||
type Template,
|
||||
generatePassword,
|
||||
generateRandomDomain,
|
||||
} from "../utils";
|
||||
|
||||
export function generate(schema: Schema): Template {
|
||||
const randomDomain = generateRandomDomain(schema);
|
||||
const databasePassword = generatePassword();
|
||||
const databaseRootPassword = generatePassword();
|
||||
const envs = [
|
||||
`NEXTCLOUD_DOMAIN=${randomDomain}`,
|
||||
`MYSQL_SECRET_PASSWORD=${databasePassword}`,
|
||||
`MYSQL_SECRET_PASSWORD_ROOT=${databaseRootPassword}`,
|
||||
];
|
||||
|
||||
const domains: DomainSchema[] = [
|
||||
{
|
||||
host: randomDomain,
|
||||
port: 80,
|
||||
serviceName: "nextcloud",
|
||||
},
|
||||
];
|
||||
|
||||
return { envs, domains };
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
nocodb:
|
||||
image: nocodb/nocodb:0.251.1
|
||||
image: nocodb/nocodb:0.257.2
|
||||
restart: always
|
||||
environment:
|
||||
NC_DB: "pg://root_db?u=postgres&p=password&d=root_db"
|
||||
@@ -11,7 +11,7 @@ services:
|
||||
- nc_data:/usr/app/data
|
||||
|
||||
root_db:
|
||||
image: postgres:14.7
|
||||
image: postgres:17
|
||||
restart: always
|
||||
networks:
|
||||
- dokploy-network
|
||||
|
||||