Compare commits

...

107 Commits

Author SHA1 Message Date
Mauricio Siu
572579af91 Merge pull request #785 from Dokploy/canary
v0.13.1
2024-11-28 23:44:33 -06:00
Mauricio Siu
63998f71ec chore: bump version 2024-11-28 23:37:35 -06:00
Mauricio Siu
45fd2d149c Merge pull request #784 from Dokploy/754-dokploy-ui-duplicate-deployments
fix: add missing server flag boolean
2024-11-28 23:35:53 -06:00
Mauricio Siu
9a35c85277 refactor: upgrade biome 2024-11-28 23:29:24 -06:00
Mauricio Siu
0cf21cf3f7 refactor: add missing flag 2024-11-28 23:20:50 -06:00
Mauricio Siu
7400913646 fix: add missing server flag boolean 2024-11-28 23:19:27 -06:00
Mauricio Siu
e78d354d0d fix: add missing server flag boolean 2024-11-28 23:17:21 -06:00
Mauricio Siu
bec3ad6bb5 Merge pull request #783 from Dokploy/725-the-logo-does-not-appear-on-the-notification-email
fix: update img url
2024-11-28 22:57:10 -06:00
Mauricio Siu
5846e429e5 Merge pull request #782 from Dokploy/733-project-env-ux-improvements
refactor: apply suggested changes
2024-11-28 22:56:58 -06:00
Mauricio Siu
0f7652d02c fix: update img url 2024-11-28 22:56:31 -06:00
Mauricio Siu
fef19056fa Merge pull request #781 from Dokploy/remove-husky
Remove husky
2024-11-28 22:49:45 -06:00
Mauricio Siu
b07d9939a6 refactor: apply suggested changes 2024-11-28 22:49:35 -06:00
Mauricio Siu
301e3480e4 test: add missing prop 2024-11-28 22:33:18 -06:00
Mauricio Siu
976ac053f7 fix: linter 2024-11-28 22:32:37 -06:00
Mauricio Siu
f102bae5d5 refactor: remove biome ci 2024-11-28 22:21:31 -06:00
Mauricio Siu
00883dde11 refactor: remove husky 2024-11-28 22:14:01 -06:00
Mauricio Siu
e194f3c454 chore: add lefthook 2024-11-28 22:09:42 -06:00
Mauricio Siu
cdd39670f5 Merge pull request #780 from Dokploy/766-registry-with-ghcrio-failed-error-500
fix: add registry url and use spawnAsync
2024-11-28 22:02:58 -06:00
Mauricio Siu
88f7cf2546 fix: add registry url and use spawnAsync 2024-11-28 21:54:20 -06:00
Mauricio Siu
34ea7ad8c9 Merge pull request #779 from Dokploy/678-requests-made-to-stripe
fix: prevent to load stripe code when is not cloud
2024-11-28 20:57:20 -06:00
Mauricio Siu
081a2d8f69 fix: prevent to load stripe code when is not cloud 2024-11-28 20:56:35 -06:00
Mauricio Siu
a6368ee0b8 Merge pull request #771 from faeztgh/fa-locale
feat: add fa locale
2024-11-28 20:45:40 -06:00
Mauricio Siu
4132f714ae Merge branch 'canary' into fa-locale 2024-11-28 20:45:24 -06:00
Mauricio Siu
333776a5a1 Merge pull request #775 from 190km/i18n-french
feat(i18n): add french language support
2024-11-28 20:41:16 -06:00
Mauricio Siu
5853117e5f Merge branch 'canary' into i18n-french 2024-11-28 20:39:25 -06:00
Mauricio Siu
9e0e3540f5 Merge pull request #776 from 190km/status-tooltip-colors
fix: Add green color for done status tooltip, and lightens destructive colors
2024-11-28 20:37:05 -06:00
Mauricio Siu
7bd6e7fd9a Merge pull request #777 from 190km/delete-app-input-validation
feat: Add an input box to enter the project name for a second confirmation when deleting the application.
2024-11-28 20:36:40 -06:00
Mauricio Siu
95ab6af3ac Merge pull request #773 from edereagzi/feature/add-turkish-localization
feat(i18n): add turkish(tr) localization support
2024-11-28 20:28:03 -06:00
Mauricio Siu
69876029b1 Merge pull request #767 from chuyun/canary
feat: add optional Provider attribute to S3 Destinations
2024-11-28 20:20:57 -06:00
190km
d4fdf881cd feat: validating app name before delete 2024-11-29 02:06:25 +01:00
usopp
3b14ebcaa4 Delete et --soft HEAD~1 2024-11-28 22:14:13 +01:00
190km
22b8fa2c00 fix: added green color for done status tooltip, and lightens destructive color 2024-11-28 22:02:49 +01:00
usopp
714865730f feat(i18n): add french language support 2024-11-28 17:17:00 +01:00
Eray Dereağzı
7469c30992 feat(i18n): add turkish(tr) localization support 2024-11-28 13:38:40 +03:00
Mauricio Siu
b296b6bbf0 Merge pull request #772 from Dokploy/canary
v0.13.0
2024-11-27 23:28:40 -06:00
Mauricio Siu
37fa139a65 refactor: bump to 0.13.0 2024-11-27 23:20:32 -06:00
Mauricio Siu
a1cf597c2b Update package.json 2024-11-27 23:15:19 -06:00
F43Z
c8e9d9d169 feat: add fa locale 2024-11-27 23:40:42 +03:30
Mauricio Siu
d01928a878 refactor: add required 2024-11-27 02:38:54 -06:00
Mauricio Siu
30c19c5698 chore: update templates 2024-11-27 02:37:02 -06:00
Mauricio Siu
01dfa7feaf Merge pull request #768 from DanielGietmann/canary
feat: photoprism template
2024-11-26 22:30:27 -06:00
Mauricio Siu
58e6462ff1 Merge branch 'canary' into canary 2024-11-26 22:30:17 -06:00
Mauricio Siu
d18876d4fb Merge pull request #760 from henriklovhaug/canary
feat: add ontime template
2024-11-26 22:26:04 -06:00
Mauricio Siu
492c912c61 Update apps/dokploy/templates/ontime/docker-compose.yml 2024-11-26 22:25:58 -06:00
Mauricio Siu
6a283c8ee2 Merge pull request #759 from sao-coding/sao-coding/i18n-zh-Hant-TW
feat(i18n): add Traditional Chinese language support
2024-11-26 22:22:13 -06:00
Mauricio Siu
59dfdd6192 Merge pull request #761 from DerKorb/patch-1
fix compose.create not returning result
2024-11-26 22:21:58 -06:00
Mauricio Siu
3c072d7aa8 Merge pull request #746 from DrMxrcy/refactor/multiple-template-names
fix(templates): Multiple Templates Naming Schema
2024-11-26 22:21:47 -06:00
Daniel Gietmann
19d897f3ad added template 2024-11-27 03:44:18 +01:00
chuyun
0477329db7 feat: add optional Provider attribute to S3 Destinations 2024-11-27 02:14:45 +08:00
Mauricio Siu
fabe946526 Merge pull request #765 from airyland/patch-1
fix: improve English grammar in version check notice
2024-11-25 22:52:26 -06:00
Airyland
daa0c9d5d4 fix: improve English grammar in version check notice 2024-11-26 11:57:38 +08:00
ksollner
afe9b3c113 fix compose.create not returning result
when calling the compose create api, a empty result was returned
2024-11-25 14:42:05 +01:00
Henrik Tøn Løvhaug
cbfd09786a feat: add ontime template 2024-11-25 13:07:20 +01:00
sao-coding
54eb5544ac feat(i18n): add Traditional Chinese language support 2024-11-25 02:35:24 +00:00
Mauricio Siu
ac33b6b6a1 Merge pull request #739 from DrMxrcy/template/discourse
feat(add): Discourse
2024-11-22 00:46:14 -06:00
Mauricio Siu
653b1972ca Merge branch 'canary' into template/discourse 2024-11-22 00:46:01 -06:00
Mauricio Siu
7d7eb6a7a2 Merge pull request #743 from jujur10/canary
fix: stirling docker compose
2024-11-22 00:38:19 -06:00
Mauricio Siu
fab7e138b7 Merge pull request #744 from DrMxrcy/template/immich
feat(add): Immich
2024-11-22 00:36:17 -06:00
Mauricio Siu
62b635b2f0 Merge branch 'canary' into template/immich 2024-11-22 00:36:09 -06:00
Mauricio Siu
2dd352ee76 Merge pull request #740 from DrMxrcy/template/twenty
feat(add): Twenty CRM
2024-11-22 00:31:34 -06:00
Mauricio Siu
422b6eea82 Merge branch 'canary' into template/twenty 2024-11-22 00:31:25 -06:00
Mauricio Siu
4850305fb6 Merge pull request #736 from DrMxrcy/feat/YOURLS
feat(add): YOURLS Template
2024-11-22 00:25:02 -06:00
Mauricio Siu
97779f5686 Merge branch 'canary' into feat/YOURLS 2024-11-22 00:24:53 -06:00
DrMxrcy
d4b8985d71 fix(template): Twenty DB 2024-11-21 10:18:38 -05:00
Julien ROIRON
d5686063e0 fix: stirling docker compose 2024-11-21 08:16:09 +01:00
DrMxrcy
62ca8eec53 fix(templates): DiscordTickets Naming 2024-11-21 01:57:27 -05:00
DrMxrcy
204143648d fix(template): Slash Naming Schema 2024-11-21 01:56:22 -05:00
DrMxrcy
be8bd78bcc fix(template): Postiz Template 2024-11-21 01:55:40 -05:00
DrMxrcy
9003e43702 fix(template): InvoiceShelf Naming 2024-11-21 01:54:15 -05:00
DrMxrcy
55ac24ee8e fix(template): Windmill Naming 2024-11-21 01:53:36 -05:00
DrMxrcy
f3be56234b fix(template): Peppermint Naming 2024-11-21 01:52:39 -05:00
DrMxrcy
fd59beaff1 fix(add): ENV 2024-11-21 01:48:49 -05:00
Mauricio Siu
4a70d60aed Merge pull request #735 from mezotv/i18n-german
feat(i18n): add german language support
2024-11-21 00:31:45 -06:00
Mauricio Siu
f7533c88f6 Merge pull request #737 from DrMxrcy/template/ryot
feat(add): Ryot Template
2024-11-21 00:01:50 -06:00
DrMxrcy
ea8cae7815 feat(add): Immich 2024-11-20 22:20:58 -05:00
DrMxrcy
e9956a66da fix(add): template.ts 2024-11-20 11:18:33 -05:00
DrMxrcy
2eeb4017ac feat(add): Twenty CRM 2024-11-20 11:12:31 -05:00
DrMxrcy
28f0c9f162 feat(add): Discourse 2024-11-20 10:43:11 -05:00
DrMxrcy
4967d3bb31 feat(add): Ryot Logo 2024-11-20 10:09:05 -05:00
DrMxrcy
238fa5d02d feat(add): Ryot 2024-11-20 10:08:05 -05:00
DrMxrcy
d1436c992e feat(fix): Define Version 2024-11-20 09:56:40 -05:00
DrMxrcy
0654804821 feat(fix): YOURLS lint 2024-11-20 09:52:35 -05:00
DrMxrcy
c0876044b0 feat(add): YOURLS Template 2024-11-20 09:43:32 -05:00
Dominik Koch
6dff11af22 fix: formatting again 2024-11-20 11:35:57 +00:00
Dominik Koch
6d674a4c6b fix: code format 2024-11-20 11:34:13 +00:00
Dominik Koch
96b2579d69 feat(i18n): add german language support 2024-11-20 11:30:59 +00:00
Mauricio Siu
0708fa05b6 Merge pull request #721 from PaiJi/feat/add-gravatar-support
feat(Profile): support use Gravatar as avatar
2024-11-19 20:29:18 -06:00
Mauricio Siu
597842a99f Merge pull request #724 from iMuFeng/canary
feat: add HeyForm template
2024-11-19 20:26:47 -06:00
Mauricio Siu
105cf1014f Update templates.ts 2024-11-19 20:26:26 -06:00
Mauricio Siu
b93f36ae77 Merge branch 'canary' into canary 2024-11-19 20:24:40 -06:00
Mauricio Siu
bebb4b973c Update apps/dokploy/templates/heyform/index.ts 2024-11-19 20:23:43 -06:00
Mauricio Siu
f790530d4d Update apps/dokploy/templates/heyform/docker-compose.yml 2024-11-19 20:23:39 -06:00
Mauricio Siu
b7374549b8 Merge pull request #731 from DrMxrcy/feat/chatwoot-template
Add: Chatwoot Template
2024-11-19 20:19:11 -06:00
Mauricio Siu
366e881d72 Merge pull request #723 from WoWnik/canary
feat: add russian translation init
2024-11-19 20:12:14 -06:00
WoWnik
c2125d82b1 refactor: remove "ё" letter 2024-11-20 00:00:34 +03:00
DrMxrcy
32b3a76457 fix: Lint Issues 2024-11-19 12:30:36 -05:00
DrMxrcy
5cbdc8fad9 Fix: Linting issues 2024-11-19 11:56:23 -05:00
DrMxrcy
3698e8a827 Fix Chatwoot Schema 2024-11-19 11:19:33 -05:00
WoWnik
a83b62f62b refactor: sort alphabetically 2024-11-19 18:53:52 +03:00
DrMxrcy
ac033cea22 Add: Chatwoot 2024-11-19 03:06:54 -05:00
Mauricio Siu
58814239d9 Merge pull request #722 from henriklovhaug/canary
fix: postiz template pointing to wrong TLD
2024-11-18 08:57:54 -06:00
JiPai
6fc1ce2fbc chore(README): fix broken video thumbnail 2024-11-18 22:25:56 +08:00
mufeng
adde8126ab feat: add HeyForm template 2024-11-18 17:56:02 +08:00
WoWnik
cda66606ec feat: add russian translation init 2024-11-18 11:48:38 +03:00
Henrik Tøn Løvhaug
28f2c1a3c0 fix: template pointing to wrong TLD 2024-11-18 09:03:48 +01:00
JiPai
1c5fe8a283 feat(Profile): support use Gravatar as avatar 2024-11-18 14:09:42 +08:00
Mauricio Siu
da005bc511 chore: add startupfa.me sponsor 2024-11-17 22:25:03 -06:00
135 changed files with 11230 additions and 892 deletions

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm commitlint --edit $1

