Compare commits

..

70 Commits

Author SHA1 Message Date
Mauricio Siu
385fbf4af5 Merge pull request #355 from Dokploy/canary
v0.6.3
2024-08-16 22:26:35 -06:00
Mauricio Siu
44e75ee7e1 refactor: update deps 2024-08-16 22:10:23 -06:00
Mauricio Siu
6b4d6eac1d chore: bump version 2024-08-16 22:07:02 -06:00
Mauricio Siu
175e84f50e refactor: update container id 2024-08-13 23:29:51 -06:00
Mauricio Siu
efb646c43d Merge pull request #346 from Dokploy/282-add-option-to-revert-dokploy-version-opt-in-based-auto-updates
282 add option to revert dokploy version opt in based auto updates
2024-08-13 23:12:57 -06:00
Mauricio Siu
fa950dae39 fix(settings): prevent to download the latest image on reload 2024-08-13 23:04:21 -06:00
Mauricio Siu
712ad25e7a feat(permission): add permission to access to ssh key section 2024-08-13 22:19:04 -06:00
Mauricio Siu
35a41e774e Merge pull request #343 from Tuluobo/bugfix/delete_service_with_container
fix(ui): close dialog after templete selected & add config editor line wrapping
2024-08-13 22:02:33 -06:00
Mauricio Siu
c2ac193fbe Merge pull request #344 from Dokploy/340-dokploy-postgres-and-redis-are-exposed
fix(services): set published port 0 to prevent swarm assign random po…
2024-08-13 21:56:12 -06:00
Mauricio Siu
ce3c89a715 Merge pull request #342 from Dokploy/326-dokploy-doesnt-persist-registry-tokens
fix(docker): add root docker to prevent registry delete in each resta…
2024-08-13 21:52:07 -06:00
Mauricio Siu
96f7206a1d fix(services): set published port 0 to prevent swarm assign random ports #340 2024-08-13 21:49:21 -06:00
Mauricio Siu
b7ace886f3 fix(docker): add root docker to prevent registry delete in each restart/update dokploy server #326 2024-08-13 20:40:34 -06:00
Mauricio Siu
5dc330eaa3 Merge pull request #341 from Dokploy/337-incorrect-github-apps-install-link-when-app-name-contain-special-characters
fix(github): use github url to install the application #337
2024-08-13 20:04:46 -06:00
Mauricio Siu
b7f5bee2f8 fix(github): use github url to install the application #337 2024-08-13 19:57:10 -06:00
Tuluobo
19ee5f073b feat: add line wrapping for traefik config editor 2024-08-13 20:51:50 +08:00
Tuluobo
1fd4a6ae80 refactor: close dialog after selected template 2024-08-13 19:38:09 +08:00
Mauricio Siu
3c8a412014 Merge pull request #339 from Vladislav-CS/patch-1
fix: responsive design in the project settings page
2024-08-10 16:00:06 -06:00
Vladislav Popovič
eee617719b Update show-deployments.tsx 2024-08-10 22:19:15 +03:00
Mauricio Siu
fc611946a6 Merge pull request #334 from Vladislav-CS/fix-typos
fix: typos
2024-08-08 10:59:22 -06:00
Vladislav Popovič
af13c84968 Update add-template.tsx 2024-08-08 18:21:35 +03:00
Vladislav Popovič
ddb78ef8dd Update show-ssh-keys.tsx 2024-08-08 18:17:12 +03:00
Mauricio Siu
3590f3bed2 Merge pull request #332 from Dokploy/canary
v0.6.2
2024-08-07 21:48:49 -06:00
Mauricio Siu
c70089ee53 refactor(logs): add error log in build application 2024-08-07 21:41:25 -06:00
Mauricio Siu
161e479a0b chore(version): bump version 2024-08-07 21:08:42 -06:00
Mauricio Siu
bd735bfb64 Merge pull request #331 from Dokploy/322-git-submodules-are-not-cloned
fix(git): add --recursive-submodules flag
2024-08-07 21:09:29 -06:00
Mauricio Siu
85c814620e Merge pull request #330 from Dokploy/fix/add-validation-git-source
fix(git): don't add to ssh known host when is a http or https url
2024-08-07 20:59:42 -06:00
Mauricio Siu
fb013fe4ec fix(git): add --recursive-submodules flag 2024-08-07 20:58:16 -06:00
Mauricio Siu
90a1bd9027 fix(git): don't add to ssh known host when is a http or https url 2024-08-07 20:51:27 -06:00
Mauricio Siu
610ef8f35c Update README.md 2024-08-05 10:23:46 -06:00
Mauricio Siu
9b2fcaea31 Merge pull request #317 from Dokploy/canary
v0.6.1
2024-08-03 15:46:05 -06:00
Mauricio Siu
2ffa95b9fa chore(version): bump version 2024-08-03 15:34:09 -06:00
Mauricio Siu
559872c4e4 Merge pull request #316 from Dokploy/315-docs-website-error-dialogclose-must-be-used-within-dialog
fix(docs): update dependencies
2024-08-03 15:32:36 -06:00
Mauricio Siu
85642ed5f2 refactor(docs): add volumes deployments 2024-08-03 15:14:02 -06:00
Mauricio Siu
8bf701d2f2 fix(docs): update dependencies 2024-08-03 15:06:47 -06:00
Mauricio Siu
38809a2034 Merge pull request #314 from Dokploy/fix/add-file-path
fix(template): add missing file path
2024-08-03 15:05:31 -06:00
Mauricio Siu
4bd6ec2232 fix(templates): use filePath instead of mountPath 2024-08-03 14:59:20 -06:00
Mauricio Siu
95899b7208 fix(templates): update path file path 2024-08-02 13:45:20 -06:00
Mauricio Siu
ac26bb95e3 fix(template): add missing file path 2024-08-02 13:40:25 -06:00
Mauricio Siu
5abcc82215 Merge pull request #312 from Dokploy/canary
v0.6.0
2024-08-02 10:47:43 -06:00
Mauricio Siu
16791a9f4b refactor: remove console log 2024-08-02 10:44:39 -06:00
Mauricio Siu
54ab6e3436 chore(version): bump version 2024-08-02 10:36:06 -06:00
Mauricio Siu
f5ca72ddd7 refactor: add default value to context path 2024-08-02 10:18:41 -06:00
Mauricio Siu
7245e7dfd7 Merge pull request #310 from Dokploy/fix/docker-context
fix(docker-context): add docker context path #284
2024-08-02 09:52:27 -06:00
Mauricio Siu
547d149987 Merge pull request #311 from kucherenko/canary
docs(templates): add documentation about teable and open webui templates
2024-08-02 09:52:05 -06:00
apk
3b2d29514c fix(docs): type in description 2024-08-02 11:20:45 +03:00
apk
115abd378f docs(templates): add information about open webui and about teable to documention 2024-08-02 11:18:32 +03:00
Mauricio Siu
9c101d78d1 Merge pull request #305 from kucherenko/canary
feat(template): add teable template
2024-08-02 01:06:53 -06:00
apk
610f8fa5bc fix(teable): remove network 2024-08-02 09:57:36 +03:00
Mauricio Siu
e201bf12f8 fix(test): add missing prop 2024-08-02 00:28:17 -06:00
Mauricio Siu
bf872200a7 refactor(docker): add type password to input 2024-08-02 00:25:44 -06:00
Mauricio Siu
abc6906349 fix(docker-context): add docker context path #284 2024-08-02 00:22:37 -06:00
apk
9440fd89ae fix(teable): round port for public database 2024-08-02 08:29:53 +03:00
apk
bce1eb8907 fix(teable): port for public database 2024-08-02 08:27:17 +03:00
apk
4bf44b3275 fix(docker-compose): issue with volumes, issue with database connection 2024-08-01 22:05:36 +03:00
Andrey Kucherenko
06355ff089 fix(taeble): version and env variables 2024-08-01 19:27:07 +03:00
Mauricio Siu
95ecf4fe21 Merge pull request #309 from ca110us/canary
fix: align entry point names in configs
2024-08-01 10:19:45 -06:00
ian
d50a6ce76f fix: align entry point names in configs 2024-08-01 23:53:53 +08:00
Mauricio Siu
2d951e0b1f Merge pull request #307 from Dokploy/306-fix-containers-are-deleted-when-dokploy-server-restart
fix(swarm): remove restart policy #306
2024-08-01 09:53:44 -06:00
Mauricio Siu
416de9879b fix(swarm): remove restart policy #306 2024-08-01 09:31:58 -06:00
Mauricio Siu
082aff58a9 chore: add ref query to links 2024-08-01 09:12:25 -06:00
Mauricio Siu
b74666fc2f Merge pull request #304 from lorenzomigliorero/feat/static-buildtype
feat: static buildtype
2024-08-01 09:11:02 -06:00
Andrey Kucherenko
dc626f1a94 chore(lint): fix formating 2024-08-01 13:00:03 +03:00
Andrey Kucherenko
533a5e490f feat(template): add teable template (one more no/low-code database) 2024-08-01 12:13:36 +03:00
Lorenzo Migliorero
cf54e4f5c2 feat: add static migration 2024-08-01 11:10:52 +02:00
Lorenzo Migliorero
d84c808887 feat: add build static 2024-08-01 11:10:28 +02:00
Andrey Kucherenko
89cd35adc6 chore(gitignore): add .idea folder to gitignore file 2024-08-01 11:34:09 +03:00
Mauricio Siu
6299385bb4 Merge pull request #297 from lorenzomigliorero/feat/static-build-phase
feat: new publish directory flag
2024-08-01 02:28:37 -06:00
Lorenzo Migliorero
e6f9867500 fix: error handling 2024-08-01 10:13:29 +02:00
Lorenzo Migliorero
3fdd3ddc74 fix: missing type 2024-07-31 19:27:11 +02:00
Lorenzo Migliorero
6ed379243e feat: add publish directory flag 2024-07-31 19:08:34 +02:00
64 changed files with 12759 additions and 204 deletions

