mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
30 Commits
v0.20.1
...
1481-traef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbe7d5bdc5 | ||
|
|
6f7a5609a3 | ||
|
|
c3a5e2a8d6 | ||
|
|
1ca965268e | ||
|
|
e323ade29e | ||
|
|
8c916bc431 | ||
|
|
0670f9b910 | ||
|
|
44f002d8d0 | ||
|
|
27f6c945e0 | ||
|
|
e61c216ea0 | ||
|
|
9f9492af79 | ||
|
|
68f608bdc9 | ||
|
|
8f671d1691 | ||
|
|
7afbe8b208 | ||
|
|
8c05214e78 | ||
|
|
2ace36f035 | ||
|
|
b7196a3494 | ||
|
|
3b737ca55b | ||
|
|
581e590f65 | ||
|
|
d66a5d55a3 | ||
|
|
47db6831b4 | ||
|
|
56cbd1abb3 | ||
|
|
cb40ac5c6b | ||
|
|
7218b3f79b | ||
|
|
19ea4d3fcd | ||
|
|
6edfd1e547 | ||
|
|
666a8ede97 | ||
|
|
08e4b8fe33 | ||
|
|
5fc265d14f | ||
|
|
c3887af5d1 |
@@ -165,86 +165,8 @@ Thank you for your contribution!
|
|||||||
|
|
||||||
## Templates
|
## Templates
|
||||||
|
|
||||||
To add a new template, go to `templates` folder and create a new folder with the name of the template.
|
To add a new template, go to `https://github.com/Dokploy/templates` repository and read the README.md file.
|
||||||
|
|
||||||
Let's take the example of `plausible` template.
|
|
||||||
|
|
||||||
1. create a folder in `templates/plausible`
|
|
||||||
2. create a `docker-compose.yml` file inside the folder with the content of compose.
|
|
||||||
3. create a `index.ts` file inside the folder with the following code as base:
|
|
||||||
4. When creating a pull request, please provide a video of the template working in action.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// EXAMPLE
|
|
||||||
import {
|
|
||||||
generateBase64,
|
|
||||||
generateHash,
|
|
||||||
generateRandomDomain,
|
|
||||||
type Template,
|
|
||||||
type Schema,
|
|
||||||
type DomainSchema,
|
|
||||||
} from "../utils";
|
|
||||||
|
|
||||||
export function generate(schema: Schema): Template {
|
|
||||||
// do your stuff here, like create a new domain, generate random passwords, mounts.
|
|
||||||
const mainServiceHash = generateHash(schema.projectName);
|
|
||||||
const mainDomain = generateRandomDomain(schema);
|
|
||||||
const secretBase = generateBase64(64);
|
|
||||||
const toptKeyBase = generateBase64(32);
|
|
||||||
|
|
||||||
const domains: DomainSchema[] = [
|
|
||||||
{
|
|
||||||
host: mainDomain,
|
|
||||||
port: 8000,
|
|
||||||
serviceName: "plausible",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const envs = [
|
|
||||||
`BASE_URL=http://${mainDomain}`,
|
|
||||||
`SECRET_KEY_BASE=${secretBase}`,
|
|
||||||
`TOTP_VAULT_KEY=${toptKeyBase}`,
|
|
||||||
`HASH=${mainServiceHash}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const mounts: Template["mounts"] = [
|
|
||||||
{
|
|
||||||
filePath: "./clickhouse/clickhouse-config.xml",
|
|
||||||
content: "some content......",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
envs,
|
|
||||||
mounts,
|
|
||||||
domains,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Now you need to add the information about the template to the `templates/templates.ts` is a object with the following properties:
|
|
||||||
|
|
||||||
**Make sure the id of the template is the same as the folder name and don't have any spaces, only slugified names and lowercase.**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
id: "plausible",
|
|
||||||
name: "Plausible",
|
|
||||||
version: "v2.1.0",
|
|
||||||
description:
|
|
||||||
"Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
|
|
||||||
logo: "plausible.svg", // we defined the name and the extension of the logo
|
|
||||||
links: {
|
|
||||||
github: "https://github.com/plausible/plausible",
|
|
||||||
website: "https://plausible.io/",
|
|
||||||
docs: "https://plausible.io/docs",
|
|
||||||
},
|
|
||||||
tags: ["analytics"],
|
|
||||||
load: () => import("./plausible/index").then((m) => m.generate),
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Add the logo or image of the template to `public/templates/plausible.svg`
|
|
||||||
|
|
||||||
### Recommendations
|
### Recommendations
|
||||||
|
|
||||||
|
|||||||
@@ -1,242 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
# Contributing
|
|
||||||
|
|
||||||
Hey, thanks for your interest in contributing to Dokploy! We appreciate your help and taking your time to contribute.
|
|
||||||
|
|
||||||
|
|
||||||
Before you start, please first discuss the feature/bug you want to add with the owners and comunity via github issues.
|
|
||||||
|
|
||||||
We have a few guidelines to follow when contributing to this project:
|
|
||||||
|
|
||||||
- [Commit Convention](#commit-convention)
|
|
||||||
- [Setup](#setup)
|
|
||||||
- [Development](#development)
|
|
||||||
- [Build](#build)
|
|
||||||
- [Pull Request](#pull-request)
|
|
||||||
|
|
||||||
## Commit Convention
|
|
||||||
|
|
||||||
Before you craete 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>
|
|
||||||
|
|
||||||
[optional body]
|
|
||||||
|
|
||||||
[optional footer(s)]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Type
|
|
||||||
Must be one of the following:
|
|
||||||
|
|
||||||
* **feat**: A new feature
|
|
||||||
* **fix**: A bug fix
|
|
||||||
* **docs**: Documentation only changes
|
|
||||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
|
||||||
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
|
||||||
* **perf**: A code change that improves performance
|
|
||||||
* **test**: Adding missing tests or correcting existing tests
|
|
||||||
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
|
||||||
* **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
|
|
||||||
* **chore**: Other changes that don't modify `src` or `test` files
|
|
||||||
* **revert**: Reverts a previous commit
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
feat: add new feature
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Before you start, please make the clone based on the `canary` branch, since the `main` branch is the source of truth and should always reflect the latest stable release, also the PRs will be merged to the `canary` branch.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/dokploy/dokploy.git
|
|
||||||
cd dokploy
|
|
||||||
pnpm install
|
|
||||||
cp .env.example .env
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Is required to have **Docker** installed on your machine.
|
|
||||||
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
Run the command that will spin up all the required services and files.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm run setup
|
|
||||||
```
|
|
||||||
|
|
||||||
Now run the development server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Go to http://localhost:3000 to see the development server
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Docker
|
|
||||||
|
|
||||||
To build the docker image
|
|
||||||
```bash
|
|
||||||
pnpm run docker:build
|
|
||||||
```
|
|
||||||
|
|
||||||
To push the docker image
|
|
||||||
```bash
|
|
||||||
pnpm run docker:push
|
|
||||||
```
|
|
||||||
|
|
||||||
## Password Reset
|
|
||||||
|
|
||||||
In the case you lost your password, you can reset it using the following command
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm run reset-password
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to test the webhooks on development mode using localtunnel, make sure to install `localtunnel`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bunx lt --port 3000
|
|
||||||
```
|
|
||||||
|
|
||||||
If you run into permission issues of docker run the following command
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo chown -R USERNAME dokploy or sudo chown -R $(whoami) ~/.docker
|
|
||||||
```
|
|
||||||
|
|
||||||
## Application deploy
|
|
||||||
|
|
||||||
In case you want to deploy the application on your machine and you selected nixpacks or buildpacks, you need to install first.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install Nixpacks
|
|
||||||
curl -sSL https://nixpacks.com/install.sh -o install.sh \
|
|
||||||
&& chmod +x install.sh \
|
|
||||||
&& ./install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install Buildpacks
|
|
||||||
curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.32.1/pack-v0.32.1-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Pull Request
|
|
||||||
|
|
||||||
- The `main` branch is the source of truth and should always reflect the latest stable release.
|
|
||||||
- Create a new branch for each feature or bug fix.
|
|
||||||
- Make sure to add tests for your changes.
|
|
||||||
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
|
|
||||||
- When creating a pull request, please provide a clear and concise description of the changes made.
|
|
||||||
- If you include a video or screenshot, would be awesome so we can see the changes in action.
|
|
||||||
- If your pull request fixes an open issue, please reference the issue in the pull request description.
|
|
||||||
- Once your pull request is merged, you will be automatically added as a contributor to the project.
|
|
||||||
|
|
||||||
Thank you for your contribution!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Templates
|
|
||||||
|
|
||||||
To add a new template, go to `templates` folder and create a new folder with the name of the template.
|
|
||||||
|
|
||||||
Let's take the example of `plausible` template.
|
|
||||||
|
|
||||||
1. create a folder in `templates/plausible`
|
|
||||||
2. create a `docker-compose.yml` file inside the folder with the content of compose.
|
|
||||||
3. create a `index.ts` file inside the folder with the following code as base:
|
|
||||||
4. When creating a pull request, please provide a video of the template working in action.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// EXAMPLE
|
|
||||||
import {
|
|
||||||
generateHash,
|
|
||||||
generateRandomDomain,
|
|
||||||
type Template,
|
|
||||||
type Schema,
|
|
||||||
} from "../utils";
|
|
||||||
|
|
||||||
|
|
||||||
export function generate(schema: Schema): Template {
|
|
||||||
|
|
||||||
// do your stuff here, like create a new domain, generate random passwords, mounts.
|
|
||||||
const mainServiceHash = generateHash(schema.projectName);
|
|
||||||
const randomDomain = generateRandomDomain(schema);
|
|
||||||
const secretBase = generateBase64(64);
|
|
||||||
const toptKeyBase = generateBase64(32);
|
|
||||||
|
|
||||||
const envs = [
|
|
||||||
// If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
|
|
||||||
`PLAUSIBLE_HOST=${randomDomain}`,
|
|
||||||
"PLAUSIBLE_PORT=8000",
|
|
||||||
`BASE_URL=http://${randomDomain}`,
|
|
||||||
`SECRET_KEY_BASE=${secretBase}`,
|
|
||||||
`TOTP_VAULT_KEY=${toptKeyBase}`,
|
|
||||||
`HASH=${mainServiceHash}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
const mounts: Template["mounts"] = [
|
|
||||||
{
|
|
||||||
mountPath: "./clickhouse/clickhouse-config.xml",
|
|
||||||
content: `some content......`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
envs,
|
|
||||||
mounts,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Now you need to add the information about the template to the `templates/templates.ts` is a object with the following properties:
|
|
||||||
|
|
||||||
**Make sure the id of the template is the same as the folder name and don't have any spaces, only slugified names and lowercase.**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
{
|
|
||||||
id: "plausible",
|
|
||||||
name: "Plausible",
|
|
||||||
version: "v2.1.0",
|
|
||||||
description:
|
|
||||||
"Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
|
|
||||||
logo: "plausible.svg", // we defined the name and the extension of the logo
|
|
||||||
links: {
|
|
||||||
github: "https://github.com/plausible/plausible",
|
|
||||||
website: "https://plausible.io/",
|
|
||||||
docs: "https://plausible.io/docs",
|
|
||||||
},
|
|
||||||
tags: ["analytics"],
|
|
||||||
load: () => import("./plausible/index").then((m) => m.generate),
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Add the logo or image of the template to `public/templates/plausible.svg`
|
|
||||||
|
|
||||||
|
|
||||||
### Recomendations
|
|
||||||
- Use the same name of the folder as the id of the template.
|
|
||||||
- The logo should be in the public folder.
|
|
||||||
- If you want to show a domain in the UI, please add the prefix _HOST at the end of the variable name.
|
|
||||||
- Test first on a vps or a server to make sure the template works.
|
|
||||||
|
|
||||||
@@ -166,7 +166,38 @@ describe("processTemplate", () => {
|
|||||||
if (!baseUrl || !secretKey) return;
|
if (!baseUrl || !secretKey) return;
|
||||||
|
|
||||||
expect(baseUrl).toContain(mockSchema.projectName);
|
expect(baseUrl).toContain(mockSchema.projectName);
|
||||||
expect(secretKey.split("=")[1]).toHaveLength(64);
|
const base64Value = secretKey.split("=")[1];
|
||||||
|
expect(base64Value).toBeDefined();
|
||||||
|
if (!base64Value) return;
|
||||||
|
expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
|
||||||
|
expect(base64Value.length).toBeGreaterThanOrEqual(86);
|
||||||
|
expect(base64Value.length).toBeLessThanOrEqual(88);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should process env vars when provided as an array", () => {
|
||||||
|
const template: CompleteTemplate = {
|
||||||
|
metadata: {} as any,
|
||||||
|
variables: {},
|
||||||
|
config: {
|
||||||
|
domains: [],
|
||||||
|
env: [
|
||||||
|
'CLOUDFLARE_TUNNEL_TOKEN="<INSERT TOKEN>"',
|
||||||
|
'ANOTHER_VAR="some value"',
|
||||||
|
"DOMAIN=${domain}",
|
||||||
|
],
|
||||||
|
mounts: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = processTemplate(template, mockSchema);
|
||||||
|
expect(result.envs).toHaveLength(3);
|
||||||
|
|
||||||
|
// Should preserve exact format for static values
|
||||||
|
expect(result.envs[0]).toBe('CLOUDFLARE_TUNNEL_TOKEN="<INSERT TOKEN>"');
|
||||||
|
expect(result.envs[1]).toBe('ANOTHER_VAR="some value"');
|
||||||
|
|
||||||
|
// Should process variables in array items
|
||||||
|
expect(result.envs[2]).toContain(mockSchema.projectName);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow using utility functions directly in env vars", () => {
|
it("should allow using utility functions directly in env vars", () => {
|
||||||
@@ -195,7 +226,12 @@ describe("processTemplate", () => {
|
|||||||
if (!randomDomainEnv || !secretKeyEnv) return;
|
if (!randomDomainEnv || !secretKeyEnv) return;
|
||||||
|
|
||||||
expect(randomDomainEnv).toContain(mockSchema.projectName);
|
expect(randomDomainEnv).toContain(mockSchema.projectName);
|
||||||
expect(secretKeyEnv.split("=")[1]).toHaveLength(32);
|
const base64Value = secretKeyEnv.split("=")[1];
|
||||||
|
expect(base64Value).toBeDefined();
|
||||||
|
if (!base64Value) return;
|
||||||
|
expect(base64Value).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
|
||||||
|
expect(base64Value.length).toBeGreaterThanOrEqual(42);
|
||||||
|
expect(base64Value.length).toBeLessThanOrEqual(44);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -325,8 +361,22 @@ describe("processTemplate", () => {
|
|||||||
if (!baseUrl || !secretKey || !totpKey) return;
|
if (!baseUrl || !secretKey || !totpKey) return;
|
||||||
|
|
||||||
expect(baseUrl).toContain(mockSchema.projectName);
|
expect(baseUrl).toContain(mockSchema.projectName);
|
||||||
expect(secretKey.split("=")[1]).toHaveLength(64);
|
|
||||||
expect(totpKey.split("=")[1]).toHaveLength(32);
|
// Check base64 lengths and format
|
||||||
|
const secretKeyValue = secretKey.split("=")[1];
|
||||||
|
const totpKeyValue = totpKey.split("=")[1];
|
||||||
|
|
||||||
|
expect(secretKeyValue).toBeDefined();
|
||||||
|
expect(totpKeyValue).toBeDefined();
|
||||||
|
if (!secretKeyValue || !totpKeyValue) return;
|
||||||
|
|
||||||
|
expect(secretKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
|
||||||
|
expect(secretKeyValue.length).toBeGreaterThanOrEqual(86);
|
||||||
|
expect(secretKeyValue.length).toBeLessThanOrEqual(88);
|
||||||
|
|
||||||
|
expect(totpKeyValue).toMatch(/^[A-Za-z0-9+/]+={0,2}$/);
|
||||||
|
expect(totpKeyValue.length).toBeGreaterThanOrEqual(42);
|
||||||
|
expect(totpKeyValue.length).toBeLessThanOrEqual(44);
|
||||||
|
|
||||||
// Check mounts
|
// Check mounts
|
||||||
expect(result.mounts).toHaveLength(1);
|
expect(result.mounts).toHaveLength(1);
|
||||||
@@ -334,8 +384,8 @@ describe("processTemplate", () => {
|
|||||||
expect(mount).toBeDefined();
|
expect(mount).toBeDefined();
|
||||||
if (!mount) return;
|
if (!mount) return;
|
||||||
expect(mount.content).toContain(mockSchema.projectName);
|
expect(mount.content).toContain(mockSchema.projectName);
|
||||||
expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{64}/);
|
expect(mount.content).toMatch(/secret=[A-Za-z0-9+/]{86,88}/);
|
||||||
expect(mount.content).toMatch(/totp=[A-Za-z0-9+/]{32}/);
|
expect(mount.content).toMatch(/totp=[A-Za-z0-9+/]{42,44}/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const UpdateApplication = ({ applicationId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const UpdateCompose = ({ composeId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -23,6 +24,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
externalPort: z.preprocess((a) => {
|
externalPort: z.preprocess((a) => {
|
||||||
@@ -106,6 +108,17 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
{!getIp && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
You need to set an IP address in your{" "}
|
||||||
|
<Link href="/dashboard/settings" className="text-primary">
|
||||||
|
{data?.serverId
|
||||||
|
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
||||||
|
: "Web Server -> Server -> Update Server IP"}
|
||||||
|
</Link>{" "}
|
||||||
|
to fix the database url connection.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -23,6 +24,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
externalPort: z.preprocess((a) => {
|
externalPort: z.preprocess((a) => {
|
||||||
@@ -106,6 +108,17 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
{!getIp && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
You need to set an IP address in your{" "}
|
||||||
|
<Link href="/dashboard/settings" className="text-primary">
|
||||||
|
{data?.serverId
|
||||||
|
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
||||||
|
: "Web Server -> Server -> Update Server IP"}
|
||||||
|
</Link>{" "}
|
||||||
|
to fix the database url connection.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const UpdateMongo = ({ mongoId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ export const ContainerFreeMonitoring = ({
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col gap-2 w-full">
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
Used: {currentData.cpu.value}%
|
Used: {currentData.cpu.value}
|
||||||
</span>
|
</span>
|
||||||
<Progress value={currentData.cpu.value} className="w-[100%]" />
|
<Progress value={currentData.cpu.value} className="w-[100%]" />
|
||||||
<DockerCpuChart acummulativeData={acummulativeData.cpu} />
|
<DockerCpuChart acummulativeData={acummulativeData.cpu} />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -23,6 +24,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
externalPort: z.preprocess((a) => {
|
externalPort: z.preprocess((a) => {
|
||||||
@@ -106,6 +108,17 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
{!getIp && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
You need to set an IP address in your{" "}
|
||||||
|
<Link href="/dashboard/settings" className="text-primary">
|
||||||
|
{data?.serverId
|
||||||
|
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
||||||
|
: "Web Server -> Server -> Update Server IP"}
|
||||||
|
</Link>{" "}
|
||||||
|
to fix the database url connection.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -23,6 +24,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
externalPort: z.preprocess((a) => {
|
externalPort: z.preprocess((a) => {
|
||||||
@@ -108,6 +110,17 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
{!getIp && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
You need to set an IP address in your{" "}
|
||||||
|
<Link href="/dashboard/settings" className="text-primary">
|
||||||
|
{data?.serverId
|
||||||
|
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
||||||
|
: "Web Server -> Server -> Update Server IP"}
|
||||||
|
</Link>{" "}
|
||||||
|
to fix the database url connection.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export const AddTemplate = ({ projectId, baseUrl }: Props) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={`${customBaseUrl || "https://dokploy.github.io/templates"}/blueprints/${template.id}/${template.logo}`}
|
src={`${customBaseUrl || "https://templates.dokploy.com/"}/blueprints/${template.id}/${template.logo}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"object-contain",
|
"object-contain",
|
||||||
viewMode === "detailed" ? "size-24" : "size-16",
|
viewMode === "detailed" ? "size-24" : "size-16",
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export const HandleProject = ({ projectId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export const ShowProjects = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="w-full grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 flex-wrap gap-5">
|
<div className="w-full grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 3xl:grid-cols-5 flex-wrap gap-5">
|
||||||
{filteredProjects?.map((project) => {
|
{filteredProjects?.map((project) => {
|
||||||
const emptyServices =
|
const emptyServices =
|
||||||
project?.mariadb.length === 0 &&
|
project?.mariadb.length === 0 &&
|
||||||
@@ -186,7 +186,7 @@ export const ShowProjects = () => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||||
>
|
>
|
||||||
<span>{domain.host}</span>
|
<span className="truncate">{domain.host}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -222,7 +222,7 @@ export const ShowProjects = () => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||||
>
|
>
|
||||||
<span>{domain.host}</span>
|
<span className="truncate">{domain.host}</span>
|
||||||
<ExternalLinkIcon className="size-4 shrink-0" />
|
<ExternalLinkIcon className="size-4 shrink-0" />
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -23,6 +24,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
const DockerProviderSchema = z.object({
|
const DockerProviderSchema = z.object({
|
||||||
externalPort: z.preprocess((a) => {
|
externalPort: z.preprocess((a) => {
|
||||||
@@ -100,6 +102,17 @@ export const ShowExternalRedisCredentials = ({ redisId }: Props) => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-4">
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
{!getIp && (
|
||||||
|
<AlertBlock type="warning">
|
||||||
|
You need to set an IP address in your{" "}
|
||||||
|
<Link href="/dashboard/settings" className="text-primary">
|
||||||
|
{data?.serverId
|
||||||
|
? "Remote Servers -> Server -> Edit Server -> Update IP Address"
|
||||||
|
: "Web Server -> Server -> Update Server IP"}
|
||||||
|
</Link>{" "}
|
||||||
|
to fix the database url connection.
|
||||||
|
</AlertBlock>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Tesla" {...field} />
|
<Input placeholder="Vandelay Industries" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ export const Enable2FA = () => {
|
|||||||
const handlePasswordSubmit = async (formData: PasswordForm) => {
|
const handlePasswordSubmit = async (formData: PasswordForm) => {
|
||||||
setIsPasswordLoading(true);
|
setIsPasswordLoading(true);
|
||||||
try {
|
try {
|
||||||
const { data: enableData } = await authClient.twoFactor.enable({
|
const { data: enableData, error } = await authClient.twoFactor.enable({
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!enableData) {
|
if (!enableData) {
|
||||||
throw new Error("No data received from server");
|
throw new Error(error?.message || "Error enabling 2FA");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableData.backupCodes) {
|
if (enableData.backupCodes) {
|
||||||
@@ -95,7 +95,8 @@ export const Enable2FA = () => {
|
|||||||
error instanceof Error ? error.message : "Error setting up 2FA",
|
error instanceof Error ? error.message : "Error setting up 2FA",
|
||||||
);
|
);
|
||||||
passwordForm.setError("password", {
|
passwordForm.setError("password", {
|
||||||
message: "Error verifying password",
|
message:
|
||||||
|
error instanceof Error ? error.message : "Error setting up 2FA",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsPasswordLoading(false);
|
setIsPasswordLoading(false);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -23,6 +24,7 @@ import { Loader2 } from "lucide-react";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { badgeStateColor } from "../../application/logs/show";
|
||||||
|
|
||||||
const Terminal = dynamic(
|
const Terminal = dynamic(
|
||||||
() =>
|
() =>
|
||||||
@@ -109,7 +111,10 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
|
|||||||
key={container.containerId}
|
key={container.containerId}
|
||||||
value={container.containerId}
|
value={container.containerId}
|
||||||
>
|
>
|
||||||
{container.name} ({container.containerId}) {container.state}
|
{container.name} ({container.containerId}){" "}
|
||||||
|
<Badge variant={badgeStateColor(container.state)}>
|
||||||
|
{container.state}
|
||||||
|
</Badge>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { Loader2 } from "lucide-react";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { badgeStateColor } from "../../application/logs/show";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
export const DockerLogsId = dynamic(
|
export const DockerLogsId = dynamic(
|
||||||
() =>
|
() =>
|
||||||
@@ -90,7 +92,10 @@ export const ShowModalLogs = ({
|
|||||||
key={container.containerId}
|
key={container.containerId}
|
||||||
value={container.containerId}
|
value={container.containerId}
|
||||||
>
|
>
|
||||||
{container.name} ({container.containerId}) {container.state}
|
{container.name} ({container.containerId}){" "}
|
||||||
|
<Badge variant={badgeStateColor(container.state)}>
|
||||||
|
{container.state}
|
||||||
|
</Badge>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
<SelectLabel>Containers ({data?.length})</SelectLabel>
|
||||||
|
|||||||
@@ -1068,7 +1068,7 @@ export default function Page({ children }: Props) {
|
|||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col w-full gap-4 p-4 pt-0">{children}</div>
|
<div className="flex flex-col w-full p-4 pt-0">{children}</div>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.20.1",
|
"version": "v0.20.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"adm-zip": "^0.5.14",
|
"adm-zip": "^0.5.14",
|
||||||
"ai": "^4.0.23",
|
"ai": "^4.0.23",
|
||||||
"bcrypt": "5.1.1",
|
"bcrypt": "5.1.1",
|
||||||
"better-auth": "1.2.0",
|
"better-auth": "1.2.4",
|
||||||
"bl": "6.0.11",
|
"bl": "6.0.11",
|
||||||
"boxen": "^7.1.1",
|
"boxen": "^7.1.1",
|
||||||
"bullmq": "5.4.2",
|
"bullmq": "5.4.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"settings.common.save": "Save",
|
"settings.common.save": "Save",
|
||||||
"settings.common.enterTerminal": "Enter the terminal",
|
"settings.common.enterTerminal": "Terminal",
|
||||||
"settings.server.domain.title": "Server Domain",
|
"settings.server.domain.title": "Server Domain",
|
||||||
"settings.server.domain.description": "Add a domain to your server application.",
|
"settings.server.domain.description": "Add a domain to your server application.",
|
||||||
"settings.server.domain.form.domain": "Domain",
|
"settings.server.domain.form.domain": "Domain",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"settings.server.webServer.description": "Reload or clean the web server.",
|
"settings.server.webServer.description": "Reload or clean the web server.",
|
||||||
"settings.server.webServer.actions": "Actions",
|
"settings.server.webServer.actions": "Actions",
|
||||||
"settings.server.webServer.reload": "Reload",
|
"settings.server.webServer.reload": "Reload",
|
||||||
"settings.server.webServer.watchLogs": "Watch logs",
|
"settings.server.webServer.watchLogs": "View Logs",
|
||||||
"settings.server.webServer.updateServerIp": "Update Server IP",
|
"settings.server.webServer.updateServerIp": "Update Server IP",
|
||||||
"settings.server.webServer.server.label": "Server",
|
"settings.server.webServer.server.label": "Server",
|
||||||
"settings.server.webServer.traefik.label": "Traefik",
|
"settings.server.webServer.traefik.label": "Traefik",
|
||||||
|
|||||||
@@ -437,13 +437,12 @@ export const composeRouter = createTRPCRouter({
|
|||||||
serverIp = "127.0.0.1";
|
serverIp = "127.0.0.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const projectName = slugify(`${project.name} ${input.id}`);
|
||||||
const generate = processTemplate(template.config, {
|
const generate = processTemplate(template.config, {
|
||||||
serverIp: serverIp,
|
serverIp: serverIp,
|
||||||
projectName: project.name,
|
projectName: projectName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectName = slugify(`${project.name} ${input.id}`);
|
|
||||||
|
|
||||||
const compose = await createComposeByTemplate({
|
const compose = await createComposeByTemplate({
|
||||||
...input,
|
...input,
|
||||||
composeFile: template.dockerCompose,
|
composeFile: template.dockerCompose,
|
||||||
@@ -607,7 +606,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const processedTemplate = processTemplate(config, {
|
const processedTemplate = processTemplate(config, {
|
||||||
serverIp: serverIp,
|
serverIp: serverIp,
|
||||||
projectName: compose.project.name,
|
projectName: compose.appName,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -676,7 +675,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const processedTemplate = processTemplate(config, {
|
const processedTemplate = processTemplate(config, {
|
||||||
serverIp: serverIp,
|
serverIp: serverIp,
|
||||||
projectName: compose.project.name,
|
projectName: compose.appName,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update compose file
|
// Update compose file
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ const config = {
|
|||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["var(--font-inter)", ...defaultTheme.fontFamily.sans],
|
sans: ["var(--font-inter)", ...defaultTheme.fontFamily.sans],
|
||||||
},
|
},
|
||||||
|
screens: {
|
||||||
|
"3xl": "120rem",
|
||||||
|
},
|
||||||
maxWidth: {
|
maxWidth: {
|
||||||
"2xl": "40rem",
|
"2xl": "40rem",
|
||||||
"8xl": "85rem",
|
"8xl": "85rem",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"@oslojs/encoding":"1.1.0",
|
"@oslojs/encoding":"1.1.0",
|
||||||
"@oslojs/crypto":"1.0.1",
|
"@oslojs/crypto":"1.0.1",
|
||||||
"drizzle-dbml-generator":"0.10.0",
|
"drizzle-dbml-generator":"0.10.0",
|
||||||
"better-auth":"1.2.0",
|
"better-auth":"1.2.4",
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^8.4.1",
|
||||||
"@lucia-auth/adapter-drizzle": "1.0.7",
|
"@lucia-auth/adapter-drizzle": "1.0.7",
|
||||||
"@octokit/auth-app": "^6.0.4",
|
"@octokit/auth-app": "^6.0.4",
|
||||||
|
|||||||
@@ -28,6 +28,26 @@ const { handler, api } = betterAuth({
|
|||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
...(!IS_CLOUD && {
|
||||||
|
async trustedOrigins() {
|
||||||
|
const admin = await db.query.member.findFirst({
|
||||||
|
where: eq(schema.member.role, "owner"),
|
||||||
|
with: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (admin) {
|
||||||
|
return [
|
||||||
|
...(admin.user.serverIp
|
||||||
|
? [`http://${admin.user.serverIp}:3000`]
|
||||||
|
: []),
|
||||||
|
...(admin.user.host ? [`https://${admin.user.host}`] : []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
}),
|
||||||
emailVerification: {
|
emailVerification: {
|
||||||
sendOnSignUp: true,
|
sendOnSignUp: true,
|
||||||
autoSignInAfterVerification: true,
|
autoSignInAfterVerification: true,
|
||||||
|
|||||||
@@ -53,14 +53,12 @@ export const generatePassword = (quantity = 16): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a random base64 string of specified length
|
* Generate a random base64 string from N random bytes
|
||||||
|
* @param bytes Number of random bytes to generate before base64 encoding (default: 32)
|
||||||
|
* @returns base64 encoded string of the random bytes
|
||||||
*/
|
*/
|
||||||
export function generateBase64(length: number): string {
|
export function generateBase64(bytes = 32): string {
|
||||||
// To get N characters in base64, we need to generate N * 3/4 bytes
|
return randomBytes(bytes).toString("base64");
|
||||||
const bytesNeeded = Math.ceil((length * 3) / 4);
|
|
||||||
return Buffer.from(randomBytes(bytesNeeded))
|
|
||||||
.toString("base64")
|
|
||||||
.substring(0, length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateJwt(length = 256): string {
|
export function generateJwt(length = 256): string {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export interface CompleteTemplate {
|
|||||||
variables: Record<string, string>;
|
variables: Record<string, string>;
|
||||||
config: {
|
config: {
|
||||||
domains: DomainConfig[];
|
domains: DomainConfig[];
|
||||||
env: Record<string, string>;
|
env: Record<string, string> | string[];
|
||||||
mounts?: MountConfig[];
|
mounts?: MountConfig[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -176,7 +176,6 @@ export function processDomains(
|
|||||||
schema: Schema,
|
schema: Schema,
|
||||||
): Template["domains"] {
|
): Template["domains"] {
|
||||||
if (!template?.config?.domains) return [];
|
if (!template?.config?.domains) return [];
|
||||||
|
|
||||||
return template?.config?.domains?.map((domain: DomainConfig) => ({
|
return template?.config?.domains?.map((domain: DomainConfig) => ({
|
||||||
...domain,
|
...domain,
|
||||||
host: domain.host
|
host: domain.host
|
||||||
@@ -195,7 +194,18 @@ export function processEnvVars(
|
|||||||
): Template["envs"] {
|
): Template["envs"] {
|
||||||
if (!template?.config?.env) return [];
|
if (!template?.config?.env) return [];
|
||||||
|
|
||||||
return Object.entries(template?.config?.env).map(
|
// Handle array of env vars
|
||||||
|
if (Array.isArray(template.config.env)) {
|
||||||
|
return template.config.env.map((env) => {
|
||||||
|
if (typeof env === "string") {
|
||||||
|
return processValue(env, variables, schema);
|
||||||
|
}
|
||||||
|
return env;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle object of env vars
|
||||||
|
return Object.entries(template.config.env).map(
|
||||||
([key, value]: [string, string]) => {
|
([key, value]: [string, string]) => {
|
||||||
const processedValue = processValue(value, variables, schema);
|
const processedValue = processValue(value, variables, schema);
|
||||||
return `${key}=${processedValue}`;
|
return `${key}=${processedValue}`;
|
||||||
|
|||||||
46
pnpm-lock.yaml
generated
46
pnpm-lock.yaml
generated
@@ -269,8 +269,8 @@ importers:
|
|||||||
specifier: 5.1.1
|
specifier: 5.1.1
|
||||||
version: 5.1.1(encoding@0.1.13)
|
version: 5.1.1(encoding@0.1.13)
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.2.0
|
specifier: 1.2.4
|
||||||
version: 1.2.0(typescript@5.5.3)
|
version: 1.2.4(typescript@5.5.3)
|
||||||
bl:
|
bl:
|
||||||
specifier: 6.0.11
|
specifier: 6.0.11
|
||||||
version: 6.0.11
|
version: 6.0.11
|
||||||
@@ -303,10 +303,10 @@ importers:
|
|||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.39.1
|
specifier: ^0.39.1
|
||||||
version: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
version: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
||||||
drizzle-zod:
|
drizzle-zod:
|
||||||
specifier: 0.5.1
|
specifier: 0.5.1
|
||||||
version: 0.5.1(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
|
version: 0.5.1(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
|
||||||
fancy-ansi:
|
fancy-ansi:
|
||||||
specifier: ^0.1.3
|
specifier: ^0.1.3
|
||||||
version: 0.1.3
|
version: 0.1.3
|
||||||
@@ -547,7 +547,7 @@ importers:
|
|||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.39.1
|
specifier: ^0.39.1
|
||||||
version: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
version: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
||||||
hono:
|
hono:
|
||||||
specifier: ^4.5.8
|
specifier: ^4.5.8
|
||||||
version: 4.5.8
|
version: 4.5.8
|
||||||
@@ -646,8 +646,8 @@ importers:
|
|||||||
specifier: 5.1.1
|
specifier: 5.1.1
|
||||||
version: 5.1.1(encoding@0.1.13)
|
version: 5.1.1(encoding@0.1.13)
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.2.0
|
specifier: 1.2.4
|
||||||
version: 1.2.0(typescript@5.5.3)
|
version: 1.2.4(typescript@5.5.3)
|
||||||
bl:
|
bl:
|
||||||
specifier: 6.0.11
|
specifier: 6.0.11
|
||||||
version: 6.0.11
|
version: 6.0.11
|
||||||
@@ -665,13 +665,13 @@ importers:
|
|||||||
version: 16.4.5
|
version: 16.4.5
|
||||||
drizzle-dbml-generator:
|
drizzle-dbml-generator:
|
||||||
specifier: 0.10.0
|
specifier: 0.10.0
|
||||||
version: 0.10.0(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))
|
version: 0.10.0(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))
|
||||||
drizzle-orm:
|
drizzle-orm:
|
||||||
specifier: ^0.39.1
|
specifier: ^0.39.1
|
||||||
version: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
version: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
||||||
drizzle-zod:
|
drizzle-zod:
|
||||||
specifier: 0.5.1
|
specifier: 0.5.1
|
||||||
version: 0.5.1(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
|
version: 0.5.1(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
|
||||||
hi-base32:
|
hi-base32:
|
||||||
specifier: ^0.5.1
|
specifier: ^0.5.1
|
||||||
version: 0.5.1
|
version: 0.5.1
|
||||||
@@ -4030,8 +4030,8 @@ packages:
|
|||||||
before-after-hook@2.2.3:
|
before-after-hook@2.2.3:
|
||||||
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
||||||
|
|
||||||
better-auth@1.2.0:
|
better-auth@1.2.4:
|
||||||
resolution: {integrity: sha512-eIRGOXfix25bh4fgs8jslZAZssufpIkxfEeEokQu5G4wICoDee1wPctkFb8v80PvhtI4dPm28SuAoZaAdRc6Wg==}
|
resolution: {integrity: sha512-/ZK2jbUjm8JwdeCLFrUWUBmexPyI9PkaLVXWLWtN60sMDHTY8B5G72wcHglo1QMFBaw4G0qFkP5ayl9k6XfDaA==}
|
||||||
|
|
||||||
better-call@1.0.3:
|
better-call@1.0.3:
|
||||||
resolution: {integrity: sha512-DUKImKoDIy5UtCvQbHTg0wuBRse6gu1Yvznn7+1B3I5TeY8sclRPFce0HI+4WF2bcb+9PqmkET8nXZubrHQh9A==}
|
resolution: {integrity: sha512-DUKImKoDIy5UtCvQbHTg0wuBRse6gu1Yvznn7+1B3I5TeY8sclRPFce0HI+4WF2bcb+9PqmkET8nXZubrHQh9A==}
|
||||||
@@ -5486,8 +5486,8 @@ packages:
|
|||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
kysely@0.27.5:
|
kysely@0.27.6:
|
||||||
resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==}
|
resolution: {integrity: sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
leac@0.6.0:
|
leac@0.6.0:
|
||||||
@@ -10902,7 +10902,7 @@ snapshots:
|
|||||||
|
|
||||||
before-after-hook@2.2.3: {}
|
before-after-hook@2.2.3: {}
|
||||||
|
|
||||||
better-auth@1.2.0(typescript@5.5.3):
|
better-auth@1.2.4(typescript@5.5.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@better-auth/utils': 0.2.3
|
'@better-auth/utils': 0.2.3
|
||||||
'@better-fetch/fetch': 1.1.15
|
'@better-fetch/fetch': 1.1.15
|
||||||
@@ -10913,7 +10913,7 @@ snapshots:
|
|||||||
better-call: 1.0.3
|
better-call: 1.0.3
|
||||||
defu: 6.1.4
|
defu: 6.1.4
|
||||||
jose: 5.9.6
|
jose: 5.9.6
|
||||||
kysely: 0.27.5
|
kysely: 0.27.6
|
||||||
nanostores: 0.11.3
|
nanostores: 0.11.3
|
||||||
valibot: 1.0.0-beta.15(typescript@5.5.3)
|
valibot: 1.0.0-beta.15(typescript@5.5.3)
|
||||||
zod: 3.24.1
|
zod: 3.24.1
|
||||||
@@ -11534,9 +11534,9 @@ snapshots:
|
|||||||
|
|
||||||
drange@1.1.1: {}
|
drange@1.1.1: {}
|
||||||
|
|
||||||
drizzle-dbml-generator@0.10.0(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)):
|
drizzle-dbml-generator@0.10.0(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)):
|
||||||
dependencies:
|
dependencies:
|
||||||
drizzle-orm: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
drizzle-orm: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
||||||
|
|
||||||
drizzle-kit@0.30.4:
|
drizzle-kit@0.30.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11547,18 +11547,18 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7):
|
drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
'@types/react': 18.3.5
|
'@types/react': 18.3.5
|
||||||
kysely: 0.27.5
|
kysely: 0.27.6
|
||||||
postgres: 3.4.4
|
postgres: 3.4.4
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
sqlite3: 5.1.7
|
sqlite3: 5.1.7
|
||||||
|
|
||||||
drizzle-zod@0.5.1(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8):
|
drizzle-zod@0.5.1(drizzle-orm@0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8):
|
||||||
dependencies:
|
dependencies:
|
||||||
drizzle-orm: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
drizzle-orm: 0.39.1(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(kysely@0.27.6)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
|
|
||||||
eastasianwidth@0.2.0: {}
|
eastasianwidth@0.2.0: {}
|
||||||
@@ -12450,7 +12450,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
|
|
||||||
kysely@0.27.5: {}
|
kysely@0.27.6: {}
|
||||||
|
|
||||||
leac@0.6.0: {}
|
leac@0.6.0: {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user