View File

@@ -1,6 +0,0 @@
// Skip Husky install in production and CI
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
process.exit(0);
}
const husky = (await import("husky")).default;
console.log(husky());

View File

@@ -1 +0,0 @@
pnpm lint-staged

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Create a bug report
labels: ['bug']
labels: ["bug"]
body:
- type: markdown
attributes:
@@ -11,18 +11,27 @@ body:
- type: textarea
attributes:
label: To Reproduce
description: A step-by-step description of how to reproduce the issue, or a link to the reproducible repository.
description: |
A detailed, step-by-step description of how to reproduce the issue is required.
Please ensure your report includes clear instructions using numbered lists.
If possible, provide a link to a repository or project where the issue can be reproduced.
placeholder: |
1. Create a application
2. Click X
3. Y will happen
Make sure to:
- Use numbered lists to outline steps clearly.
- Include all relevant commands and configurations.
- Provide a link to a reproducible repository if applicable.
validations:
required: true
- type: textarea
attributes:
label: Current vs. Expected behavior
description: A clear and concise description of what the bug is, and what you expected to happen.
placeholder: 'Following the steps from the previous section, I expected A to happen, but I observed B instead'
placeholder: "Following the steps from the previous section, I expected A to happen, but I observed B instead"
validations:
required: true
- type: textarea
@@ -45,12 +54,23 @@ body:
label: Which area(s) are affected? (Select all that apply)
multiple: true
options:
- 'Installation'
- 'Application'
- 'Databases'
- 'Docker Compose'
- 'Traefik'
- 'Docker'
- "Installation"
- "Application"
- "Databases"
- "Docker Compose"
- "Traefik"
- "Docker"
- "Remote server"
- "Local Development"
validations:
required: true
- type: dropdown
attributes:
label: Are you deploying the applications where Dokploy is installed or on a remote server?
options:
- "Same server where Dokploy is installed"
- "Remote server"
- "Both"
validations:
required: true
- type: textarea
@@ -59,4 +79,16 @@ body:
description: |
Any extra information that might help us investigate.
placeholder: |
I tested on a DigitalOcean VPS with Ubuntu 20.04 and Docker version 20.10.12.
I tested on a DigitalOcean VPS with Ubuntu 20.04 and Docker version 20.10.12.
- type: dropdown
attributes:
label: Will you send a PR to fix it?
description: Let us know if you are planning to submit a pull request to address this issue.
options:
- "Yes"
- "No"
- "Maybe, need help"
validations:
required: true

View File

@@ -1,6 +1,6 @@
name: Feature Request
description: Suggest a new feature or improvement to the project
labels: ['enhancement']
labels: ["enhancement"]
body:
- type: textarea
attributes:
@@ -30,4 +30,15 @@ body:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false
required: false
- type: dropdown
attributes:
label: Will you send a PR to implement it?
description: Let us know if you are planning to submit a pull request to implement this feature.
options:
- "Yes"
- "No"
- "Maybe, need help"
validations:
required: true

BIN
.github/sponsors/startupfame.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -4,9 +4,6 @@ on:
pull_request:
branches: [main, canary]
env:
HUSKY: 0
jobs:
lint-and-typecheck:
runs-on: ubuntu-latest
@@ -18,8 +15,7 @@ jobs:
node-version: 18.18.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm biome ci
- run: pnpm run server:build
- run: pnpm typecheck
build-and-test:
@@ -46,5 +42,5 @@ jobs:
node-version: 18.18.0
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm run server:build
- run: pnpm run server:build
- run: pnpm test

View File

@@ -1 +0,0 @@
npx commitlint --edit "$1"

View File

@@ -1,6 +0,0 @@
// Skip Husky install in production and CI
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
process.exit(0);
}
const husky = (await import("husky")).default;
console.log(husky());

View File

@@ -1,2 +0,0 @@
pnpm run check
git add .

View File

@@ -87,7 +87,8 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Lightspeed.run"/></a>
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
<a href="https://startupfa.me/?ref=dokploy "><img src=".github/sponsors/startupfame.png" width="65px" alt="Startupfame"/></a>
</div>
### Community Backers 🤝
@@ -116,7 +117,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
## Video Tutorial
<a href="https://youtu.be/mznYKPvhcfw">
<img src="https://dokploy.com/banner.webp" alt="Watch the video" width="400" style="border-radius:20px;"/>
<img src="https://dokploy.com/banner.png" alt="Watch the video" width="400" style="border-radius:20px;"/>
</a>
<!-- ## Supported OS

View File