1
.gitignore vendored
View File

@@ -35,6 +35,7 @@ yarn-error.log*
# Editor
.vscode
.idea
# Misc
.DS_Store

View File

@@ -1,7 +1,7 @@
<div align="center">
<h1 align="center">Dokploy</h1>
<div>
<img style="object-fit: cover; border-radius:20px;" align="center" width="50%"src="https://raw.githubusercontent.com/Dokploy/docs/main/public/logo.png" >
<img style="object-fit: cover; border-radius:20px;" align="center" width="50%"src="https://dokploy.com/og.png" >
</div>
@@ -59,7 +59,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
### Premium Supporters 🥇
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://supafort.com/" target="_blank"><img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" width="190"/></a>
<a href="https://supafort.com/?ref=dokploy" target="_blank"><img src="https://supafort.com/build/q-4Ht4rBZR.webp" alt="Supafort.com" width="190"/></a>
</div>
<!-- Elite Contributors 🥈 -->
@@ -69,13 +69,13 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
### Supporting Members 🥉
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://lightspeed.run/"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
</div>
### Community Backers 🤝
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://steamsets.com/"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
<a href="https://steamsets.com/?ref=dokploy"><img src="https://avatars.githubusercontent.com/u/111978405?s=200&v=4" width="60px" alt="Steamsets.com"/></a>
</div>
#### Organizations:

View File

@@ -3,13 +3,14 @@ title: "Overview"
description: "Learn how to use Docker Compose with Dokploy"
---
import { Callout } from "fumadocs-ui/components/callout";
Dokploy integrates with Docker Compose and Docker Stack to provide flexible deployment solutions. Whether you are developing locally or deploying at scale, Dokploy facilitates application management through these powerful Docker tools.
### Configuration Methods
Dokploy provides two methods for creating Docker Compose configurations:
- **Docker Compose**: Ideal for standard Docker Compose configurations.
- **Stack**: Geared towards orchestrating applications using Docker Swarm. Note that some Docker Compose features, such as `build`, are not available in this mode.
@@ -21,7 +22,7 @@ Configure the source of your code, the way your application is built, and also m
A code editor within Dokploy allows you to specify environment variables for your Docker Compose file. By default, Dokploy creates a `.env` file in the specified Docker Compose file path.
### Monitoring
### Monitoring
Monitor each service individually within Dokploy. If your application consists of multiple services, each can be monitored separately to ensure optimal performance.
@@ -29,7 +30,6 @@ Monitor each service individually within Dokploy. If your application consists o
Access detailed logs for each service through the Dokploy log viewer, which can help in troubleshooting and ensuring the stability of your services.
### Deployments
You can view the last 10 deployments of your application. When you deploy your application in real time, a new deployment record will be created and it will gradually show you how your application is being built.
@@ -38,7 +38,6 @@ We also offer a button to cancel deployments that are in queue. Note that those
We provide a webhook so that you can trigger your own deployments by pushing to your GitHub, Gitea, GitLab, Bitbucket repository.
### Advanced
This section provides advanced configuration options for experienced users. It includes tools for custom commands within the container and volumes.
@@ -46,4 +45,32 @@ This section provides advanced configuration options for experienced users. It i
- **Command**: Dokploy has a defined command to run the Docker Compose file, ensuring complete control through the UI. However, you can append flags or options to the command.
- **Volumes**: To ensure data persistence across deployments, configure storage volumes for your application.
<ImageZoom src="/assets/images/compose/overview.png" width={800} height={630} quality={100} priority alt='home og image' className="rounded-lg" />
<ImageZoom
src="/assets/images/compose/overview.png"
width={800}
height={630}
quality={100}
priority
alt="home og image"
className="rounded-lg"
/>
<Callout title="Volumes">
Docker volumes are a way to persist data generated and used by Docker containers. They are particularly useful for maintaining data between container restarts or for sharing data among different containers.
To bind a volume to the host machine, you can use the following syntax in your docker-compose.yml file, but this way will clean up the volumes when a new deployment is made:
```yaml
volumes:
- "/folder:/path/in/container" ❌
```
It's recommended to use the ../files folder to ensure your data persists between deployments. For example:
```yaml
volumes:
- "../files/my-database:/var/lib/mysql" ✅
- "../files/my-configs:/etc/my-app/config" ✅
```
</Callout>

View File

@@ -32,7 +32,8 @@ The following templates are available:
- **Metabase**: Open Source Business Intelligence
- **Grafana**: Open Source Dashboard for your metrics
- **Wordpress**: Open Source Content Management System
- **Open WebUI**: Free and Open Source ChatGPT Alternative
- **Teable**: Open Source Airtable Alternative, Developer Friendly, No-code Database Built on Postgres
@@ -42,4 +43,4 @@ We accept contributions to upload new templates to the dokploy repository.
Make sure to follow the guidelines for creating a template:
[Steps to create your own template](https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#templates)
[Steps to create your own template](https://github.com/Dokploy/dokploy/blob/canary/CONTRIBUTING.md#templates)

View File

@@ -10,10 +10,10 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"fumadocs-core": "12.2.2",
"fumadocs-mdx": "8.2.33",
"fumadocs-openapi": "^3.1.3",
"fumadocs-ui": "12.2.2",
"fumadocs-core": "^12.5.6",
"fumadocs-mdx": "^8.2.34",
"fumadocs-openapi": "^3.3.0",
"fumadocs-ui": "^12.5.6",
"lucide-react": "^0.394.0",
"next": "^14.2.4",
"react": "^18.3.1",

View File

@@ -78,7 +78,7 @@ test("Should not touch config without host", () => {
expect(originalConfig).toEqual(config);
});
test("Should remove web-secure if https rollback to http", () => {
test("Should remove websecure if https rollback to http", () => {
const originalConfig: FileConfig = loadOrCreateConfig("dokploy");
updateServerTraefik(

View File

@@ -40,6 +40,7 @@ const baseApp: ApplicationNested = {
placementSwarm: null,
ports: [],
projectId: "",
publishDirectory: null,
redirects: [],
refreshToken: "",
registry: null,
@@ -54,6 +55,7 @@ const baseApp: ApplicationNested = {
title: null,
updateConfigSwarm: null,
username: null,
dockerContextPath: null,
};
const baseDomain: Domain = {

View File

@@ -46,6 +46,7 @@ export const ShowTraefikConfig = ({ applicationId }: Props) => {
<div className="flex flex-col pt-2 relative">
<div className="flex flex-col gap-6 max-h-[35rem] min-h-[10rem] overflow-y-auto">
<CodeEditor
lineWrapping
value={data || "Empty"}
disabled
className="font-mono"

View File

@@ -144,6 +144,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
<FormLabel>Traefik config</FormLabel>
<FormControl>
<CodeEditor
lineWrapping
wrapperClassName="h-[35rem] font-mono"
placeholder={`http:
routers:

View File

@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
@@ -23,6 +24,7 @@ enum BuildType {
heroku_buildpacks = "heroku_buildpacks",
paketo_buildpacks = "paketo_buildpacks",
nixpacks = "nixpacks",
static = "static",
}
const mySchema = z.discriminatedUnion("buildType", [
@@ -34,6 +36,7 @@ const mySchema = z.discriminatedUnion("buildType", [
invalid_type_error: "Dockerfile path is required",
})
.min(1, "Dockerfile required"),
dockerContextPath: z.string().nullable().default(""),
}),
z.object({
buildType: z.literal("heroku_buildpacks"),
@@ -43,6 +46,10 @@ const mySchema = z.discriminatedUnion("buildType", [
}),
z.object({
buildType: z.literal("nixpacks"),
publishDirectory: z.string().optional(),
}),
z.object({
buildType: z.literal("static"),
}),
]);
@@ -73,17 +80,18 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
const buildType = form.watch("buildType");
useEffect(() => {
if (data) {
// TODO: refactor this
if (data.buildType === "dockerfile") {
form.reset({
buildType: data.buildType,
...(data.buildType && {
dockerfile: data.dockerfile || "",
dockerContextPath: data.dockerContextPath || "",
}),
});
} else {
form.reset({
buildType: data.buildType,
publishDirectory: data.publishDirectory || undefined,
});
}
}
@@ -93,7 +101,11 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
await mutateAsync({
applicationId,
buildType: data.buildType,
publishDirectory:
data.buildType === "nixpacks" ? data.publishDirectory : null,
dockerfile: data.buildType === "dockerfile" ? data.dockerfile : null,
dockerContextPath:
data.buildType === "dockerfile" ? data.dockerContextPath : null,
})
.then(async () => {
toast.success("Build type saved");
@@ -171,6 +183,12 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
Paketo Buildpacks
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="static" />
</FormControl>
<FormLabel className="font-normal">Static</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
@@ -179,16 +197,71 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
}}
/>
{buildType === "dockerfile" && (
<>
<FormField
control={form.control}
name="dockerfile"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Docker File</FormLabel>
<FormControl>
<Input
placeholder={"Path of your docker file"}
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="dockerContextPath"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Docker Context Path</FormLabel>
<FormControl>
<Input
placeholder={
"Path of your docker context default: ."
}
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
</>
)}
{buildType === "nixpacks" && (
<FormField
control={form.control}
name="dockerfile"
name="publishDirectory"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Docker File</FormLabel>
<div className="space-y-0.5">
<FormLabel>Publish Directory</FormLabel>
<FormDescription>
Allows you to serve a single directory via NGINX after
the build phase. Useful if the final build assets
should be served as a static site.
</FormDescription>
</div>
<FormControl>
<Input
placeholder={"Path of your docker file"}
placeholder={"Publish Directory"}
{...field}
value={field.value ?? ""}
/>

View File

@@ -53,7 +53,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
<div className="flex flex-row items-center gap-2 flex-wrap">
<span>Webhook URL: </span>
<div className="flex flex-row items-center gap-2">
<span className="text-muted-foreground">
<span className="break-all text-muted-foreground">
{`${url}/api/deploy/${data?.refreshToken}`}
</span>
<RefreshToken applicationId={applicationId} />
@@ -72,7 +72,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
{deployments?.map((deployment) => (
<div
key={deployment.deploymentId}
className="flex items-center justify-between rounded-lg border p-4"
className="flex items-center justify-between rounded-lg border p-4 gap-2"
>
<div className="flex flex-col">
<span className="flex items-center gap-4 font-medium capitalize text-foreground">
@@ -87,7 +87,7 @@ export const ShowDeployments = ({ applicationId }: Props) => {
{deployment.title}
</span>
{deployment.description && (
<span className="text-sm text-muted-foreground">
<span className="break-all text-sm text-muted-foreground">
{deployment.description}
</span>
)}

View File

@@ -114,7 +114,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="Password" {...field} />
<Input placeholder="Password" {...field} type="password" />
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -104,6 +104,7 @@ export const ShowTraefikFile = ({ path }: Props) => {
</FormDescription>
<FormControl>
<CodeEditor
lineWrapping
wrapperClassName="h-[35rem] font-mono"
placeholder={`http:
routers:

View File

@@ -55,6 +55,7 @@ interface Props {
export const AddTemplate = ({ projectId }: Props) => {
const [query, setQuery] = useState("");
const [open, setOpen] = useState(false);
const { data } = api.compose.templates.useQuery();
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const { data: tags, isLoading: isLoadingTags } =
@@ -75,14 +76,14 @@ export const AddTemplate = ({ projectId }: Props) => {
}) || [];
return (
<Dialog>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger className="w-full">
<DropdownMenuItem
className="w-full cursor-pointer space-x-3"
onSelect={(e) => e.preventDefault()}
>
<PuzzleIcon className="size-4 text-muted-foreground" />
<span>Templates</span>
<span>Template</span>
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-7xl p-0">
@@ -283,6 +284,7 @@ export const AddTemplate = ({ projectId }: Props) => {
utils.project.one.invalidate({
projectId,
});
setOpen(false);
})
.catch(() => {
toast.error(

View File

@@ -9,36 +9,11 @@ import {
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { format } from "date-fns";
import { BadgeCheck } from "lucide-react";
import Link from "next/link";
import React, { useEffect, useState } from "react";
import { RemoveGithubApp } from "./remove-github-app";
export const generateName = () => {
const n1 = ["Blue", "Green", "Red", "Orange", "Violet", "Indigo", "Yellow"];
const n2 = [
"One",
"Two",
"Three",
"Four",
"Five",
"Six",
"Seven",
"Eight",
"Nine",
"Zero",
];
return `Dokploy-${n1[Math.round(Math.random() * (n1.length - 1))]}-${
n2[Math.round(Math.random() * (n2.length - 1))]
}`;
};
function slugify(text: string) {
return text
.toLowerCase()
.replace(/[\s\^&*()+=!]+/g, "-")
.replace(/[\$.,*+~()'"!:@^&]+/g, "")
.replace(/-+/g, "-")
.replace(/^-+|-+$/g, "");
}
export const GithubSetup = () => {
const [isOrganization, setIsOrganization] = useState(false);
@@ -52,10 +27,9 @@ export const GithubSetup = () => {
const manifest = JSON.stringify(
{
redirect_url: `${origin}/api/redirect?authId=${data?.authId}`,
name: generateName(),
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`,
url: origin,
hook_attributes: {
// JUST FOR TESTING
url: `${url}/api/deploy/github`,
// url: `${origin}/api/webhook`, // Aquí especificas la URL del endpoint de tu webhook
},
@@ -95,8 +69,8 @@ export const GithubSetup = () => {
</div>
<div className="flex items-end gap-4 flex-wrap">
<RemoveGithubApp />
{/* <Link
href={`https://github.com/settings/apps/${data?.githubAppName}`}
<Link
href={`${data?.githubAppName}`}
target="_blank"
className={buttonVariants({
className: "w-fit",
@@ -104,7 +78,7 @@ export const GithubSetup = () => {
})}
>
<span className="text-sm">Manage Github App</span>
</Link> */}
</Link>
</div>
</div>
) : (
@@ -119,9 +93,9 @@ export const GithubSetup = () => {
<div className="flex flex-row gap-4">
<Link
href={`https://github.com/apps/${slugify(
data.githubAppName,
)}/installations/new?state=gh_setup:${data?.authId}`}
href={`${
data.githubAppName
}/installations/new?state=gh_setup:${data?.authId}`}
className={buttonVariants({ className: "w-fit" })}
>
Install Github App

View File

@@ -22,7 +22,7 @@ export const ShowDestinations = () => {
<CardHeader>
<CardTitle className="text-xl">SSH Keys</CardTitle>
<CardDescription>
Use SSH to beeing able cloning from private repositories.
Use SSH to be able to clone from private repositories.
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 pt-4">

View File

@@ -39,6 +39,7 @@ const addPermissions = z.object({
canAccessToTraefikFiles: z.boolean().optional().default(false),
canAccessToDocker: z.boolean().optional().default(false),
canAccessToAPI: z.boolean().optional().default(false),
canAccessToSSHKeys: z.boolean().optional().default(false),
});
type AddPermissions = z.infer<typeof addPermissions>;
@@ -82,6 +83,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
canAccessToTraefikFiles: data.canAccessToTraefikFiles,
canAccessToDocker: data.canAccessToDocker,
canAccessToAPI: data.canAccessToAPI,
canAccessToSSHKeys: data.canAccessToSSHKeys,
});
}
}, [form, form.formState.isSubmitSuccessful, form.reset, data]);
@@ -98,6 +100,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
accesedServices: data.accesedServices || [],
canAccessToDocker: data.canAccessToDocker,
canAccessToAPI: data.canAccessToAPI,
canAccessToSSHKeys: data.canAccessToSSHKeys,
})
.then(async () => {
toast.success("Permissions updated");
@@ -270,6 +273,26 @@ export const AddUserPermissions = ({ userId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="canAccessToSSHKeys"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Access to SSH Keys</FormLabel>
<FormDescription>
Allow to users to access to the SSH Keys section
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="accesedProjects"

View File

@@ -106,6 +106,7 @@ export const ShowMainTraefikConfig = ({ children }: Props) => {
<FormLabel>Traefik config</FormLabel>
<FormControl>
<CodeEditor
lineWrapping
wrapperClassName="h-[35rem] font-mono"
placeholder={`providers:
docker:

View File

@@ -109,6 +109,7 @@ export const ShowServerTraefikConfig = ({ children }: Props) => {
<FormLabel>Traefik config</FormLabel>
<FormControl>
<CodeEditor
lineWrapping
wrapperClassName="h-[35rem] font-mono"
placeholder={`http:
routers:

View File

@@ -79,6 +79,16 @@ export const SettingsLayout = ({ children }: Props) => {
},
]
: []),
...(user?.canAccessToSSHKeys
? [
{
title: "SSH Keys",
label: "",
icon: KeyRound,
href: "/dashboard/settings/ssh-keys",
},
]
: []),
]}
/>
</div>

View File

@@ -3,6 +3,7 @@ import { json } from "@codemirror/lang-json";
import { yaml } from "@codemirror/lang-yaml";
import { StreamLanguage } from "@codemirror/language";
import { properties } from "@codemirror/legacy-modes/mode/properties";
import { EditorView } from "@codemirror/view";
import { githubDark, githubLight } from "@uiw/codemirror-theme-github";
import CodeMirror, { type ReactCodeMirrorProps } from "@uiw/react-codemirror";
import { useTheme } from "next-themes";
@@ -10,6 +11,7 @@ interface Props extends ReactCodeMirrorProps {
wrapperClassName?: string;
disabled?: boolean;
language?: "yaml" | "json" | "properties";
lineWrapping?: boolean;
}
export const CodeEditor = ({
@@ -36,6 +38,7 @@ export const CodeEditor = ({
: language === "json"
? json()
: StreamLanguage.define(properties),
props.lineWrapping ? EditorView.lineWrapping : [],
]}
{...props}
editable={!props.disabled}

View File

@@ -0,0 +1 @@
ALTER TABLE "application" ADD COLUMN "publishDirectory" text;

View File

@@ -0,0 +1 @@
ALTER TYPE "buildType" ADD VALUE 'static';

View File

@@ -0,0 +1 @@
ALTER TABLE "application" ADD COLUMN "dockerContextPath" text;

View File

@@ -0,0 +1 @@
ALTER TABLE "user" ADD COLUMN "canAccessToSSHKeys" boolean DEFAULT false NOT NULL;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -190,6 +190,34 @@
"when": 1721979220929,
"tag": "0026_known_dormammu",
"breakpoints": true
},
{
"idx": 27,
"version": "6",
"when": 1722445099203,
"tag": "0027_red_lady_bullseye",
"breakpoints": true
},
{
"idx": 28,
"version": "6",
"when": 1722503439951,
"tag": "0028_jittery_eternity",
"breakpoints": true
},
{
"idx": 29,
"version": "6",
"when": 1722578386823,
"tag": "0029_colossal_zodiak",
"breakpoints": true
},
{
"idx": 30,
"version": "6",
"when": 1723608499147,
"tag": "0030_little_kabuki",
"breakpoints": true
}
]
}

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.5.1",
"version": "v0.6.3",
"private": true,
"license": "Apache-2.0",
"type": "module",
@@ -39,6 +39,7 @@
"@codemirror/lang-yaml": "^6.1.1",
"@codemirror/language": "^6.10.1",
"@codemirror/legacy-modes": "6.4.0",
"@codemirror/view": "6.29.0",
"@dokploy/trpc-openapi": "0.0.4",
"@faker-js/faker": "^8.4.1",
"@hookform/resolvers": "^3.3.4",

View File

@@ -35,7 +35,7 @@ export default async function handler(
.update(admins)
.set({
githubAppId: data.id,
githubAppName: data.name,
githubAppName: data.html_url,
githubClientId: data.client_id,
githubClientSecret: data.client_secret,
githubWebhookSecret: data.webhook_secret,

View File

@@ -1,9 +1,12 @@
import { ShowDestinations } from "@/components/dashboard/settings/ssh-keys/show-ssh-keys";
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { SettingsLayout } from "@/components/layouts/settings-layout";
import { appRouter } from "@/server/api/root";
import { validateRequest } from "@/server/auth/auth";
import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext } from "next";
import React, { type ReactElement } from "react";
import superjson from "superjson";
const Page = () => {
return (
@@ -26,7 +29,7 @@ export async function getServerSideProps(
ctx: GetServerSidePropsContext<{ serviceId: string }>,
) {
const { user, session } = await validateRequest(ctx.req, ctx.res);
if (!user || user.rol === "user") {
if (!user) {
return {
redirect: {
permanent: true,
@@ -34,8 +37,45 @@ export async function getServerSideProps(
},
};
}
const { req, res, resolvedUrl } = ctx;
const helpers = createServerSideHelpers({
router: appRouter,
ctx: {
req: req as any,
res: res as any,
db: null as any,
session: session,
user: user,
},
transformer: superjson,
});
return {
props: {},
};
try {
await helpers.project.all.prefetch();
const auth = await helpers.auth.get.fetch();
if (auth.rol === "user") {
const user = await helpers.user.byAuthId.fetch({
authId: auth.id,
});
if (!user.canAccessToSSHKeys) {
return {
redirect: {
permanent: true,
destination: "/",
},
};
}
}
return {
props: {
trpcState: helpers.dehydrate(),
},
};
} catch (error) {
return {
props: {},
};
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

View File

@@ -191,6 +191,8 @@ export const applicationRouter = createTRPCRouter({
await updateApplication(input.applicationId, {
buildType: input.buildType,
dockerfile: input.dockerfile,
publishDirectory: input.publishDirectory,
dockerContextPath: input.dockerContextPath,
});
return true;

View File

@@ -227,7 +227,8 @@ export const composeRouter = createTRPCRouter({
if (mounts && mounts?.length > 0) {
for (const mount of mounts) {
await createMount({
mountPath: mount.mountPath,
filePath: mount.filePath,
mountPath: "",
content: mount.content,
serviceId: compose.composeId,
serviceType: "compose",

View File

@@ -20,6 +20,7 @@ import {
} from "@/server/utils/docker/utils";
import { recreateDirectory } from "@/server/utils/filesystem/directory";
import { sendDockerCleanupNotifications } from "@/server/utils/notifications/docker-cleanup";
import { execAsync } from "@/server/utils/process/execAsync";
import { spawnAsync } from "@/server/utils/process/spawnAsync";
import {
readConfig,
@@ -49,14 +50,10 @@ import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
export const settingsRouter = createTRPCRouter({
reloadServer: adminProcedure.mutation(async () => {
await spawnAsync("docker", [
"service",
"update",
"--force",
"--image",
getDokployImage(),
"dokploy",
]);
const { stdout } = await execAsync(
"docker service inspect dokploy --format '{{.ID}}'",
);
await execAsync(`docker service update --force ${stdout.trim()}`);
return true;
}),
reloadTraefik: adminProcedure.mutation(async () => {

View File

@@ -34,21 +34,23 @@ export const sshRouter = createTRPCRouter({
});
}
}),
remove: adminProcedure.input(apiRemoveSshKey).mutation(async ({ input }) => {
try {
return await removeSSHKeyById(input.sshKeyId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this ssh key",
});
}
}),
remove: protectedProcedure
.input(apiRemoveSshKey)
.mutation(async ({ input }) => {
try {
return await removeSSHKeyById(input.sshKeyId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this ssh key",
});
}
}),
one: protectedProcedure.input(apiFindOneSshKey).query(async ({ input }) => {
const sshKey = await findSSHKeyById(input.sshKeyId);
return sshKey;
}),
all: adminProcedure.query(async () => {
all: protectedProcedure.query(async () => {
return await db.query.sshKeys.findMany({});
}),
generate: protectedProcedure
@@ -56,15 +58,17 @@ export const sshRouter = createTRPCRouter({
.mutation(async ({ input }) => {
return await generateSSHKey(input.type);
}),
update: adminProcedure.input(apiUpdateSshKey).mutation(async ({ input }) => {
try {
return await updateSSHKeyById(input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this ssh key",
cause: error,
});
}
}),
update: protectedProcedure
.input(apiUpdateSshKey)
.mutation(async ({ input }) => {
try {
return await updateSSHKeyById(input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this ssh key",
cause: error,
});
}
}),
});

View File

@@ -35,6 +35,7 @@ export const buildType = pgEnum("buildType", [
"heroku_buildpacks",
"paketo_buildpacks",
"nixpacks",
"static",
]);
// TODO: refactor this types
@@ -140,6 +141,7 @@ export const applications = pgTable("application", {
},
),
dockerfile: text("dockerfile"),
dockerContextPath: text("dockerContextPath"),
// Drop
dropBuildPath: text("dropBuildPath"),
// Docker swarm json
@@ -157,6 +159,7 @@ export const applications = pgTable("application", {
.notNull()
.default("idle"),
buildType: buildType("buildType").notNull().default("nixpacks"),
publishDirectory: text("publishDirectory"),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
@@ -315,7 +318,9 @@ const createSchema = createInsertSchema(applications, {
"heroku_buildpacks",
"paketo_buildpacks",
"nixpacks",
"static",
]),
publishDirectory: z.string().optional(),
owner: z.string(),
healthCheckSwarm: HealthCheckSwarmSchema.nullable(),
restartPolicySwarm: RestartPolicySwarmSchema.nullable(),
@@ -352,8 +357,10 @@ export const apiSaveBuildType = createSchema
applicationId: true,
buildType: true,
dockerfile: true,
dockerContextPath: true,
})
.required();
.required()
.merge(createSchema.pick({ publishDirectory: true }));
export const apiSaveGithubProvider = createSchema
.pick({

View File

@@ -28,6 +28,7 @@ export const users = pgTable("user", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
canCreateProjects: boolean("canCreateProjects").notNull().default(false),
canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
canCreateServices: boolean("canCreateServices").notNull().default(false),
canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
canDeleteServices: boolean("canDeleteServices").notNull().default(false),
@@ -107,6 +108,7 @@ export const apiAssignPermissions = createSchema
canAccessToTraefikFiles: true,
canAccessToDocker: true,
canAccessToAPI: true,
canAccessToSSHKeys: true,
})
.required();

View File

@@ -36,10 +36,9 @@ export const initializePostgres = async () => {
Ports: [
{
TargetPort: 5432,
...(process.env.NODE_ENV === "development"
? { PublishedPort: 5432 }
: {}),
PublishedPort: process.env.NODE_ENV === "development" ? 5432 : 0,
Protocol: "tcp",
PublishMode: "host",
},
],
},

View File

@@ -33,10 +33,9 @@ export const initializeRedis = async () => {
Ports: [
{
TargetPort: 6379,
...(process.env.NODE_ENV === "development"
? { PublishedPort: 6379 }
: {}),
PublishedPort: process.env.NODE_ENV === "development" ? 6379 : 0,
Protocol: "tcp",
PublishMode: "host",
},
],
},

View File

@@ -1,7 +1,10 @@
import type { WriteStream } from "node:fs";
import { prepareEnvironmentVariables } from "@/server/utils/docker/utils";
import type { ApplicationNested } from ".";
import { getBuildAppDirectory } from "../filesystem/directory";
import {
getBuildAppDirectory,
getDockerContextPath,
} from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
import { createEnvFile } from "./utils";
@@ -9,22 +12,30 @@ export const buildCustomDocker = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const { appName, env, buildArgs } = application;
const { appName, env, publishDirectory, buildArgs } = application;
const dockerFilePath = getBuildAppDirectory(application);
try {
const image = `${appName}`;
const contextPath =
const defaultContextPath =
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
const args = prepareEnvironmentVariables(buildArgs);
const dockerContextPath = getDockerContextPath(application);
const commandArgs = ["build", "-t", image, "-f", dockerFilePath, "."];
for (const arg of args) {
commandArgs.push("--build-arg", arg);
}
/*
Do not generate an environment file when publishDirectory is specified,
as it could be publicly exposed.
*/
if (!publishDirectory) {
createEnvFile(dockerFilePath, env);
}
createEnvFile(dockerFilePath, env);
await spawnAsync(
"docker",
commandArgs,
@@ -34,7 +45,7 @@ export const buildCustomDocker = async (
}
},
{
cwd: contextPath,
cwd: dockerContextPath || defaultContextPath,
},
);
} catch (error) {

View File

@@ -15,6 +15,7 @@ import { buildCustomDocker } from "./docker-file";
import { buildHeroku } from "./heroku";
import { buildNixpacks } from "./nixpacks";
import { buildPaketo } from "./paketo";
import { buildStatic } from "./static";
// NIXPACKS codeDirectory = where is the path of the code directory
// HEROKU codeDirectory = where is the path of the code directory
@@ -43,6 +44,8 @@ export const buildApplication = async (
await buildPaketo(application, writeStream);
} else if (buildType === "dockerfile") {
await buildCustomDocker(application, writeStream);
} else if (buildType === "static") {
await buildStatic(application, writeStream);
}
if (application.registryId) {
@@ -51,7 +54,11 @@ export const buildApplication = async (
await mechanizeDockerContainer(application);
writeStream.write("Docker Deployed: ✅");
} catch (error) {
writeStream.write("Error ❌");
if (error instanceof Error) {
writeStream.write(`Error ❌\n${error?.message}`);
} else {
writeStream.write("Error ❌");
}
throw error;
} finally {
writeStream.end();

View File

@@ -1,18 +1,28 @@
import type { WriteStream } from "node:fs";
import path from "node:path";
import { buildStatic } from "@/server/utils/builders/static";
import { nanoid } from "nanoid";
import type { ApplicationNested } from ".";
import { prepareEnvironmentVariables } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
import { spawnAsync } from "../process/spawnAsync";
// TODO: integrate in the vps sudo chown -R $(whoami) ~/.docker
export const buildNixpacks = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const { env, appName } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const { env, appName, publishDirectory } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const buildContainerId = `${appName}-${nanoid(10)}`;
const envVariables = prepareEnvironmentVariables(env);
const writeToStream = (data: string) => {
if (writeStream.writable) {
writeStream.write(data);
}
};
try {
const args = ["build", buildAppDirectory, "--name", appName];
@@ -20,13 +30,44 @@ export const buildNixpacks = async (
args.push("--env", env);
}
await spawnAsync("nixpacks", args, (data) => {
if (writeStream.writable) {
writeStream.write(data);
}
});
if (publishDirectory) {
/* No need for any start command, since we'll use nginx later on */
args.push("--no-error-without-start");
}
await spawnAsync("nixpacks", args, writeToStream);
/*
Run the container with the image created by nixpacks,
and copy the artifacts on the host filesystem.
Then, remove the container and create a static build.
*/
if (publishDirectory) {
await spawnAsync(
"docker",
["create", "--name", buildContainerId, appName],
writeToStream,
);
await spawnAsync(
"docker",
[
"cp",
`${buildContainerId}:/app/${publishDirectory}`,
path.join(buildAppDirectory, publishDirectory),
],
writeToStream,
);
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
await buildStatic(application, writeStream);
}
return true;
} catch (e) {
await spawnAsync("docker", ["rm", buildContainerId], writeToStream);
throw e;
}
};

View File

@@ -0,0 +1,38 @@
import type { WriteStream } from "node:fs";
import { buildCustomDocker } from "@/server/utils/builders/docker-file";
import type { ApplicationNested } from ".";
import { createFile } from "../docker/utils";
import { getBuildAppDirectory } from "../filesystem/directory";
export const buildStatic = async (
application: ApplicationNested,
writeStream: WriteStream,
) => {
const { publishDirectory } = application;
const buildAppDirectory = getBuildAppDirectory(application);
try {
createFile(
buildAppDirectory,
"Dockerfile",
[
"FROM nginx:alpine",
"WORKDIR /usr/share/nginx/html/",
`COPY ${publishDirectory || "."} .`,
].join("\n"),
);
await buildCustomDocker(
{
...application,
buildType: "dockerfile",
dockerfile: "Dockerfile",
},
writeStream,
);
return true;
} catch (e) {
throw e;
}
};

View File

@@ -242,12 +242,7 @@ export const generateConfigContainer = (application: ApplicationNested) => {
? {
RestartPolicy: restartPolicySwarm,
}
: {
// if no restartPolicySwarm provided use default
RestartPolicy: {
Condition: "on-failure",
},
}),
: {}),
...(placementSwarm
? {
Placement: placementSwarm,

View File

@@ -89,3 +89,12 @@ export const getBuildAppDirectory = (application: Application) => {
return path.join(APPLICATIONS_PATH, appName, "code", buildPath ?? "");
};
export const getDockerContextPath = (application: Application) => {
const { appName, dockerContextPath } = application;
if (!dockerContextPath) {
return null;
}
return path.join(APPLICATIONS_PATH, appName, "code", dockerContextPath);
};

View File

@@ -33,7 +33,9 @@ export const cloneGitRepository = async (
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
try {
await addHostToKnownHosts(customGitUrl);
if (!isHttpOrHttps(customGitUrl)) {
await addHostToKnownHosts(customGitUrl);
}
await recreateDirectory(outputPath);
// const command = `GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}" git clone --branch ${customGitBranch} --depth 1 ${customGitUrl} ${gitCopyPath} --progress`;
// const { stdout, stderr } = await execAsync(command);
@@ -56,6 +58,7 @@ export const cloneGitRepository = async (
customGitBranch,
"--depth",
"1",
"--recurse-submodules",
customGitUrl,
outputPath,
"--progress",
@@ -84,6 +87,11 @@ export const cloneGitRepository = async (
}
};
const isHttpOrHttps = (url: string): boolean => {
const regex = /^https?:\/\//;
return regex.test(url);
};
const addHostToKnownHosts = async (repositoryURL: string) => {
const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
@@ -121,7 +129,7 @@ const sanitizeRepoPathSSH = (input: string) => {
return {
user: found.groups?.user ?? "git",
domain: found.groups?.domain,
port: 22,
port: Number(found.groups?.port ?? 22),
owner: found.groups?.owner ?? "",
repo: found.groups?.repo,
get repoPath() {

View File

@@ -25,7 +25,7 @@ export const updateServerTraefik = (
if (admin?.certificateType === "letsencrypt") {
config.http.routers[`${appName}-router-app-secure`] = {
...currentRouterConfig,
entryPoints: ["web-secure"],
entryPoints: ["websecure"],
tls: { certResolver: "letsencrypt" },
};

View File

@@ -21,7 +21,7 @@ export function generate(schema: Schema): Template {
const mounts: Template["mounts"] = [
{
mountPath: "./config.toml",
filePath: "config.toml",
content: `[app]
address = "0.0.0.0:9000"

View File

@@ -23,7 +23,7 @@ export function generate(schema: Schema): Template {
const mounts: Template["mounts"] = [
{
mountPath: "./clickhouse/clickhouse-config.xml",
filePath: "/clickhouse/clickhouse-config.xml",
content: `
<clickhouse>
<logger>
@@ -45,7 +45,7 @@ export function generate(schema: Schema): Template {
`,
},
{
mountPath: "./clickhouse/clickhouse-user-config.xml",
filePath: "/clickhouse/clickhouse-user-config.xml",
content: `
<clickhouse>
<profiles>

View File

@@ -0,0 +1,81 @@
version: "3.9"
services:
teable:
image: ghcr.io/teableio/teable:1.3.1-alpha-build.460
restart: always
ports:
- ${TEABLE_PORT}
volumes:
- teable-data:/app/.assets
# you may use a bind-mounted host directory instead,
# so that it is harder to accidentally remove the volume and lose all your data!
# - ./docker/teable/data:/app/.assets:rw
environment:
- TZ=${TIMEZONE}
- NEXT_ENV_IMAGES_ALL_REMOTE=true
- PUBLIC_ORIGIN=${PUBLIC_ORIGIN}
- PRISMA_DATABASE_URL=${PRISMA_DATABASE_URL}
- PUBLIC_DATABASE_PROXY=${PUBLIC_DATABASE_PROXY}
- BACKEND_MAIL_HOST=${BACKEND_MAIL_HOST}
- BACKEND_MAIL_PORT=${BACKEND_MAIL_PORT}
- BACKEND_MAIL_SECURE=${BACKEND_MAIL_SECURE}
- BACKEND_MAIL_SENDER=${BACKEND_MAIL_SENDER}
- BACKEND_MAIL_SENDER_NAME=${BACKEND_MAIL_SENDER_NAME}
- BACKEND_MAIL_AUTH_USER=${BACKEND_MAIL_AUTH_USER}
- BACKEND_MAIL_AUTH_PASS=${BACKEND_MAIL_AUTH_PASS}
networks:
- dokploy-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.${HASH}.rule=Host(`${TEABLE_HOST}`)"
- "traefik.http.services.${HASH}.loadbalancer.server.port=${TEABLE_PORT}"
depends_on:
teable-db-migrate:
condition: service_completed_successfully
teable-db:
image: postgres:15.4
restart: always
ports:
- "${TEABLE_DB_PORT}:${POSTGRES_PORT}"
volumes:
- teable-db:/var/lib/postgresql/data
# you may use a bind-mounted host directory instead,
# so that it is harder to accidentally remove the volume and lose all your data!
# - ./docker/db/data:/var/lib/postgresql/data:rw
environment:
- TZ=${TIMEZONE}
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
networks:
- dokploy-network
healthcheck:
test:
[
"CMD-SHELL",
"sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'",
]
interval: 10s
timeout: 3s
retries: 3
teable-db-migrate:
image: ghcr.io/teableio/teable-db-migrate:latest
environment:
- TZ=${TIMEZONE}
- PRISMA_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
networks:
- dokploy-network
depends_on:
teable-db:
condition: service_healthy
networks:
dokploy-network:
external: true
volumes:
teable-data: {}
teable-db: {}

View File

@@ -0,0 +1,48 @@
import {
type Schema,
type Template,
generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainServiceHash = generateHash(schema.projectName);
const password = generatePassword();
const randomDomain = generateRandomDomain(schema);
const publicDbPort = ((min: number, max: number) => {
return Math.round(Math.random() * (max - min) + min);
})(32769, 65534);
const envs = [
`TEABLE_HOST=${randomDomain}`,
"TEABLE_PORT=3000",
`TEABLE_DB_PORT=${publicDbPort}`,
`HASH=${mainServiceHash}`,
"TIMEZONE=UTC",
"# Postgres",
"POSTGRES_HOST=teable-db",
"POSTGRES_PORT=5432",
"POSTGRES_DB=teable",
"POSTGRES_USER=teable",
`POSTGRES_PASSWORD=${password}`,
"# App",
"PUBLIC_ORIGIN=https://${TEABLE_HOST}",
"PRISMA_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}",
"PUBLIC_DATABASE_PROXY=${TEABLE_HOST}:${TEABLE_DB_PORT}",
"# Need to support sending emails to enable the following configurations",
"# You need to modify the configuration according to the actual situation, otherwise it will not be able to send emails correctly.",
"#BACKEND_MAIL_HOST=smtp.teable.io",
"#BACKEND_MAIL_PORT=465",
"#BACKEND_MAIL_SECURE=true",
"#BACKEND_MAIL_SENDER=noreply.teable.io",
"#BACKEND_MAIL_SENDER_NAME=Teable",
"#BACKEND_MAIL_AUTH_USER=username",
"#BACKEND_MAIL_AUTH_PASS=password",
];
return {
envs,
};
}

View File

@@ -393,4 +393,19 @@ export const templates: TemplateData[] = [
tags: ["media system"],
load: () => import("./jellyfin/index").then((m) => m.generate),
},
{
id: "teable",
name: "teable",
version: "v1.3.1-alpha-build.460",
description:
"Teable is a Super fast, Real-time, Professional, Developer friendly, No-code database built on Postgres. It uses a simple, spreadsheet-like interface to create complex enterprise-level database applications. Unlock efficient app development with no-code, free from the hurdles of data security and scalability.",
logo: "teable.png",
links: {
github: "https://github.com/teableio/teable",
website: "https://teable.io/",
docs: "https://help.teable.io/",
},
tags: ["database", "spreadsheet", "low-code", "nocode"],
load: () => import("./teable/index").then((m) => m.generate),
},
];

View File

@@ -13,7 +13,7 @@ export interface Schema {
export interface Template {
envs: string[];
mounts?: {
mountPath: string;
filePath: string;
content?: string;
}[];
}

View File

@@ -81,6 +81,7 @@ docker service create \
--network dokploy-network \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
--publish published=3000,target=3000,mode=host \
--update-parallelism 1 \
--update-order stop-first \

View File

@@ -65,6 +65,7 @@ docker service create \
--network dokploy-network \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
--publish published=3000,target=3000,mode=host \
--update-parallelism 1 \
--update-order stop-first \

View File

@@ -81,6 +81,7 @@ docker service create \
--network dokploy-network \
--mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
--mount type=bind,source=/etc/dokploy,target=/etc/dokploy \
--mount type=volume,source=dokploy-docker-config,target=/root/.docker \
--publish published=3000,target=3000,mode=host \
--update-parallelism 1 \
--update-order stop-first \

View File

@@ -20,7 +20,7 @@
"format-and-lint": "biome check .",
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
"format-and-lint:fix": "biome check . --write",
"prepare": "node .husky/install.mjs"
"prepare": "node ./.config/.husky/install.mjs"
},
"devDependencies": {
"dotenv": "16.4.5",

150
pnpm-lock.yaml generated
View File

@@ -39,17 +39,17 @@ importers:
apps/docs:
dependencies:
fumadocs-core:
specifier: 12.2.2
version: 12.2.2(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^12.5.6
version: 12.5.6(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
fumadocs-mdx:
specifier: 8.2.33
version: 8.2.33(fumadocs-core@12.2.2(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
specifier: ^8.2.34
version: 8.2.34(fumadocs-core@12.5.6(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
fumadocs-openapi:
specifier: ^3.1.3
specifier: ^3.3.0
version: 3.3.0
fumadocs-ui:
specifier: 12.2.2
version: 12.2.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.7)
specifier: ^12.5.6
version: 12.5.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.7)
lucide-react:
specifier: ^0.394.0
version: 0.394.0(react@18.3.1)
@@ -114,6 +114,9 @@ importers:
'@codemirror/legacy-modes':
specifier: 6.4.0
version: 6.4.0
'@codemirror/view':
specifier: 6.29.0
version: 6.29.0
'@dokploy/trpc-openapi':
specifier: 0.0.4
version: 0.0.4(@trpc/server@10.45.2)(zod@3.23.8)
@@ -2251,6 +2254,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-accordion@1.2.0':
resolution: {integrity: sha512-HJOzSX8dQqtsp/3jVxCU3CXEONF7/2jlGAB28oX8TTw1Dz8JYbEI1UcL8355PuLBE41/IRRMvCw7VkiK/jcUOQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-alert-dialog@1.1.1':
resolution: {integrity: sha512-wmCoJwj7byuVuiLKqDLlX7ClSUU0vd9sdCeM+2Ls+uf13+cpSJoMgwysHq1SGVVkJj5Xn0XWi1NoRCdkMpr6Mw==}
peerDependencies:
@@ -2316,6 +2332,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-collapsible@1.1.0':
resolution: {integrity: sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-collection@1.0.3':
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies:
@@ -5409,15 +5438,15 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
fumadocs-core@12.2.2:
resolution: {integrity: sha512-e8WZ/+iNUuqsxMHWYr/gBXjB5tHVJpeNfNkINyzrwDpatVQ3Ds3sGyvPn/cWkNka4ZYvMbwkjw/7n/0ijsjfRA==}
fumadocs-core@12.5.6:
resolution: {integrity: sha512-sKbVq+Yrf7uLZD/pHojLGwClRxuOyjR1NaIlm/cO9dCaEO6n568NTom/2rEn+jyhII+LbGTJqfIJmktBW6rgNA==}
peerDependencies:
next: '>= 14.1.0'
react: '>= 18'
react-dom: '>= 18'
fumadocs-mdx@8.2.33:
resolution: {integrity: sha512-bKT4CaFMWrPYUqsM2MD6uN4i2XIgZ2mCw9XoMJMpPS3Oq2Eq5MlNs9B5L6ItO7FfckCb9hoCA6LHdT1tVkgtyQ==}
fumadocs-mdx@8.2.34:
resolution: {integrity: sha512-vq7gd16z3fxMCjkjzFSTkqbQXbhZ3ucNvXAWdQxhkbhRbx3EAcmokkbcySsqVdqHPDIm0eGmCV33fQJsku1DgA==}
peerDependencies:
fumadocs-core: 12.x.x
next: '>= 14.1.0'
@@ -5425,8 +5454,8 @@ packages:
fumadocs-openapi@3.3.0:
resolution: {integrity: sha512-a6G1+FoBA4kH2HnjlgwmPpO9z4itrKPRENkVqxJPsOCwpCKdHZc6Tpwogo9CXvEifGqjAvyTbhNPy0N3+YmHLg==}
fumadocs-ui@12.2.2:
resolution: {integrity: sha512-cOUiGk/Bp8Cqd/lBx5LqjbCHO4Ny8qtFItJog6aI5POFVJDJoYuCMv65MLoS/ar7QxIdaiFSw2XYYziZQaLmrQ==}
fumadocs-ui@12.5.6:
resolution: {integrity: sha512-8kYsrSCbRuo2rS09xTAb5HejtH8QDsSlqWGC8k+D9GI4WdAW7kAQCg6k9idnhhP/nq3QOsyMtvpoUGkje91bpQ==}
peerDependencies:
next: '>= 14.1.0'
react: '>= 18'
@@ -6212,10 +6241,10 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0
lucide-react@0.395.0:
resolution: {integrity: sha512-6hzdNH5723A4FLaYZWpK50iyZH8iS2Jq5zuPRRotOFkhu6kxxJiebVdJ72tCR5XkiIeYFOU5NUawFZOac+VeYw==}
lucide-react@0.414.0:
resolution: {integrity: sha512-Krr/MHg9AWoJc52qx8hyJ64X9++JNfS1wjaJviLM1EP/68VNB7Tv0VMldLCB1aUe6Ka9QxURPhQm/eB6cqOM3A==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
luxon@3.4.4:
resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==}
@@ -10203,18 +10232,17 @@ snapshots:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-accordion@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@radix-ui/react-accordion@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-collapsible': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-collection': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
@@ -10298,17 +10326,16 @@ snapshots:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
'@radix-ui/react-collapsible@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/primitive': 1.1.0
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-presence': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
@@ -10328,19 +10355,6 @@ snapshots:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-collection@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.3
'@types/react-dom': 18.3.0
'@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0)
@@ -10529,13 +10543,6 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.3
'@radix-ui/react-direction@1.0.1(@types/react@18.3.3)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.25.0
react: 18.3.1
optionalDependencies:
'@types/react': 18.3.3
'@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -12409,7 +12416,7 @@ snapshots:
'@types/hast@3.0.4':
dependencies:
'@types/unist': 2.0.10
'@types/unist': 3.0.2
'@types/http-cache-semantics@4.0.4': {}
@@ -12429,7 +12436,7 @@ snapshots:
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 2.0.10
'@types/unist': 3.0.2
'@types/mdx@2.0.13': {}
@@ -14033,7 +14040,7 @@ snapshots:
eslint: 8.45.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1)(eslint@8.45.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0)
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.45.0)
eslint-plugin-react: 7.35.0(eslint@8.45.0)
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.45.0)
@@ -14057,7 +14064,7 @@ snapshots:
enhanced-resolve: 5.17.1
eslint: 8.45.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1)(eslint@8.45.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.5
is-core-module: 2.15.0
@@ -14079,7 +14086,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1)(eslint@8.45.0):
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
@@ -14434,7 +14441,7 @@ snapshots:
fsevents@2.3.3:
optional: true
fumadocs-core@12.2.2(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
fumadocs-core@12.5.6(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@formatjs/intl-localematcher': 0.5.4
'@shikijs/rehype': 1.12.0
@@ -14458,13 +14465,13 @@ snapshots:
- '@types/react'
- supports-color
fumadocs-mdx@8.2.33(fumadocs-core@12.2.2(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
fumadocs-mdx@8.2.34(fumadocs-core@12.5.6(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
dependencies:
'@mdx-js/mdx': 3.0.1
cross-spawn: 7.0.3
estree-util-value-to-estree: 3.1.2
fast-glob: 3.3.2
fumadocs-core: 12.2.2(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
fumadocs-core: 12.5.6(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
gray-matter: 4.0.3
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
zod: 3.23.8
@@ -14479,10 +14486,10 @@ snapshots:
json-schema-to-typescript: 14.1.0
openapi-sampler: 1.5.1
fumadocs-ui@12.2.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.7):
fumadocs-ui@12.5.6(@types/react-dom@18.3.0)(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.7):
dependencies:
'@radix-ui/react-accordion': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-accordion': 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-collapsible': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dialog': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.3.1)
'@radix-ui/react-popover': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -14491,13 +14498,14 @@ snapshots:
'@tailwindcss/typography': 0.5.13(tailwindcss@3.4.7)
class-variance-authority: 0.7.0
cmdk: 1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
fumadocs-core: 12.2.2(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
lucide-react: 0.395.0(react@18.3.1)
fumadocs-core: 12.5.6(@types/react@18.3.3)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
lucide-react: 0.414.0(react@18.3.1)
next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-medium-image-zoom: 5.2.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
swr: 2.2.5(react@18.3.1)
tailwind-merge: 2.4.0
transitivePeerDependencies:
- '@types/react'
@@ -15357,7 +15365,7 @@ snapshots:
dependencies:
react: 18.3.1
lucide-react@0.395.0(react@18.3.1):
lucide-react@0.414.0(react@18.3.1):
dependencies:
react: 18.3.1