@@ -30,6 +30,7 @@ const baseApp: ApplicationNested = {
appName: "",
autoDeploy: true,
serverId: "",
registryUrl: "",
branch: null,
dockerBuildStage: "",
project: {

View File

@@ -12,6 +12,7 @@ const baseApp: ApplicationNested = {
serverId: "",
branch: null,
dockerBuildStage: "",
registryUrl: "",
buildArgs: null,
project: {
env: "",

View File

@@ -1,63 +1,143 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteApplicationSchema = z.object({
projectName: z.string().min(1, {
message: "Application name is required",
}),
});
type DeleteApplication = z.infer<typeof deleteApplicationSchema>;
interface Props {
applicationId: string;
}
export const DeleteApplication = ({ applicationId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.application.delete.useMutation();
const { data } = api.application.one.useQuery(
{ applicationId },
{ enabled: !!applicationId },
);
const { push } = useRouter();
const form = useForm<DeleteApplication>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteApplicationSchema),
});
const onSubmit = async (formData: DeleteApplication) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({
applicationId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Application deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the application");
});
} else {
form.setError("projectName", {
message: "Project name does not match",
});
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
application
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
applicationId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Application delete succesfully");
})
.catch(() => {
toast.error("Error to delete Application");
});
application. If you are sure please enter the application name to
delete this application.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-application"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter application name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-application"
type="submit"
variant="destructive"
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -202,7 +202,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
@@ -281,7 +280,6 @@ export const SaveBitbucketProvider = ({ applicationId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -21,6 +21,7 @@ const DockerProviderSchema = z.object({
}),
username: z.string().optional(),
password: z.string().optional(),
registryURL: z.string().optional(),
});
type DockerProvider = z.infer<typeof DockerProviderSchema>;
@@ -33,12 +34,12 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
const { data, refetch } = api.application.one.useQuery({ applicationId });
const { mutateAsync } = api.application.saveDockerProvider.useMutation();
const form = useForm<DockerProvider>({
defaultValues: {
dockerImage: "",
password: "",
username: "",
registryURL: "",
},
resolver: zodResolver(DockerProviderSchema),
});
@@ -49,6 +50,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
dockerImage: data.dockerImage || "",
password: data.password || "",
username: data.username || "",
registryURL: data.registryUrl || "",
});
}
}, [form.reset, data, form]);
@@ -59,6 +61,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
password: values.password || null,
applicationId,
username: values.username || null,
registryUrl: values.registryURL || null,
})
.then(async () => {
toast.success("Docker Provider Saved");
@@ -76,7 +79,7 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
className="flex flex-col gap-4"
>
<div className="grid md:grid-cols-2 gap-4 ">
<div className="md:col-span-2 space-y-4">
<div className="space-y-4">
<FormField
control={form.control}
name="dockerImage"
@@ -91,6 +94,19 @@ export const SaveDockerProvider = ({ applicationId }: Props) => {
)}
/>
</div>
<FormField
control={form.control}
name="registryURL"
render={({ field }) => (
<FormItem>
<FormLabel>Registry URL</FormLabel>
<FormControl>
<Input placeholder="Registry URL" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="space-y-4">
<FormField
control={form.control}

View File

@@ -193,7 +193,6 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
@@ -272,7 +271,6 @@ export const SaveGithubProvider = ({ applicationId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -209,7 +209,6 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
@@ -297,7 +296,6 @@ export const SaveGitlabProvider = ({ applicationId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -1,63 +1,141 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteComposeSchema = z.object({
projectName: z.string().min(1, {
message: "Compose name is required",
}),
});
type DeleteCompose = z.infer<typeof deleteComposeSchema>;
interface Props {
composeId: string;
}
export const DeleteCompose = ({ composeId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.compose.delete.useMutation();
const { data } = api.compose.one.useQuery(
{ composeId },
{ enabled: !!composeId },
);
const { push } = useRouter();
const form = useForm<DeleteCompose>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteComposeSchema),
});
const onSubmit = async (formData: DeleteCompose) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ composeId })
.then((result) => {
push(`/dashboard/project/${result?.projectId}`);
toast.success("Compose deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the compose");
});
} else {
form.setError("projectName", {
message: `Project name must match "${expectedName}"`,
});
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
compose and all its services.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
composeId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Compose delete succesfully");
})
.catch(() => {
toast.error("Error to delete the compose");
});
compose. If you are sure please enter the compose name to delete
this compose.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-compose"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>{" "}
<FormControl>
<Input
placeholder="Enter compose name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-compose"
type="submit"
variant="destructive"
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -204,7 +204,6 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
@@ -283,7 +282,6 @@ export const SaveBitbucketProviderCompose = ({ composeId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -195,7 +195,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
@@ -274,7 +273,6 @@ export const SaveGithubProviderCompose = ({ composeId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -211,7 +211,6 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",
@@ -299,7 +298,6 @@ export const SaveGitlabProviderCompose = ({ composeId }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
" w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -160,7 +160,6 @@ export const AddBackup = ({ databaseId, databaseType, refetch }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -144,7 +144,6 @@ export const UpdateBackup = ({ backupId, refetch }: Props) => {
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-full justify-between !bg-input",
!field.value && "text-muted-foreground",

View File

@@ -1,62 +1,140 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteMariadbSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteMariadb = z.infer<typeof deleteMariadbSchema>;
interface Props {
mariadbId: string;
}
export const DeleteMariadb = ({ mariadbId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.mariadb.remove.useMutation();
const { data } = api.mariadb.one.useQuery(
{ mariadbId },
{ enabled: !!mariadbId },
);
const { push } = useRouter();
const form = useForm<DeleteMariadb>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteMariadbSchema),
});
const onSubmit = async (formData: DeleteMariadb) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ mariadbId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
mariadbId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully");
})
.catch(() => {
toast.error("Error to delete the database");
});
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-mariadb"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-mariadb"
type="submit"
variant="destructive"
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,62 +1,139 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteMongoSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteMongo = z.infer<typeof deleteMongoSchema>;
interface Props {
mongoId: string;
}
// commen
export const DeleteMongo = ({ mongoId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.mongo.remove.useMutation();
const { data } = api.mongo.one.useQuery({ mongoId }, { enabled: !!mongoId });
const { push } = useRouter();
const form = useForm<DeleteMongo>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteMongoSchema),
});
const onSubmit = async (formData: DeleteMongo) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ mongoId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
mongoId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully");
})
.catch(() => {
toast.error("Error to delete the database");
});
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-mongo"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-mongo"
type="submit"
variant="destructive"
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -1,62 +1,138 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteMysqlSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteMysql = z.infer<typeof deleteMysqlSchema>;
interface Props {
mysqlId: string;
}
export const DeleteMysql = ({ mysqlId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.mysql.remove.useMutation();
const { data } = api.mysql.one.useQuery({ mysqlId }, { enabled: !!mysqlId });
const { push } = useRouter();
const form = useForm<DeleteMysql>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteMysqlSchema),
});
const onSubmit = async (formData: DeleteMysql) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ mysqlId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
mysqlId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully");
})
.catch(() => {
toast.error("Error to delete the database");
});
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-mysql"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-mysql"
type="submit"
variant="destructive"
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -86,14 +86,12 @@ export const ShowVolumes = ({ mysqlId }: Props) => {
)}
{mount.type === "file" && (
<>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground">
{mount.content}
</span>
</div>
</>
<div className="flex flex-col gap-1">
<span className="font-medium">Content</span>
<span className="text-sm text-muted-foreground">
{mount.content}
</span>
</div>
)}
{mount.type === "bind" && (
<div className="flex flex-col gap-1">

View File

@@ -1,62 +1,141 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deletePostgresSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeletePostgres = z.infer<typeof deletePostgresSchema>;
interface Props {
postgresId: string;
}
export const DeletePostgres = ({ postgresId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.postgres.remove.useMutation();
const { data } = api.postgres.one.useQuery(
{ postgresId },
{ enabled: !!postgresId },
);
const { push } = useRouter();
const form = useForm<DeletePostgres>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deletePostgresSchema),
});
const onSubmit = async (formData: DeletePostgres) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ postgresId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
postgresId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully");
})
.catch(() => {
toast.error("Error to delete the database");
});
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-postgres"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-postgres"
type="submit"
variant="destructive"
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -127,7 +127,6 @@ export const AddTemplate = ({ projectId }: Props) => {
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className={cn(
"md:max-w-[15rem] w-full justify-between !bg-input",
)}

View File

@@ -19,11 +19,9 @@ import {
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, FileIcon, SquarePen } from "lucide-react";
import { FileIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@@ -37,9 +35,10 @@ type UpdateProject = z.infer<typeof updateProjectSchema>;
interface Props {
projectId: string;
children?: React.ReactNode;
}
export const AddEnv = ({ projectId }: Props) => {
export const ProjectEnviroment = ({ projectId, children }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const utils = api.useUtils();
const { mutateAsync, error, isError, isLoading } =
@@ -53,7 +52,6 @@ export const AddEnv = ({ projectId }: Props) => {
},
);
console.log(data);
const form = useForm<UpdateProject>({
defaultValues: {
env: data?.env ?? "",
@@ -86,34 +84,29 @@ export const AddEnv = ({ projectId }: Props) => {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<DropdownMenuItem
className="w-full cursor-pointer space-x-3"
onSelect={(e) => e.preventDefault()}
>
<FileIcon className="size-4" />
<span>Add Env</span>
</DropdownMenuItem>
{children ?? (
<DropdownMenuItem
className="w-full cursor-pointer space-x-3"
onSelect={(e) => e.preventDefault()}
>
<FileIcon className="size-4" />
<span>Project Enviroment</span>
</DropdownMenuItem>
)}
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-6xl">
<DialogHeader>
<DialogTitle>Modify Shared Env</DialogTitle>
<DialogDescription>Update the env variables</DialogDescription>
<DialogTitle>Project Enviroment</DialogTitle>
<DialogDescription>
Update the env Environment variables that are accessible to all
services of this project. Use this syntax to reference project-level
variables in your service environments:
</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<AlertBlock type="info">
To use a shared env, in one of your services, you need to use like
this: Let's say you have a shared env ENVIROMENT="development" and you
want to use it in your service, you need to use like this:
<ul>
<li>
<code>ENVIRONMENT=${"{{project.ENVIRONMENT}}"}</code>
</li>
<li>
<code>DATABASE_URL=${"{{project.DATABASE_URL}}"}</code>
</li>
</ul>{" "}
This allows the service to inherit and use the shared variables from
the project level, ensuring consistency across services.
Use this syntax to reference project-level variables in your service
environments: <code>DATABASE_URL=${"{{project.DATABASE_URL}}"}</code>
</AlertBlock>
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@@ -25,7 +25,6 @@ import { api } from "@/utils/api";
import {
AlertTriangle,
BookIcon,
CircuitBoard,
ExternalLink,
ExternalLinkIcon,
FolderInput,
@@ -35,7 +34,7 @@ import {
import Link from "next/link";
import { Fragment } from "react";
import { toast } from "sonner";
import { AddEnv } from "./add-env";
import { ProjectEnviroment } from "./project-enviroment";
import { UpdateProject } from "./update";
export const ShowProjects = () => {
@@ -192,7 +191,9 @@ export const ShowProjects = () => {
Actions
</DropdownMenuLabel>
<div onClick={(e) => e.stopPropagation()}>
<AddEnv projectId={project.projectId} />
<ProjectEnviroment
projectId={project.projectId}
/>
</div>
<div onClick={(e) => e.stopPropagation()}>
<UpdateProject projectId={project.projectId} />

View File

@@ -1,62 +1,138 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const deleteRedisSchema = z.object({
projectName: z.string().min(1, {
message: "Database name is required",
}),
});
type DeleteRedis = z.infer<typeof deleteRedisSchema>;
interface Props {
redisId: string;
}
export const DeleteRedis = ({ redisId }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.redis.remove.useMutation();
const { data } = api.redis.one.useQuery({ redisId }, { enabled: !!redisId });
const { push } = useRouter();
const form = useForm<DeleteRedis>({
defaultValues: {
projectName: "",
},
resolver: zodResolver(deleteRedisSchema),
});
const onSubmit = async (formData: DeleteRedis) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
await mutateAsync({ redisId })
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the database");
});
} else {
form.setError("projectName", {
message: "Database name does not match",
});
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button variant="ghost" isLoading={isLoading}>
<TrashIcon className="size-4 text-muted-foreground " />
<TrashIcon className="size-4 text-muted-foreground" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
database
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await mutateAsync({
redisId,
})
.then((data) => {
push(`/dashboard/project/${data?.projectId}`);
toast.success("Database delete succesfully");
})
.catch(() => {
toast.error("Error to delete the database");
});
database. If you are sure please enter the database name to delete
this database.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
id="hook-form-delete-redis"
className="grid w-full gap-4"
>
<FormField
control={form.control}
name="projectName"
render={({ field }) => (
<FormItem>
<FormLabel>
To confirm, type "{data?.name}/{data?.appName}" in the box
below
</FormLabel>
<FormControl>
<Input
placeholder="Enter database name to confirm"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter>
<Button
variant="secondary"
onClick={() => {
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
isLoading={isLoading}
form="hook-form-delete-redis"
type="submit"
variant="destructive"
>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -37,9 +37,12 @@ const appearanceFormSchema = z.object({
theme: z.enum(["light", "dark", "system"], {
required_error: "Please select a theme.",
}),
language: z.enum(["en", "pl", "zh-Hans"], {
required_error: "Please select a language.",
}),
language: z.enum(
["en", "pl", "ru", "fr", "de", "tr", "zh-Hant", "zh-Hans", "fa"],
{
required_error: "Please select a language.",
},
),
});
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
@@ -175,7 +178,16 @@ export function AppearanceForm() {
{[
{ label: "English", value: "en" },
{ label: "Polski", value: "pl" },
{ label: "Русский", value: "ru" },
{ label: "Français", value: "fr" },
{ label: "Deutsch", value: "de" },
{ label: "繁體中文", value: "zh-Hant" },
{ label: "简体中文", value: "zh-Hans" },
{ label: "Türkçe", value: "tr" },
{
label: "Persian",
value: "fa",
},
].map((preset) => (
<SelectItem key={preset.label} value={preset.value}>
{preset.label}

View File

@@ -89,18 +89,14 @@ export const ShowBilling = () => {
<div className="pb-5">
<Progress value={safePercentage} className="max-w-lg" />
</div>
{admin && (
<>
{admin.serversQuantity! <= servers?.length! && (
<div className="flex flex-row gap-4 p-2 bg-yellow-50 dark:bg-yellow-950 rounded-lg items-center">
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
<span className="text-sm text-yellow-600 dark:text-yellow-400">
You have reached the maximum number of servers you can
create, please upgrade your plan to add more servers.
</span>
</div>
)}
</>
{admin && admin.serversQuantity! <= servers?.length! && (
<div className="flex flex-row gap-4 p-2 bg-yellow-50 dark:bg-yellow-950 rounded-lg items-center">
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
<span className="text-sm text-yellow-600 dark:text-yellow-400">
You have reached the maximum number of servers you can create,
please upgrade your plan to add more servers.
</span>
</div>
)}
</div>
)}
@@ -188,7 +184,6 @@ export const ShowBilling = () => {
</p>
<ul
role="list"
className={clsx(
" mt-4 flex flex-col gap-y-2 text-sm",
featured ? "text-white" : "text-slate-200",

View File

@@ -28,11 +28,7 @@ export const ShowRegistry = () => {
</div>
<div className="flex flex-row gap-2">
{data && data?.length > 0 && (
<>
<AddRegistry />
</>
)}
{data && data?.length > 0 && <AddRegistry />}
</div>
</CardHeader>
<CardContent className="space-y-2 pt-4 h-full">

View File

@@ -34,9 +34,11 @@ import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { S3_PROVIDERS } from "./constants";
const addDestination = z.object({
name: z.string().min(1, "Name is required"),
provider: z.string().optional(),
accessKeyId: z.string(),
secretAccessKey: z.string(),
bucket: z.string(),
@@ -58,6 +60,7 @@ export const AddDestination = () => {
api.destination.testConnection.useMutation();
const form = useForm<AddDestination>({
defaultValues: {
provider: "",
accessKeyId: "",
bucket: "",
name: "",
@@ -73,6 +76,7 @@ export const AddDestination = () => {
const onSubmit = async (data: AddDestination) => {
await mutateAsync({
provider: data.provider || "",
accessKey: data.accessKeyId,
bucket: data.bucket,
endpoint: data.endpoint,
@@ -123,6 +127,40 @@ export const AddDestination = () => {
);
}}
/>
<FormField
control={form.control}
name="provider"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Provider</FormLabel>
<FormControl>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a S3 Provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
{S3_PROVIDERS.map((s3Provider) => (
<SelectItem
key={s3Provider.key}
value={s3Provider.key}
>
{s3Provider.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
@@ -255,6 +293,7 @@ export const AddDestination = () => {
isLoading={isLoading}
onClick={async () => {
await testConnection({
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),
@@ -283,6 +322,7 @@ export const AddDestination = () => {
variant="secondary"
onClick={async () => {
await testConnection({
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),

View File

@@ -0,0 +1,133 @@
export const S3_PROVIDERS: Array<{
key: string;
name: string;
}> = [
{
key: "AWS",
name: "Amazon Web Services (AWS) S3",
},
{
key: "Alibaba",
name: "Alibaba Cloud Object Storage System (OSS) formerly Aliyun",
},
{
key: "ArvanCloud",
name: "Arvan Cloud Object Storage (AOS)",
},
{
key: "Ceph",
name: "Ceph Object Storage",
},
{
key: "ChinaMobile",
name: "China Mobile Ecloud Elastic Object Storage (EOS)",
},
{
key: "Cloudflare",
name: "Cloudflare R2 Storage",
},
{
key: "DigitalOcean",
name: "DigitalOcean Spaces",
},
{
key: "Dreamhost",
name: "Dreamhost DreamObjects",
},
{
key: "GCS",
name: "Google Cloud Storage",
},
{
key: "HuaweiOBS",
name: "Huawei Object Storage Service",
},
{
key: "IBMCOS",
name: "IBM COS S3",
},
{
key: "IDrive",
name: "IDrive e2",
},
{
key: "IONOS",
name: "IONOS Cloud",
},
{
key: "LyveCloud",
name: "Seagate Lyve Cloud",
},
{
key: "Leviia",
name: "Leviia Object Storage",
},
{
key: "Liara",
name: "Liara Object Storage",
},
{
key: "Linode",
name: "Linode Object Storage",
},
{
key: "Magalu",
name: "Magalu Object Storage",
},
{
key: "Minio",
name: "Minio Object Storage",
},
{
key: "Netease",
name: "Netease Object Storage (NOS)",
},
{
key: "Petabox",
name: "Petabox Object Storage",
},
{
key: "RackCorp",
name: "RackCorp Object Storage",
},
{
key: "Rclone",
name: "Rclone S3 Server",
},
{
key: "Scaleway",
name: "Scaleway Object Storage",
},
{
key: "SeaweedFS",
name: "SeaweedFS S3",
},
{
key: "StackPath",
name: "StackPath Object Storage",
},
{
key: "Storj",
name: "Storj (S3 Compatible Gateway)",
},
{
key: "Synology",
name: "Synology C2 Object Storage",
},
{
key: "TencentCOS",
name: "Tencent Cloud Object Storage (COS)",
},
{
key: "Wasabi",
name: "Wasabi Object Storage",
},
{
key: "Qiniu",
name: "Qiniu Object Storage (Kodo)",
},
{
key: "Other",
name: "Any other S3 compatible provider",
},
];

View File

@@ -35,9 +35,11 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
import { S3_PROVIDERS } from "./constants";
const updateDestination = z.object({
name: z.string().min(1, "Name is required"),
provider: z.string().optional(),
accessKeyId: z.string(),
secretAccessKey: z.string(),
bucket: z.string(),
@@ -70,6 +72,7 @@ export const UpdateDestination = ({ destinationId }: Props) => {
api.destination.testConnection.useMutation();
const form = useForm<UpdateDestination>({
defaultValues: {
provider: "",
accessKeyId: "",
bucket: "",
name: "",
@@ -152,6 +155,40 @@ export const UpdateDestination = ({ destinationId }: Props) => {
);
}}
/>
<FormField
control={form.control}
name="provider"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Provider</FormLabel>
<FormControl>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a S3 Provider" />
</SelectTrigger>
</FormControl>
<SelectContent>
{S3_PROVIDERS.map((s3Provider) => (
<SelectItem
key={s3Provider.key}
value={s3Provider.key}
>
{s3Provider.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
@@ -285,6 +322,7 @@ export const UpdateDestination = ({ destinationId }: Props) => {
variant={"secondary"}
onClick={async () => {
await testConnection({
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),
@@ -311,6 +349,7 @@ export const UpdateDestination = ({ destinationId }: Props) => {
variant="secondary"
onClick={async () => {
await testConnection({
provider: form.getValues("provider") || "",
accessKey: form.getValues("accessKeyId"),
bucket: form.getValues("bucket"),
endpoint: form.getValues("endpoint"),

View File

@@ -397,25 +397,23 @@ export const AddNotification = () => {
)}
{type === "discord" && (
<>
<FormField
control={form.control}
name="webhookUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook URL</FormLabel>
<FormControl>
<Input
placeholder="https://discord.com/api/webhooks/123456789/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
{...field}
/>
</FormControl>
<FormField
control={form.control}
name="webhookUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook URL</FormLabel>
<FormControl>
<Input
placeholder="https://discord.com/api/webhooks/123456789/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
<FormMessage />
</FormItem>
)}
/>
)}
{type === "email" && (

View File

@@ -356,25 +356,23 @@ export const UpdateNotification = ({ notificationId }: Props) => {
)}
{type === "discord" && (
<>
<FormField
control={form.control}
name="webhookUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook URL</FormLabel>
<FormControl>
<Input
placeholder="https://discord.com/api/webhooks/123456789/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
{...field}
/>
</FormControl>
<FormField
control={form.control}
name="webhookUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook URL</FormLabel>
<FormControl>
<Input
placeholder="https://discord.com/api/webhooks/123456789/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</>
<FormMessage />
</FormItem>
)}
/>
)}
{type === "email" && (
<>

View File

@@ -16,10 +16,11 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { generateSHA256Hash } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
@@ -53,6 +54,14 @@ export const ProfileForm = () => {
const { data, refetch } = api.auth.get.useQuery();
const { mutateAsync, isLoading } = api.auth.update.useMutation();
const { t } = useTranslation("settings");
const [gravatarHash, setGravatarHash] = useState<string | null>(null);
const availableAvatars = useMemo(() => {
if (gravatarHash === null) return randomImages;
return randomImages.concat([
`https://www.gravatar.com/avatar/${gravatarHash}`,
]);
}, [gravatarHash]);
const form = useForm<Profile>({
defaultValues: {
@@ -70,6 +79,12 @@ export const ProfileForm = () => {
password: "",
image: data?.image || "",
});
if (data.email) {
generateSHA256Hash(data.email).then((hash) => {
setGravatarHash(hash);
});
}
}
form.reset();
}, [form, form.reset, data]);
@@ -154,7 +169,7 @@ export const ProfileForm = () => {
value={field.value}
className="flex flex-row flex-wrap gap-2 max-xl:justify-center"
>
{randomImages.map((image) => (
{availableAvatars.map((image) => (
<FormItem key={image}>
<FormLabel className="[&:has([data-state=checked])>img]:border-primary [&:has([data-state=checked])>img]:border-1 [&:has([data-state=checked])>img]:p-px cursor-pointer">
<FormControl>

View File

@@ -98,7 +98,7 @@ export const ShowServers = () => {
)
)}
{data && data?.length > 0 && (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-6 overflow-auto">
<Table>
<TableCaption>See all servers</TableCaption>
<TableHeader>
@@ -228,21 +228,17 @@ export const ShowServers = () => {
</DropdownMenuItem>
</DialogAction>
{isActive && (
{isActive && server.sshKeyId && (
<>
{server.sshKeyId && (
<>
<DropdownMenuSeparator />
<DropdownMenuLabel>Extra</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuLabel>Extra</DropdownMenuLabel>
<ShowTraefikFileSystemModal
serverId={server.serverId}
/>
<ShowDockerContainersModal
serverId={server.serverId}
/>
</>
)}
<ShowTraefikFileSystemModal
serverId={server.serverId}
/>
<ShowDockerContainersModal
serverId={server.serverId}
/>
</>
)}
</DropdownMenuContent>

View File

@@ -48,8 +48,8 @@ export const UpdateServer = () => {
<li>Some bug that is blocking to use some features</li>
</ul>
<AlertBlock type="info">
Please we recommend to see the latest version to see if there are
any breaking changes before updating. Go to{" "}
We recommend checking the latest version for any breaking changes
before updating. Go to{" "}
<Link
href="https://github.com/Dokploy/dokploy/releases"
target="_blank"

View File

@@ -231,8 +231,8 @@ export const BitbucketIcon = ({ className }: Props) => {
y2="10.814"
gradientUnits="userSpaceOnUse"
>
<stop offset=".18" stop-color="#0052CC" />
<stop offset="1" stop-color="#2684FF" />
<stop offset=".18" stopColor="#0052CC" />
<stop offset="1" stopColor="#2684FF" />
</linearGradient>
</defs>
</svg>

View File

@@ -31,7 +31,7 @@ export const StatusTooltip = ({ status, className }: Props) => {
)}
{status === "done" && (
<div
className={cn("size-3.5 rounded-full bg-primary", className)}
className={cn("size-3.5 rounded-full bg-green-500", className)}
/>
)}
{status === "running" && (

View File

@@ -12,7 +12,7 @@ const buttonVariants = cva(
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
"bg-destructive text-destructive-foreground hover:bg-destructive/70",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:

View File

@@ -61,7 +61,7 @@ const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<div ref={ref} {...props}>
<Dot />
</div>
));

View File

@@ -0,0 +1 @@
ALTER TABLE "destination" ADD COLUMN "provider" text;

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -316,6 +316,20 @@
"when": 1731875539532,
"tag": "0044_sour_true_believers",
"breakpoints": true
},
{
"idx": 45,
"version": "6",
"when": 1732644181718,
"tag": "0045_smiling_blur",
"breakpoints": true
},
{
"idx": 46,
"version": "6",
"when": 1732851191048,
"tag": "0046_purple_sleeper",
"breakpoints": true
}
]
}

View File

@@ -4,3 +4,11 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export async function generateSHA256Hash(text: string) {
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}

View File

@@ -2,7 +2,7 @@
module.exports = {
i18n: {
defaultLocale: "en",
locales: ["en", "pl", "zh-Hans"],
locales: ["en", "pl", "ru", "fr", "de", "tr", "zh-Hant", "zh-Hans", "fa"],
localeDetection: false,
},
fallbackLng: "en",

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.12.0",
"version": "v0.13.1",
"private": true,
"license": "Apache-2.0",
"type": "module",

View File

@@ -32,10 +32,10 @@ const MyApp = ({
return (
<>
<style jsx global>{`
:root {
--font-inter: ${inter.style.fontFamily};
}
`}</style>
:root {
--font-inter: ${inter.style.fontFamily};
}
`}</style>
<Head>
<title>Dokploy</title>
</Head>
@@ -71,7 +71,17 @@ export default api.withTRPC(
{
i18n: {
defaultLocale: "en",
locales: ["en", "pl", "zh-Hans"],
locales: [
"en",
"pl",
"ru",
"fr",
"de",
"tr",
"zh-Hant",
"zh-Hans",
"fa",
],
localeDetection: false,
},
fallbackLng: "en",

View File

@@ -10,137 +10,138 @@ import type { NextApiRequest, NextApiResponse } from "next";
import { extractCommitMessage, extractHash } from "./[refreshToken]";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
req: NextApiRequest,
res: NextApiResponse
) {
const signature = req.headers["x-hub-signature-256"];
const githubBody = req.body;
const signature = req.headers["x-hub-signature-256"];
const githubBody = req.body;
if (!githubBody?.installation?.id) {
res.status(400).json({ message: "Github Installation not found" });
return;
}
if (!githubBody?.installation?.id) {
res.status(400).json({ message: "Github Installation not found" });
return;
}
const githubResult = await db.query.github.findFirst({
where: eq(github.githubInstallationId, githubBody.installation.id),
});
const githubResult = await db.query.github.findFirst({
where: eq(github.githubInstallationId, githubBody.installation.id),
});
if (!githubResult) {
res.status(400).json({ message: "Github Installation not found" });
return;
}
if (!githubResult) {
res.status(400).json({ message: "Github Installation not found" });
return;
}
if (!githubResult.githubWebhookSecret) {
res.status(400).json({ message: "Github Webhook Secret not set" });
return;
}
const webhooks = new Webhooks({
secret: githubResult.githubWebhookSecret,
});
if (!githubResult.githubWebhookSecret) {
res.status(400).json({ message: "Github Webhook Secret not set" });
return;
}
const webhooks = new Webhooks({
secret: githubResult.githubWebhookSecret,
});
const verified = await webhooks.verify(
JSON.stringify(githubBody),
signature as string,
);
const verified = await webhooks.verify(
JSON.stringify(githubBody),
signature as string
);
if (!verified) {
res.status(401).json({ message: "Unauthorized" });
return;
}
if (!verified) {
res.status(401).json({ message: "Unauthorized" });
return;
}
if (req.headers["x-github-event"] === "ping") {
res.status(200).json({ message: "Ping received, webhook is active" });
return;
}
if (req.headers["x-github-event"] === "ping") {
res.status(200).json({ message: "Ping received, webhook is active" });
return;
}
if (req.headers["x-github-event"] !== "push") {
res.status(400).json({ message: "We only accept push events" });
return;
}
if (req.headers["x-github-event"] !== "push") {
res.status(400).json({ message: "We only accept push events" });
return;
}
try {
const branchName = githubBody?.ref?.replace("refs/heads/", "");
const repository = githubBody?.repository?.name;
const deploymentTitle = extractCommitMessage(req.headers, req.body);
const deploymentHash = extractHash(req.headers, req.body);
try {
const branchName = githubBody?.ref?.replace("refs/heads/", "");
const repository = githubBody?.repository?.name;
const deploymentTitle = extractCommitMessage(req.headers, req.body);
const deploymentHash = extractHash(req.headers, req.body);
const apps = await db.query.applications.findMany({
where: and(
eq(applications.sourceType, "github"),
eq(applications.autoDeploy, true),
eq(applications.branch, branchName),
eq(applications.repository, repository),
),
});
const apps = await db.query.applications.findMany({
where: and(
eq(applications.sourceType, "github"),
eq(applications.autoDeploy, true),
eq(applications.branch, branchName),
eq(applications.repository, repository)
),
});
for (const app of apps) {
const jobData: DeploymentJob = {
applicationId: app.applicationId as string,
titleLog: deploymentTitle,
descriptionLog: `Hash: ${deploymentHash}`,
type: "deploy",
applicationType: "application",
server: !!app.serverId,
};
for (const app of apps) {
const jobData: DeploymentJob = {
applicationId: app.applicationId as string,
titleLog: deploymentTitle,
descriptionLog: `Hash: ${deploymentHash}`,
type: "deploy",
applicationType: "application",
server: !!app.serverId,
};
if (IS_CLOUD && app.serverId) {
jobData.serverId = app.serverId;
await deploy(jobData);
return true;
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
}
if (IS_CLOUD && app.serverId) {
jobData.serverId = app.serverId;
await deploy(jobData);
return true;
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
}
);
}
const composeApps = await db.query.compose.findMany({
where: and(
eq(compose.sourceType, "github"),
eq(compose.autoDeploy, true),
eq(compose.branch, branchName),
eq(compose.repository, repository),
),
});
const composeApps = await db.query.compose.findMany({
where: and(
eq(compose.sourceType, "github"),
eq(compose.autoDeploy, true),
eq(compose.branch, branchName),
eq(compose.repository, repository)
),
});
for (const composeApp of composeApps) {
const jobData: DeploymentJob = {
composeId: composeApp.composeId as string,
titleLog: deploymentTitle,
type: "deploy",
applicationType: "compose",
descriptionLog: `Hash: ${deploymentHash}`,
};
for (const composeApp of composeApps) {
const jobData: DeploymentJob = {
composeId: composeApp.composeId as string,
titleLog: deploymentTitle,
type: "deploy",
applicationType: "compose",
descriptionLog: `Hash: ${deploymentHash}`,
server: !!composeApp.serverId,
};
if (IS_CLOUD && composeApp.serverId) {
jobData.serverId = composeApp.serverId;
await deploy(jobData);
return true;
}
if (IS_CLOUD && composeApp.serverId) {
jobData.serverId = composeApp.serverId;
await deploy(jobData);
return true;
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
}
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
}
);
}
const totalApps = apps.length + composeApps.length;
const emptyApps = totalApps === 0;
const totalApps = apps.length + composeApps.length;
const emptyApps = totalApps === 0;
if (emptyApps) {
res.status(200).json({ message: "No apps to deploy" });
return;
}
res.status(200).json({ message: `Deployed ${totalApps} apps` });
} catch (error) {
res.status(400).json({ message: "Error To Deploy Application", error });
}
if (emptyApps) {
res.status(200).json({ message: "No apps to deploy" });
return;
}
res.status(200).json({ message: `Deployed ${totalApps} apps` });
} catch (error) {
res.status(400).json({ message: "Error To Deploy Application", error });
}
}

View File

@@ -2,6 +2,7 @@ import { AddApplication } from "@/components/dashboard/project/add-application";
import { AddCompose } from "@/components/dashboard/project/add-compose";
import { AddDatabase } from "@/components/dashboard/project/add-database";
import { AddTemplate } from "@/components/dashboard/project/add-template";
import { ProjectEnviroment } from "@/components/dashboard/projects/project-enviroment";
import {
MariadbIcon,
MongodbIcon,
@@ -198,27 +199,35 @@ const Project = (
</div>
{(auth?.rol === "admin" || user?.canCreateServices) && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>
<PlusIcon className="h-4 w-4" />
Create Service
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[200px] space-y-2" align="end">
<DropdownMenuLabel className="text-sm font-normal ">
Actions
</DropdownMenuLabel>
<DropdownMenuSeparator />
<AddApplication
projectId={projectId}
projectName={data?.name}
/>
<AddDatabase projectId={projectId} projectName={data?.name} />
<AddCompose projectId={projectId} projectName={data?.name} />
<AddTemplate projectId={projectId} />
</DropdownMenuContent>
</DropdownMenu>
<div className="flex flex-row gap-4 flex-wrap">
<ProjectEnviroment projectId={projectId}>
<Button variant="outline">Project Enviroment</Button>
</ProjectEnviroment>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>
<PlusIcon className="h-4 w-4" />
Create Service
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[200px] space-y-2"
align="end"
>
<DropdownMenuLabel className="text-sm font-normal ">
Actions
</DropdownMenuLabel>
<DropdownMenuSeparator />
<AddApplication
projectId={projectId}
projectName={data?.name}
/>
<AddDatabase projectId={projectId} projectName={data?.name} />
<AddCompose projectId={projectId} projectName={data?.name} />
<AddTemplate projectId={projectId} />
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
</header>
</div>

View File

@@ -1,18 +1,29 @@
import { ShowProjects } from "@/components/dashboard/projects/show";
import { ShowWelcomeDokploy } from "@/components/dashboard/settings/billing/show-welcome-dokploy";
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
import { appRouter } from "@/server/api/root";
import { api } from "@/utils/api";
import { validateRequest } from "@dokploy/server";
import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext } from "next";
import dynamic from "next/dynamic";
import type React from "react";
import type { ReactElement } from "react";
import superjson from "superjson";
const ShowWelcomeDokploy = dynamic(
() =>
import("@/components/dashboard/settings/billing/show-welcome-dokploy").then(
(mod) => mod.ShowWelcomeDokploy,
),
{ ssr: false },
);
const Dashboard = () => {
const { data: isCloud } = api.settings.isCloud.useQuery();
return (
<>
<ShowWelcomeDokploy />
{isCloud && <ShowWelcomeDokploy />}
<ShowProjects />
</>
);

View File

@@ -8,7 +8,6 @@ import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext } from "next";
import React, { type ReactElement } from "react";
import superjson from "superjson";
import nextI18NextConfig from "../../../next-i18next.config.cjs";
const Page = () => {
return (

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,44 @@
{
"settings.common.save": "Speichern",
"settings.server.domain.title": "Server-Domain",
"settings.server.domain.description": "Füg eine Domain zu deiner Server-Anwendung hinzu.",
"settings.server.domain.form.domain": "Domain",
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt E-Mail",
"settings.server.domain.form.certificate.label": "Zertifikat",
"settings.server.domain.form.certificate.placeholder": "Wähl ein Zertifikat aus",
"settings.server.domain.form.certificateOptions.none": "Keins",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Standard)",
"settings.server.webServer.title": "Web-Server",
"settings.server.webServer.description": "Lade den Web-Server neu oder reinige ihn.",
"settings.server.webServer.actions": "Aktionen",
"settings.server.webServer.reload": "Neu laden",
"settings.server.webServer.watchLogs": "Logs anschauen",
"settings.server.webServer.updateServerIp": "Server-IP Aktualisieren",
"settings.server.webServer.server.label": "Server",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "Umgebungsvariablen ändern",
"settings.server.webServer.storage.label": "Speicherplatz",
"settings.server.webServer.storage.cleanUnusedImages": "Nicht genutzte Bilder löschen",
"settings.server.webServer.storage.cleanUnusedVolumes": "Nicht genutzte Volumes löschen",
"settings.server.webServer.storage.cleanStoppedContainers": "Gestoppte Container löschen",
"settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & System bereinigen",
"settings.server.webServer.storage.cleanMonitoring": "Monitoring bereinigen",
"settings.server.webServer.storage.cleanAll": "Alles bereinigen",
"settings.profile.title": "Konto",
"settings.profile.description": "Ändere die Details deines Profiles hier.",
"settings.profile.email": "E-Mail",
"settings.profile.password": "Passwort",
"settings.profile.avatar": "Avatar",
"settings.appearance.title": "Aussehen",
"settings.appearance.description": "Pass das Design deines Dashboards an.",
"settings.appearance.theme": "Theme",
"settings.appearance.themeDescription": "Wähl ein Theme für dein Dashboard aus",
"settings.appearance.themes.light": "Hell",
"settings.appearance.themes.dark": "Dunkel",
"settings.appearance.themes.system": "System",
"settings.appearance.language": "Sprache",
"settings.appearance.languageDescription": "Wähl eine Sprache für dein Dashboard aus"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,44 @@
{
"settings.common.save": "ذخیره",
"settings.server.domain.title": "دامنه سرور",
"settings.server.domain.description": "یک دامنه به برنامه سرور خود اضافه کنید.",
"settings.server.domain.form.domain": "دامنه",
"settings.server.domain.form.letsEncryptEmail": "ایمیل Let's Encrypt",
"settings.server.domain.form.certificate.label": "گواهینامه",
"settings.server.domain.form.certificate.placeholder": "یک گواهینامه انتخاب کنید",
"settings.server.domain.form.certificateOptions.none": "هیچکدام",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (پیش‌فرض)",
"settings.server.webServer.title": "وب سرور",
"settings.server.webServer.description": "وب سرور را بازنشانی یا پاک کنید.",
"settings.server.webServer.actions": "اقدامات",
"settings.server.webServer.reload": "بارگذاری مجدد",
"settings.server.webServer.watchLogs": "مشاهده گزارش‌ها",
"settings.server.webServer.updateServerIp": "به‌روزرسانی آی‌پی سرور",
"settings.server.webServer.server.label": "سرور",
"settings.server.webServer.traefik.label": "ترافیک",
"settings.server.webServer.traefik.modifyEnv": "ویرایش محیط",
"settings.server.webServer.storage.label": "فضا",
"settings.server.webServer.storage.cleanUnusedImages": "پاکسازی Image های بدون استفاده",
"settings.server.webServer.storage.cleanUnusedVolumes": "پاک‌سازی ولوم‌های بدون استفاده",
"settings.server.webServer.storage.cleanStoppedContainers": "پاک‌سازی کانتینرهای متوقف‌شده",
"settings.server.webServer.storage.cleanDockerBuilder": "پاک‌سازی بیلدر و سیستم داکر",
"settings.server.webServer.storage.cleanMonitoring": "پاک‌سازی پایش",
"settings.server.webServer.storage.cleanAll": "پاک‌سازی همه",
"settings.profile.title": "حساب کاربری",
"settings.profile.description": "جزئیات پروفایل خود را در اینجا تغییر دهید.",
"settings.profile.email": "ایمیل",
"settings.profile.password": "رمز عبور",
"settings.profile.avatar": "تصویر پروفایل",
"settings.appearance.title": "ظاهر",
"settings.appearance.description": "تم داشبورد خود را سفارشی کنید.",
"settings.appearance.theme": "تم",
"settings.appearance.themeDescription": "یک تم برای داشبورد خود انتخاب کنید",
"settings.appearance.themes.light": "روشن",
"settings.appearance.themes.dark": "تاریک",
"settings.appearance.themes.system": "سیستم",
"settings.appearance.language": "زبان",
"settings.appearance.languageDescription": "یک زبان برای داشبورد خود انتخاب کنید"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,44 @@
{
"settings.common.save": "Sauvegarder",
"settings.server.domain.title": "Nom de domaine du serveur",
"settings.server.domain.description": "Ajouter un nom de domaine au serveur de votre application.",
"settings.server.domain.form.domain": "Domaine",
"settings.server.domain.form.letsEncryptEmail": "Adresse email Let's Encrypt",
"settings.server.domain.form.certificate.label": "Certificat",
"settings.server.domain.form.certificate.placeholder": "Choisir un certificat",
"settings.server.domain.form.certificateOptions.none": "Aucun",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Par défaut)",
"settings.server.webServer.title": "Serveur web",
"settings.server.webServer.description": "Recharger ou nettoyer le serveur web.",
"settings.server.webServer.actions": "Actions",
"settings.server.webServer.reload": "Recharger",
"settings.server.webServer.watchLogs": "Consulter les logs",
"settings.server.webServer.updateServerIp": "Mettre à jour l'IP du serveur",
"settings.server.webServer.server.label": "Serveur",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "Modifier les variables d'environnement",
"settings.server.webServer.storage.label": "Stockage",
"settings.server.webServer.storage.cleanUnusedImages": "Supprimer les images inutilisées",
"settings.server.webServer.storage.cleanUnusedVolumes": "Supprimer les volumes inutilisés",
"settings.server.webServer.storage.cleanStoppedContainers": "Supprimer les conteneurs arrêtés",
"settings.server.webServer.storage.cleanDockerBuilder": "Nettoyer le Docker Builder & System",
"settings.server.webServer.storage.cleanMonitoring": "Nettoyer le monitoring",
"settings.server.webServer.storage.cleanAll": "Tout nettoyer",
"settings.profile.title": "Compte",
"settings.profile.description": "Modifier les informations de votre compte ici.",
"settings.profile.email": "Adresse Email",
"settings.profile.password": "Mot de passe",
"settings.profile.avatar": "Photo de profil",
"settings.appearance.title": "Apparence",
"settings.appearance.description": "Customiser le thème de votre dashboard.",
"settings.appearance.theme": "Thème",
"settings.appearance.themeDescription": "Choisir un thème pour votre dashboard",
"settings.appearance.themes.light": "Clair",
"settings.appearance.themes.dark": "Sombre",
"settings.appearance.themes.system": "Système",
"settings.appearance.language": "Langue",
"settings.appearance.languageDescription": "Sélectionner une langue pour votre dashboard"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,44 @@
{
"settings.common.save": "Сохранить",
"settings.server.domain.title": "Домен сервера",
"settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.",
"settings.server.domain.form.domain": "Домен",
"settings.server.domain.form.letsEncryptEmail": "Email для Let's Encrypt",
"settings.server.domain.form.certificate.label": "Сертификат",
"settings.server.domain.form.certificate.placeholder": "Выберите сертификат",
"settings.server.domain.form.certificateOptions.none": "Нет",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (По умолчанию)",
"settings.server.webServer.title": "Веб-сервер",
"settings.server.webServer.description": "Перезагрузка или очистка веб-сервера.",
"settings.server.webServer.server.label": "Сервер",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.storage.label": "Дисковое пространство",
"settings.server.webServer.actions": "Действия",
"settings.server.webServer.reload": "Перезагрузить",
"settings.server.webServer.watchLogs": "Просмотр логов",
"settings.server.webServer.updateServerIp": "Изменить IP адрес",
"settings.server.webServer.traefik.modifyEnv": "Изменить переменные окружения",
"settings.server.webServer.storage.cleanUnusedImages": "Очистить неиспользуемые образы",
"settings.server.webServer.storage.cleanUnusedVolumes": "Очистить неиспользуемые тома",
"settings.server.webServer.storage.cleanStoppedContainers": "Очистить остановленные контейнеры",
"settings.server.webServer.storage.cleanDockerBuilder": "Очистить Docker Builder и систему",
"settings.server.webServer.storage.cleanMonitoring": "Очистить мониторинг",
"settings.server.webServer.storage.cleanAll": "Очистить все",
"settings.profile.title": "Аккаунт",
"settings.profile.description": "Измените данные вашего профиля.",
"settings.profile.email": "Email",
"settings.profile.password": "Пароль",
"settings.profile.avatar": "Аватар",
"settings.appearance.title": "Внешний вид",
"settings.appearance.description": "Настройте тему Dokploy.",
"settings.appearance.theme": "Тема",
"settings.appearance.themeDescription": "Выберите тему системной панели",
"settings.appearance.themes.light": "Светлая",
"settings.appearance.themes.dark": "Темная",
"settings.appearance.themes.system": "Системная",
"settings.appearance.language": "Язык",
"settings.appearance.languageDescription": "Select a language for your dashboard"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,44 @@
{
"settings.common.save": "Kaydet",
"settings.server.domain.title": "Sunucu Alanı",
"settings.server.domain.description": "Sunucu uygulamanıza bir alan adı ekleyin.",
"settings.server.domain.form.domain": "Alan Adı",
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt E-postası",
"settings.server.domain.form.certificate.label": "Sertifika",
"settings.server.domain.form.certificate.placeholder": "Bir sertifika seçin",
"settings.server.domain.form.certificateOptions.none": "Hiçbiri",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Varsayılan)",
"settings.server.webServer.title": "Web Sunucusu",
"settings.server.webServer.description": "Web sunucusunu yeniden yükleyin veya temizleyin.",
"settings.server.webServer.actions": "İşlemler",
"settings.server.webServer.reload": "Yeniden Yükle",
"settings.server.webServer.watchLogs": "Günlükleri İzle",
"settings.server.webServer.updateServerIp": "Sunucu IP'sini Güncelle",
"settings.server.webServer.server.label": "Sunucu",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "Env Değiştir",
"settings.server.webServer.storage.label": "Alan",
"settings.server.webServer.storage.cleanUnusedImages": "Kullanılmayan görüntüleri temizle",
"settings.server.webServer.storage.cleanUnusedVolumes": "Kullanılmayan birimleri temizle",
"settings.server.webServer.storage.cleanStoppedContainers": "Durmuş konteynerleri temizle",
"settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder ve Sistemi Temizle",
"settings.server.webServer.storage.cleanMonitoring": "İzlemeyi Temizle",
"settings.server.webServer.storage.cleanAll": "Hepsini temizle",
"settings.profile.title": "Hesap",
"settings.profile.description": "Profil detaylarınızı buradan değiştirebilirsiniz.",
"settings.profile.email": "E-posta",
"settings.profile.password": "Şifre",
"settings.profile.avatar": "Profil Resmi",
"settings.appearance.title": "Görünüm",
"settings.appearance.description": "Kontrol panelinin temasını özelleştirin.",
"settings.appearance.theme": "Tema",
"settings.appearance.themeDescription": "Kontrol paneli için bir tema seçin",
"settings.appearance.themes.light": "Açık",
"settings.appearance.themes.dark": "Koyu",
"settings.appearance.themes.system": "Sistem",
"settings.appearance.language": "Dil",
"settings.appearance.languageDescription": "Kontrol paneli için bir dil seçin"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,40 @@
{
"settings.common.save": "儲存",
"settings.server.domain.title": "伺服器網域",
"settings.server.domain.description": "將一個網域加入到您的伺服器。",
"settings.server.domain.form.domain": "網域",
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt 郵箱",
"settings.server.domain.form.certificate.label": "憑證",
"settings.server.domain.form.certificate.placeholder": "選擇一個憑證",
"settings.server.domain.form.certificateOptions.none": "無",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (預設)",
"settings.server.webServer.title": "Web 伺服器",
"settings.server.webServer.description": "管理 Web 伺服器。",
"settings.server.webServer.actions": "操作",
"settings.server.webServer.reload": "重新載入",
"settings.server.webServer.watchLogs": "查看日誌",
"settings.server.webServer.server.label": "伺服器",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "修改環境變數",
"settings.server.webServer.storage.label": "磁碟空間",
"settings.server.webServer.storage.cleanUnusedImages": "清理未使用的映像檔",
"settings.server.webServer.storage.cleanUnusedVolumes": "清理未使用的磁碟區",
"settings.server.webServer.storage.cleanStoppedContainers": "清理已停止的容器",
"settings.server.webServer.storage.cleanDockerBuilder": "清理 Docker Builder 和系統快取",
"settings.server.webServer.storage.cleanMonitoring": "清理監控",
"settings.server.webServer.storage.cleanAll": "清理所有",
"settings.profile.title": "帳戶偏好",
"settings.profile.description": "更改您的個人資料詳情。",
"settings.profile.email": "電子郵件",
"settings.profile.password": "密碼",
"settings.profile.avatar": "頭像",
"settings.appearance.title": "外觀",
"settings.appearance.description": "自訂儀表板主題。",
"settings.appearance.theme": "主題",
"settings.appearance.themeDescription": "選擇儀表板主題",
"settings.appearance.themes.light": "亮",
"settings.appearance.themes.dark": "暗",
"settings.appearance.themes.system": "系統",
"settings.appearance.language": "語言",
"settings.appearance.languageDescription": "選擇儀表板語言"
}

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="undefined" height="undefined" viewBox="0 0 24 24">
<path fill="#0ea5e9" d="M0 12c0 6.629 5.371 12 12 12s12-5.371 12-12S18.629 0 12 0S0 5.371 0 12m17.008 5.29H11.44a5.57 5.57 0 0 1-5.562-5.567A5.57 5.57 0 0 1 11.44 6.16a5.57 5.57 0 0 1 5.567 5.563Z"/>
</svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -1 104 106">
<path fill="#231f20" d="M51.87 0C23.71 0 0 22.83 0 51v52.81l51.86-.05c28.16 0 51-23.71 51-51.87S80 0 51.87 0Z"/>
<path fill="#fff9ae" d="M52.37 19.74a31.62 31.62 0 0 0-27.79 46.67l-5.72 18.4 20.54-4.64a31.61 31.61 0 1 0 13-60.43Z"/>
<path fill="#00aeef" d="M77.45 32.12a31.6 31.6 0 0 1-38.05 48l-20.54 4.7 20.91-2.47a31.6 31.6 0 0 0 37.68-50.23Z"/>
<path fill="#00a94f" d="M71.63 26.29A31.6 31.6 0 0 1 38.8 78l-19.94 6.82 20.54-4.65a31.6 31.6 0 0 0 32.23-53.88Z"/>
<path fill="#f15d22" d="M26.47 67.11a31.61 31.61 0 0 1 51-35 31.61 31.61 0 0 0-52.89 34.3l-5.72 18.4Z"/>
<path fill="#e31b23" d="M24.58 66.41a31.61 31.61 0 0 1 47.05-40.12 31.61 31.61 0 0 0-49 39.63l-3.76 18.9Z"/>
</svg>

After

Width:  |  Height:  |  Size: 761 B

View File

@@ -0,0 +1,5 @@
<svg width="130" height="130" viewBox="0 0 130 130" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M63.1957 12.5861C65.1413 14.0499 66.2222 16.0447 66.4383 18.5707L66.4722 18.5513L66.4721 19.1439C66.4765 19.2753 66.4787 19.408 66.4787 19.5421L66.4721 19.5457L66.4721 70.8151C66.5176 73.4895 67.517 75.6073 69.4703 77.1684C72.3982 79.2596 75.4512 79.454 78.6294 77.7515L37.0683 101.69C33.9831 103.279 31.0322 103.141 28.2155 101.276C26.274 99.8155 25.1936 97.826 24.9743 95.3078L24.9325 95.3318L24.9326 42.812C24.8338 40.2483 23.8366 38.2089 21.9409 36.6938C21.0056 36.0258 20.0575 35.5513 19.0967 35.2704C17.732 34.9092 17.732 33.2068 18.738 32.6801C21.0305 31.3597 32.8988 24.5238 54.3429 12.1726C57.4281 10.5835 60.3791 10.7214 63.1957 12.5861Z" fill="#9DE8FF"/>
<path d="M66.472 70.8079C66.4429 65.6445 62.2284 58.1373 53.6907 58.1373C51.3183 58.1373 49.1069 58.7573 47.0566 59.9971L85.1604 38.0562C87.0493 36.9588 89.2462 36.3301 91.5906 36.3301C98.621 36.3301 104.326 41.9843 104.372 48.9767C104.372 48.9781 104.372 49.0065 104.372 49.0621L104.372 87.79C104.418 90.4635 105.417 92.5806 107.37 94.1413C108.316 94.8171 109.275 95.2948 110.248 95.5744C111.649 96.0291 111.488 97.668 110.58 98.1513C105.284 101.202 94.6267 107.35 78.608 116.598C75.5228 118.187 72.5719 118.049 69.7552 116.184C67.8089 114.72 66.728 112.724 66.5124 110.197L66.4722 110.22V100.642C66.4721 90.69 66.4721 70.8199 66.472 70.8079Z" fill="#106BF3"/>
<path d="M53.6907 58.1373C60.7497 58.1373 66.4722 63.8376 66.4722 70.8692C66.4722 77.9274 66.4722 82.5578 66.4722 84.7602C66.4722 84.7602 44.4801 97.4179 41.2926 99.2526C41.0435 99.3959 36.9265 101.759 36.9265 101.759C40.583 99.3959 40.9093 96.2258 40.9093 94.8181C40.9093 90.7365 40.9093 82.7535 40.9093 70.8692C40.9093 63.8376 46.6317 58.1373 53.6907 58.1373Z" fill="#57AAF9"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,9 @@
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.417391" y="0.417391" width="95.1652" height="95.1652" rx="47.5826" fill="white"/>
<rect x="0.417391" y="0.417391" width="95.1652" height="95.1652" rx="47.5826" stroke="#E9EAEC" stroke-width="0.834783"/>
<path d="M45.416 33.5745C50.1219 37.7635 53.9144 42.2526 56.3551 46.4832C60.5471 38.9452 63.3482 29.9879 63.3835 24.2829C63.3835 24.2425 63.3835 24.2057 63.3835 24.1715C63.3835 15.7297 55.0081 12.4443 47.7933 12.4443C40.5786 12.4443 32.2031 15.7297 32.2031 24.1715C32.2031 24.2866 32.2031 24.4409 32.2031 24.627C36.2246 26.4246 40.9914 29.6364 45.416 33.5745Z" fill="#FA2921"/>
<path d="M19.7443 56.5951C22.6855 53.3048 27.1978 49.739 32.2898 46.7243C37.707 43.5185 43.1254 41.2789 47.8812 40.254C42.0463 33.9159 34.4394 28.4693 29.0527 26.673C29.0149 26.6607 28.9796 26.6497 28.9479 26.6387C20.9622 24.0305 15.2661 31.0236 13.0374 37.9225C10.8087 44.8214 11.3275 53.846 19.3132 56.4542C19.4216 56.4897 19.5677 56.5375 19.7443 56.5951Z" fill="#ED79B5"/>
<path d="M82.6341 37.8063C80.4054 30.9074 74.7093 23.9143 66.7236 26.5225C66.614 26.558 66.4679 26.6057 66.2925 26.6633C65.8358 31.0629 64.2708 36.6149 61.9129 42.0627C59.4053 47.8571 56.3301 52.8715 53.082 56.5119C61.5074 58.1919 70.8462 58.1013 76.2536 56.3723C76.2914 56.3601 76.3267 56.3478 76.3583 56.338C84.344 53.7286 84.8629 44.704 82.6341 37.8063Z" fill="#FFB400"/>
<path d="M40.6729 63.3397C39.315 57.1694 38.8704 51.2954 39.3698 46.4316C31.5716 50.0525 24.0694 55.6436 20.7044 60.2392C20.6812 60.271 20.6593 60.3017 20.6398 60.3286C15.7049 67.1589 20.5595 74.7668 26.3968 79.0293C32.2329 83.293 40.9299 85.5853 45.866 78.755C45.9342 78.6619 46.0243 78.537 46.1327 78.3864C43.9295 74.5574 41.9493 69.1402 40.6729 63.3397Z" fill="#1E83F7"/>
<path d="M74.9007 59.8885C70.5979 60.8118 64.8628 61.031 58.9804 60.4591C52.7241 59.8518 47.0317 58.4607 42.584 56.4795C43.5985 65.0547 46.5701 73.9569 49.8767 78.5941C49.8998 78.626 49.9218 78.6566 49.9413 78.6835C54.8761 85.5138 63.5731 83.2215 69.4104 78.9578C75.2466 74.6941 80.1023 67.0862 75.1674 60.2571C75.0992 60.164 75.0091 60.0391 74.9007 59.8885Z" fill="#18C249"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1 @@
<svg data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 266 266"><defs><linearGradient id="a" x1="45.04" y1="231.72" x2="231.72" y2="45.04" gradientUnits="userSpaceOnUse" gradientTransform="translate(-5.38 -5.38)"><stop offset="0" stop-color="#fff"/><stop offset="0" stop-color="#b8edff"/><stop offset="1" stop-color="#d4b8ff"/></linearGradient></defs><circle cx="133" cy="133" r="132" style="fill:url(#a)"/><path data-name="Logo Pfad" d="m224.19 176.51-4 24.19M41.91 177.5l14.81 14m95.76-137.65L56.62 191.31a.09.09 0 0 0 .07.15l163.41 9.37a.09.09 0 0 0 .09-.13L152.62 53.87a.1.1 0 0 0-.14-.02zm-19.74-13.29L41.8 177.31a.13.13 0 0 0 .11.19l182.18-.8a.12.12 0 0 0 .1-.19L132.95 40.56a.12.12 0 0 0-.21 0zm.11-.16 19.77 13.32" style="fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:6px"/></svg>

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 40 40">
<g id="ss11151339769_1">
<path d="M 0 40 L 0 0 L 40 0 L 40 40 Z" fill="transparent"></path>
<path d="M 34.95 0 L 5.05 0 C 2.262 0 0 2.262 0 5.05 L 0 34.95 C 0 37.738 2.262 40 5.05 40 L 34.95 40 C 37.738 40 40 37.738 40 34.95 L 40 5.05 C 40 2.262 37.738 0 34.95 0 Z M 8.021 14.894 C 8.021 12.709 9.794 10.935 11.979 10.935 L 19.6 10.935 C 19.712 10.935 19.815 11.003 19.862 11.106 C 19.909 11.209 19.888 11.329 19.812 11.415 L 18.141 13.229 C 17.85 13.544 17.441 13.726 17.012 13.726 L 12 13.726 C 11.344 13.726 10.812 14.259 10.812 14.915 L 10.812 17.909 C 10.812 18.294 10.5 18.606 10.115 18.606 L 8.721 18.606 C 8.335 18.606 8.024 18.294 8.024 17.909 L 8.024 14.894 Z M 31.729 25.106 C 31.729 27.291 29.956 29.065 27.771 29.065 L 24.532 29.065 C 22.347 29.065 20.574 27.291 20.574 25.106 L 20.574 19.438 C 20.574 19.053 20.718 18.682 20.979 18.397 L 22.868 16.347 C 22.947 16.262 23.071 16.232 23.182 16.274 C 23.291 16.318 23.365 16.421 23.365 16.538 L 23.365 25.088 C 23.365 25.744 23.897 26.276 24.553 26.276 L 27.753 26.276 C 28.409 26.276 28.941 25.744 28.941 25.088 L 28.941 14.915 C 28.941 14.259 28.409 13.726 27.753 13.726 L 24.032 13.726 C 23.606 13.726 23.2 13.906 22.909 14.218 L 11.812 26.276 L 18.479 26.276 C 18.865 26.276 19.176 26.588 19.176 26.974 L 19.176 28.368 C 19.176 28.753 18.865 29.065 18.479 29.065 L 9.494 29.065 C 8.679 29.065 8.018 28.403 8.018 27.588 L 8.018 26.85 C 8.018 26.479 8.156 26.124 8.409 25.85 L 20.85 12.335 C 21.674 11.441 22.829 10.935 24.044 10.935 L 27.768 10.935 C 29.953 10.935 31.726 12.709 31.726 14.894 L 31.726 25.106 Z" fill="rgb(0,0,0)"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -384,6 +384,7 @@ export const applicationRouter = createTRPCRouter({
password: input.password,
sourceType: "docker",
applicationStatus: "idle",
registryUrl: input.registryUrl,
});
return true;

View File

@@ -79,6 +79,8 @@ export const composeRouter = createTRPCRouter({
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newService.composeId);
}
return newService;
} catch (error) {
throw error;
}

View File

@@ -40,11 +40,10 @@ export const destinationRouter = createTRPCRouter({
testConnection: adminProcedure
.input(apiCreateDestination)
.mutation(async ({ input }) => {
const { secretAccessKey, bucket, region, endpoint, accessKey } = input;
const { secretAccessKey, bucket, region, endpoint, accessKey, provider } =
input;
try {
const rcloneFlags = [
// `--s3-provider=Cloudflare`,
`--s3-access-key-id=${accessKey}`,
`--s3-secret-access-key=${secretAccessKey}`,
`--s3-region=${region}`,
@@ -52,6 +51,9 @@ export const destinationRouter = createTRPCRouter({
"--s3-no-check-bucket",
"--s3-force-path-style",
];
if (provider) {
rcloneFlags.unshift(`--s3-provider=${provider}`);
}
const rcloneDestination = `:s3:${bucket}`;
const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`;

View File

@@ -3,173 +3,173 @@
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--radius: 0.5rem;
--overlay: rgba(0, 0, 0, 0.2);
--radius: 0.5rem;
--overlay: rgba(0, 0, 0, 0.2);
--chart-1: 173 58% 39%;
--chart-2: 12 76% 61%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
--chart-1: 173 58% 39%;
--chart-2: 12 76% 61%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 0 0% 0%;
--foreground: 0 0% 98%;
.dark {
--background: 0 0% 0%;
--foreground: 0 0% 98%;
--card: 240 4% 10%;
--card-foreground: 0 0% 98%;
--card: 240 4% 10%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 4% 10%;
--muted-foreground: 240 5% 64.9%;
--muted: 240 4% 10%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 4% 10%;
--ring: 240 4.9% 83.9%;
--border: 240 3.7% 15.9%;
--input: 240 4% 10%;
--ring: 240 4.9% 83.9%;
--overlay: rgba(0, 0, 0, 0.5);
--overlay: rgba(0, 0, 0, 0.5);
--chart-1: 220 70% 50%;
--chart-5: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-2: 340 75% 55%;
}
--chart-1: 220 70% 50%;
--chart-5: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-2: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
.xterm-viewport {
border-radius: 0.75rem /* 12px */ !important;
border-radius: 0.75rem /* 12px */ !important;
}
.xterm .xterm-viewport {
overflow-y: auto !important;
overflow-y: auto !important;
}
.xterm .xterm-screen {
overflow: hidden;
overflow: hidden;
}
@layer utilities {
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}
/* Codemirror */
.cm-editor {
@apply w-full h-full rounded-md overflow-hidden border border-solid border-border outline-none;
@apply w-full h-full rounded-md overflow-hidden border border-solid border-border outline-none;
}
.cm-editor .cm-scroller {
font-family: inherit;
line-height: inherit;
font-family: inherit;
line-height: inherit;
}
.cm-editor.cm-focused {
@apply outline-none;
@apply outline-none;
}
/* fix: placeholder bg */
.cm-editor .cm-activeLine:has(.cm-placeholder) {
background-color: transparent;
background-color: transparent;
}
.compose-file-editor .cm-editor {
@apply min-h-[25rem];
@apply min-h-[25rem];
}
@keyframes heartbeat {
0% {
transform: scale(1);
opacity: 0.7;
}
25% {
transform: scale(1.1);
opacity: 1;
}
50% {
transform: scale(1);
opacity: 0.7;
}
75% {
transform: scale(1.1);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0.7;
}
0% {
transform: scale(1);
opacity: 0.7;
}
25% {
transform: scale(1.1);
opacity: 1;
}
50% {
transform: scale(1);
opacity: 0.7;
}
75% {
transform: scale(1.1);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 0.7;
}
}
.animate-heartbeat {
animation: heartbeat 2.5s infinite;
animation: heartbeat 2.5s infinite;
}
@media (prefers-color-scheme: dark) {
.swagger-ui {
background-color: white;
}
.swagger-ui {
background-color: white;
}
.swagger-ui .info {
margin: 0px !important;
padding-top: 1rem !important;
}
.swagger-ui .info {
margin: 0px !important;
padding-top: 1rem !important;
}
}

View File

@@ -0,0 +1,76 @@
version: '3'
x-base-config: &base-config
image: chatwoot/chatwoot:v3.14.1
volumes:
- chatwoot-storage:/app/storage
networks:
- dokploy-network
environment:
- FRONTEND_URL=${FRONTEND_URL}
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
- RAILS_ENV=${RAILS_ENV}
- NODE_ENV=${NODE_ENV}
- INSTALLATION_ENV=${INSTALLATION_ENV}
- RAILS_LOG_TO_STDOUT=${RAILS_LOG_TO_STDOUT}
- LOG_LEVEL=${LOG_LEVEL}
- DEFAULT_LOCALE=${DEFAULT_LOCALE}
- POSTGRES_HOST=${POSTGRES_HOST}
- POSTGRES_PORT=${POSTGRES_PORT}
- POSTGRES_DATABASE=${POSTGRES_DATABASE}
- POSTGRES_USERNAME=${POSTGRES_USERNAME}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- REDIS_URL=${REDIS_URL}
- ENABLE_ACCOUNT_SIGNUP=${ENABLE_ACCOUNT_SIGNUP}
- ACTIVE_STORAGE_SERVICE=${ACTIVE_STORAGE_SERVICE}
services:
chatwoot-rails:
<<: *base-config
depends_on:
chatwoot-postgres:
condition: service_started
chatwoot-redis:
condition: service_started
entrypoint: docker/entrypoints/rails.sh
command: ['bundle', 'exec', 'sh', '-c', 'rails db:chatwoot_prepare && rails s -p 3000 -b 0.0.0.0']
restart: always
chatwoot-sidekiq:
<<: *base-config
depends_on:
chatwoot-postgres:
condition: service_started
chatwoot-redis:
condition: service_started
command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
restart: always
chatwoot-postgres:
image: postgres:12
restart: always
volumes:
- chatwoot-postgres-data:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
- POSTGRES_DB=${POSTGRES_DATABASE}
- POSTGRES_USER=${POSTGRES_USERNAME}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
chatwoot-redis:
image: redis:alpine
restart: always
volumes:
- chatwoot-redis-data:/data
networks:
- dokploy-network
networks:
dokploy-network:
external: true
volumes:
chatwoot-storage:
chatwoot-postgres-data:
chatwoot-redis-data:

View File

@@ -0,0 +1,46 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const secretKeyBase = generateBase64(64);
const postgresPassword = generatePassword();
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "chatwoot-rails",
},
];
const envs = [
`FRONTEND_URL=http://${mainDomain}`,
`SECRET_KEY_BASE=${secretKeyBase}`,
"RAILS_ENV=production",
"NODE_ENV=production",
"INSTALLATION_ENV=docker",
"RAILS_LOG_TO_STDOUT=true",
"LOG_LEVEL=info",
"DEFAULT_LOCALE=en",
"POSTGRES_HOST=chatwoot-postgres",
"POSTGRES_PORT=5432",
"POSTGRES_DATABASE=chatwoot",
"POSTGRES_USERNAME=postgres",
`POSTGRES_PASSWORD=${postgresPassword}`,
"REDIS_URL=redis://chatwoot-redis:6379",
"ENABLE_ACCOUNT_SIGNUP=false",
"ACTIVE_STORAGE_SERVICE=local",
];
return {
domains,
envs,
};
}

View File

@@ -1,14 +1,13 @@
version: "3.8"
services:
mysql:
tickets-postgres:
image: mysql:8
restart: unless-stopped
hostname: mysql
networks:
- dokploy-network
volumes:
- tickets-mysql:/var/lib/mysql
- tickets-mysql-data:/var/lib/mysql
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
@@ -20,23 +19,22 @@ services:
timeout: 5s
retries: 5
bot:
tickets-app:
image: eartharoid/discord-tickets:4.0.21
depends_on:
mysql:
tickets-postgres:
condition: service_healthy
restart: unless-stopped
hostname: bot
networks:
- dokploy-network
volumes:
- tickets-bot:/home/container/user
- tickets-app-data:/home/container/user
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
tty: true
stdin_open: true
environment:
DB_CONNECTION_URL: mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql/${MYSQL_DATABASE}
DB_CONNECTION_URL: mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@tickets-postgres/${MYSQL_DATABASE}
DISCORD_SECRET: ${DISCORD_SECRET}
DISCORD_TOKEN: ${DISCORD_TOKEN}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
@@ -49,6 +47,10 @@ services:
PUBLISH_COMMANDS: "true"
SUPER: ${SUPER_USERS}
networks:
dokploy-network:
external: true
volumes:
tickets-mysql:
tickets-bot:
tickets-mysql-data:
tickets-app-data:

View File

@@ -13,7 +13,6 @@ export function generate(schema: Schema): Template {
const mysqlUser = "tickets";
const mysqlDatabase = "tickets";
// Generate encryption key in the format they use
const encryptionKey = Array.from({ length: 48 }, () =>
Math.floor(Math.random() * 16).toString(16),
).join("");
@@ -22,7 +21,7 @@ export function generate(schema: Schema): Template {
{
host: mainDomain,
port: 8169,
serviceName: "bot",
serviceName: "tickets-app",
},
];
@@ -33,7 +32,6 @@ export function generate(schema: Schema): Template {
`MYSQL_ROOT_PASSWORD=${mysqlRootPassword}`,
`MYSQL_USER=${mysqlUser}`,
`ENCRYPTION_KEY=${encryptionKey}`,
// These need to be set by the user through the UI
"# Follow the guide at: https://discordtickets.app/self-hosting/installation/docker/#creating-the-discord-application",
"DISCORD_SECRET=",
"DISCORD_TOKEN=",

View File

@@ -0,0 +1,94 @@
version: '3.7'
services:
discourse-db:
image: docker.io/bitnami/postgresql:17
networks:
- dokploy-network
volumes:
- discourse-postgresql-data:/bitnami/postgresql
environment:
POSTGRESQL_USERNAME: bn_discourse
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRESQL_DATABASE: bitnami_discourse
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bn_discourse -d bitnami_discourse"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
discourse-redis:
image: docker.io/bitnami/redis:7.4
networks:
- dokploy-network
volumes:
- discourse-redis-data:/bitnami/redis
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
discourse-app:
image: docker.io/bitnami/discourse:3.3.2
networks:
- dokploy-network
volumes:
- discourse-data:/bitnami/discourse
depends_on:
discourse-db:
condition: service_healthy
discourse-redis:
condition: service_healthy
environment:
DISCOURSE_HOST: ${DISCOURSE_HOST}
DISCOURSE_DATABASE_HOST: discourse-db
DISCOURSE_DATABASE_PORT_NUMBER: 5432
DISCOURSE_DATABASE_USER: bn_discourse
DISCOURSE_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
DISCOURSE_DATABASE_NAME: bitnami_discourse
DISCOURSE_REDIS_HOST: discourse-redis
DISCOURSE_REDIS_PORT_NUMBER: 6379
DISCOURSE_REDIS_PASSWORD: ${REDIS_PASSWORD}
# Optional: Configure SMTP for email delivery
# DISCOURSE_SMTP_HOST: ${SMTP_HOST}
# DISCOURSE_SMTP_PORT: ${SMTP_PORT}
# DISCOURSE_SMTP_USER: ${SMTP_USER}
# DISCOURSE_SMTP_PASSWORD: ${SMTP_PASSWORD}
restart: unless-stopped
discourse-sidekiq:
image: docker.io/bitnami/discourse:3.3.2
networks:
- dokploy-network
volumes:
- discourse-sidekiq-data:/bitnami/discourse
depends_on:
- discourse-app
command: /opt/bitnami/scripts/discourse-sidekiq/run.sh
environment:
DISCOURSE_HOST: ${DISCOURSE_HOST}
DISCOURSE_DATABASE_HOST: discourse-db
DISCOURSE_DATABASE_PORT_NUMBER: 5432
DISCOURSE_DATABASE_USER: bn_discourse
DISCOURSE_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
DISCOURSE_DATABASE_NAME: bitnami_discourse
DISCOURSE_REDIS_HOST: discourse-redis
DISCOURSE_REDIS_PORT_NUMBER: 6379
DISCOURSE_REDIS_PASSWORD: ${REDIS_PASSWORD}
# Optional: Configure SMTP for email delivery
# DISCOURSE_SMTP_HOST: ${SMTP_HOST}
# DISCOURSE_SMTP_PORT: ${SMTP_PORT}
# DISCOURSE_SMTP_USER: ${SMTP_USER}
# DISCOURSE_SMTP_PASSWORD: ${SMTP_PASSWORD}
restart: unless-stopped
volumes:
discourse-postgresql-data:
discourse-redis-data:
discourse-data:
discourse-sidekiq-data:

View File

@@ -0,0 +1,37 @@
import {
type DomainSchema,
type Schema,
type Template,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const postgresPassword = generatePassword();
const redisPassword = generatePassword();
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "discourse-app",
},
];
const envs = [
`DISCOURSE_HOST=${mainDomain}`,
`POSTGRES_PASSWORD=${postgresPassword}`,
`REDIS_PASSWORD=${redisPassword}`,
"# Optional: Configure SMTP for email delivery",
"# SMTP_HOST=smtp.example.com",
"# SMTP_PORT=587",
"# SMTP_USER=your_smtp_user",
"# SMTP_PASSWORD=your_smtp_password",
];
return {
domains,
envs,
};
}

View File

@@ -0,0 +1,48 @@
services:
heyform:
image: heyform/community-edition:latest
restart: always
volumes:
# Persist uploaded images
- heyform-data:/app/static/upload
depends_on:
- mongo
- redis
ports:
- 8000
env_file:
- .env
environment:
MONGO_URI: 'mongodb://mongo:27017/heyform'
REDIS_HOST: redis
REDIS_PORT: 6379
networks:
- heyform-network
mongo:
image: percona/percona-server-mongodb:4.4
restart: always
networks:
- heyform-network
volumes:
# Persist MongoDB data
- mongo-data:/data/db
redis:
image: redis
restart: always
command: "redis-server --appendonly yes"
networks:
- heyform-network
volumes:
# Persist KeyDB data
- redis-data:/data
networks:
heyform-network:
driver: bridge
volumes:
heyform-data:
mongo-data:
redis-data:

View File

@@ -0,0 +1,32 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const sessionKey = generateBase64(64);
const formEncryptionKey = generateBase64(64);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8000,
serviceName: "heyform",
},
];
const envs = [
`APP_HOMEPAGE_URL=http://${mainDomain}`,
`SESSION_KEY=${sessionKey}`,
`FORM_ENCRYPTION_KEY=${formEncryptionKey}`,
];
return {
envs,
domains,
};
}

View File

@@ -0,0 +1,111 @@
version: "3.9"
services:
immich-server:
image: ghcr.io/immich-app/immich-server:v1.121.0
networks:
- dokploy-network
volumes:
- immich-library:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
depends_on:
immich-redis:
condition: service_healthy
immich-database:
condition: service_healthy
environment:
PORT: 2283
SERVER_URL: ${SERVER_URL}
FRONT_BASE_URL: ${FRONT_BASE_URL}
# Database Configuration
DB_HOSTNAME: ${DB_HOSTNAME}
DB_PORT: ${DB_PORT}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
DB_DATABASE_NAME: ${DB_DATABASE_NAME}
# Redis Configuration
REDIS_HOSTNAME: ${REDIS_HOSTNAME}
REDIS_PORT: ${REDIS_PORT}
REDIS_DBINDEX: ${REDIS_DBINDEX}
# Server Configuration
TZ: ${TZ}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2283/server-info/ping"]
interval: 30s
timeout: 10s
retries: 3
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:v1.121.0
networks:
- dokploy-network
volumes:
- immich-model-cache:/cache
environment:
REDIS_HOSTNAME: ${REDIS_HOSTNAME}
REDIS_PORT: ${REDIS_PORT}
REDIS_DBINDEX: ${REDIS_DBINDEX}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3003/ping"]
interval: 30s
timeout: 10s
retries: 3
immich-redis:
image: redis:6.2-alpine
networks:
- dokploy-network
volumes:
- immich-redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: always
immich-database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0
networks:
- dokploy-network
volumes:
- immich-postgres:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: immich
POSTGRES_INITDB_ARGS: '--data-checksums'
healthcheck:
test: pg_isready -U ${DB_USERNAME} -d immich || exit 1
interval: 10s
timeout: 5s
retries: 5
command:
[
'postgres',
'-c',
'shared_preload_libraries=vectors.so',
'-c',
'search_path="$$user", public, vectors',
'-c',
'logging_collector=on',
'-c',
'max_wal_size=2GB',
'-c',
'shared_buffers=512MB',
'-c',
'wal_compression=on',
]
restart: always
networks:
dokploy-network:
external: true
volumes:
immich-model-cache:
immich-postgres:
immich-library:
immich-redis-data:

View File

@@ -0,0 +1,46 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const dbPassword = generatePassword();
const dbUser = "immich";
const appSecret = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 2283,
serviceName: "immich-server",
},
];
const envs = [
`IMMICH_HOST=${mainDomain}`,
`SERVER_URL=https://${mainDomain}`,
`FRONT_BASE_URL=https://${mainDomain}`,
"# Database Configuration",
"DB_HOSTNAME=immich-database",
"DB_PORT=5432",
`DB_USERNAME=${dbUser}`,
`DB_PASSWORD=${dbPassword}`,
"DB_DATABASE_NAME=immich",
"# Redis Configuration",
"REDIS_HOSTNAME=immich-redis",
"REDIS_PORT=6379",
"REDIS_DBINDEX=0",
"# Server Configuration",
"TZ=UTC",
];
return {
domains,
envs,
};
}

View File

@@ -1,12 +1,12 @@
version: "3.8"
services:
invoiceshelf_db:
invoiceshelf-postgres:
image: postgres:15
networks:
- dokploy-network
volumes:
- postgres_data:/var/lib/postgresql/data
- invoiceshelf-postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_USER=${DB_USERNAME}
@@ -17,13 +17,13 @@ services:
timeout: 5s
retries: 5
invoiceshelf:
invoiceshelf-app:
image: invoiceshelf/invoiceshelf:latest
networks:
- dokploy-network
volumes:
- app_data:/data
- app_conf:/conf
- invoiceshelf-app-data:/data
- invoiceshelf-app-conf:/conf
environment:
- PHP_TZ=UTC
- TIMEZONE=UTC
@@ -32,7 +32,7 @@ services:
- APP_DEBUG=false
- APP_URL=http://${INVOICESHELF_HOST}
- DB_CONNECTION=pgsql
- DB_HOST=invoiceshelf_db
- DB_HOST=invoiceshelf-postgres
- DB_PORT=5432
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
@@ -46,10 +46,14 @@ services:
- SANCTUM_STATEFUL_DOMAINS=${INVOICESHELF_HOST}
- STARTUP_DELAY=10
depends_on:
invoiceshelf_db:
invoiceshelf-postgres:
condition: service_healthy
networks:
dokploy-network:
external: true
volumes:
postgres_data:
app_data:
app_conf:
invoiceshelf-postgres-data:
invoiceshelf-app-data:
invoiceshelf-app-conf:

View File

@@ -16,7 +16,7 @@ export function generate(schema: Schema): Template {
{
host: mainDomain,
port: 80,
serviceName: "invoiceshelf",
serviceName: "invoiceshelf-app",
},
];

View File

@@ -0,0 +1,14 @@
services:
ontime:
image: getontime/ontime:v3.8.0
ports:
- 4001
- 8888
- 9999
volumes:
- ontime-data:/data/
environment:
- TZ
restart: unless-stopped
volumes:
ontime-data:

View File

@@ -0,0 +1,25 @@
import {
type DomainSchema,
type Schema,
type Template,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 4001,
serviceName: "ontime",
},
];
const envs = ["TZ=UTC"];
return {
domains,
envs,
};
}

Some files were not shown because too many files have changed in this diff Show More