diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 16e8e6664..000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["biomejs.biome"] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d17ed236a..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "[javascript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[json]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[jsonc]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "biomejs.biome" - }, - "editor.codeActionsOnSave": { - "quickfix.biome": "explicit", - "source.organizeImports.biome": "explicit" - }, - "editor.defaultFormatter": "biomejs.biome", - "editor.formatOnPaste": true, - "editor.formatOnSave": true, - "emmet.showExpandedAbbreviation": "never", - "prettier.enable": false -} diff --git a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx index 4008d6fd5..42683887e 100644 --- a/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx +++ b/apps/dokploy/components/dashboard/docker/terminal/docker-terminal.tsx @@ -25,8 +25,6 @@ export const DockerTerminal: React.FC = ({ } const term = new Terminal({ cursorBlink: true, - cols: 80, - rows: 30, lineHeight: 1.4, convertEol: true, theme: { @@ -45,6 +43,7 @@ export const DockerTerminal: React.FC = ({ const addonAttach = new AttachAddon(ws); // @ts-ignore term.open(termRef.current); + // @ts-ignore term.loadAddon(addonFit); term.loadAddon(addonAttach); addonFit.fit(); @@ -66,7 +65,7 @@ export const DockerTerminal: React.FC = ({ -
+
diff --git a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx index 1d4daa533..65ccff0e3 100644 --- a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx @@ -26,10 +26,12 @@ import { toast } from "sonner"; import { z } from "zod"; import { Disable2FA } from "./disable-2fa"; import { Enable2FA } from "./enable-2fa"; +import { AlertBlock } from "@/components/shared/alert-block"; const profileSchema = z.object({ email: z.string(), password: z.string().nullable(), + currentPassword: z.string().nullable(), image: z.string().optional(), }); @@ -52,7 +54,8 @@ const randomImages = [ export const ProfileForm = () => { const { data, refetch } = api.auth.get.useQuery(); - const { mutateAsync, isLoading } = api.auth.update.useMutation(); + const { mutateAsync, isLoading, isError, error } = + api.auth.update.useMutation(); const { t } = useTranslation("settings"); const [gravatarHash, setGravatarHash] = useState(null); @@ -68,6 +71,7 @@ export const ProfileForm = () => { email: data?.email || "", password: "", image: data?.image || "", + currentPassword: "", }, resolver: zodResolver(profileSchema), }); @@ -78,6 +82,7 @@ export const ProfileForm = () => { email: data?.email || "", password: "", image: data?.image || "", + currentPassword: "", }); if (data.email) { @@ -94,6 +99,7 @@ export const ProfileForm = () => { email: values.email.toLowerCase(), password: values.password, image: values.image, + currentPassword: values.currentPassword, }) .then(async () => { await refetch(); @@ -116,6 +122,8 @@ export const ProfileForm = () => { {!data?.is2FAEnabled ? : } + {isError && {error?.message}} +
@@ -135,6 +143,24 @@ export const ProfileForm = () => { )} /> + ( + + Current Password + + + + + + )} + /> ; + +export const RemoveSelfAccount = () => { + const { data } = api.auth.get.useQuery(); + const { mutateAsync, isLoading, error, isError } = + api.auth.removeSelfAccount.useMutation(); + const { t } = useTranslation("settings"); + const router = useRouter(); + + const form = useForm({ + defaultValues: { + password: "", + }, + resolver: zodResolver(profileSchema), + }); + + useEffect(() => { + if (data) { + form.reset({ + password: "", + }); + } + form.reset(); + }, [form, form.reset, data]); + + const onSubmit = async (values: Profile) => { + await mutateAsync({ + password: values.password, + }) + .then(async () => { + toast.success("Profile Deleted"); + router.push("/"); + }) + .catch(() => {}); + }; + + return ( + + +
+ Remove Self Account + + If you want to remove your account, you can do it here + +
+
+ + {isError && {error?.message}} + + + e.preventDefault()} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + } + }} + className="grid gap-4" + > +
+ ( + + {t("settings.profile.password")} + + + + + + )} + /> +
+ + +
+ form.handleSubmit(onSubmit)()} + > + + +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/edit-script.tsx b/apps/dokploy/components/dashboard/settings/servers/edit-script.tsx new file mode 100644 index 000000000..ccdd8d318 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/servers/edit-script.tsx @@ -0,0 +1,167 @@ +import { AlertBlock } from "@/components/shared/alert-block"; +import { CodeEditor } from "@/components/shared/code-editor"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { FileTerminal } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +interface Props { + serverId: string; +} + +const schema = z.object({ + command: z.string().min(1, { + message: "Command is required", + }), +}); + +type Schema = z.infer; + +export const EditScript = ({ serverId }: Props) => { + const [isOpen, setIsOpen] = useState(false); + const { data: server } = api.server.one.useQuery( + { + serverId, + }, + { + enabled: !!serverId, + }, + ); + + const { mutateAsync, isLoading } = api.server.update.useMutation(); + + const { data: defaultCommand } = api.server.getDefaultCommand.useQuery( + { + serverId, + }, + { + enabled: !!serverId, + }, + ); + + const form = useForm({ + defaultValues: { + command: "", + }, + resolver: zodResolver(schema), + }); + + useEffect(() => { + if (server) { + form.reset({ + command: server.command || defaultCommand, + }); + } + }, [server, defaultCommand]); + + const onSubmit = async (formData: Schema) => { + if (server) { + await mutateAsync({ + ...server, + command: formData.command || "", + serverId, + }) + .then((data) => { + toast.success("Script modified successfully"); + }) + .catch(() => { + toast.error("Error modifying the script"); + }); + } + }; + + return ( + + + + + + + Modify Script + + Modify the script which install everything necessary to deploy + applications on your server, + + + + We suggest to don't modify the script if you don't know what you are + doing + + +
+
+ + ( + + Command + + + + + + )} + /> + + +
+ + + + +
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx index eb0d22553..33d9bb6c6 100644 --- a/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/setup-server.tsx @@ -32,6 +32,7 @@ import Link from "next/link"; import { useState } from "react"; import { toast } from "sonner"; import { ShowDeployment } from "../../application/deployments/show-deployment"; +import { EditScript } from "./edit-script"; import { GPUSupport } from "./gpu-support"; import { ValidateServer } from "./validate-server"; @@ -89,7 +90,12 @@ export const SetupServer = ({ serverId }: Props) => {
) : ( -
+
+ + Using a root user is required to ensure everything works as + expected. + + SSH Keys @@ -139,7 +145,7 @@ export const SetupServer = ({ serverId }: Props) => { Automatic process @@ -198,6 +204,28 @@ export const SetupServer = ({ serverId }: Props) => {
+
+ + Supported Distros: + +

+ We strongly recommend to use the following distros to + ensure the best experience: +

+
    +
  • 1. Ubuntu 24.04 LTS
  • +
  • 2. Ubuntu 23.10 LTS
  • +
  • 3. Ubuntu 22.04 LTS
  • +
  • 4. Ubuntu 20.04 LTS
  • +
  • 5. Ubuntu 18.04 LTS
  • +
  • 6. Debian 12
  • +
  • 7. Debian 11
  • +
  • 8. Debian 10
  • +
  • 9. Fedora 40
  • +
  • 10. Centos 9
  • +
  • 11. Centos 8
  • +
+
@@ -214,24 +242,29 @@ export const SetupServer = ({ serverId }: Props) => { See all the 5 Server Setup
- { - await mutateAsync({ - serverId: server?.serverId || "", - }) - .then(async () => { - refetch(); - toast.success("Server setup successfully"); +
+ + { + await mutateAsync({ + serverId: server?.serverId || "", }) - .catch(() => { - toast.error("Error configuring server"); - }); - }} - > - - + .then(async () => { + refetch(); + toast.success("Server setup successfully"); + }) + .catch(() => { + toast.error("Error configuring server"); + }); + }} + > + + +
diff --git a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx index 42c042f30..afd16b94c 100644 --- a/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx +++ b/apps/dokploy/components/dashboard/settings/servers/show-servers.tsx @@ -31,8 +31,12 @@ import { SetupServer } from "./setup-server"; import { ShowDockerContainersModal } from "./show-docker-containers-modal"; import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal"; import { UpdateServer } from "./update-server"; +import { useRouter } from "next/router"; +import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription"; export const ShowServers = () => { + const router = useRouter(); + const query = router.query; const { data, refetch } = api.server.all.useQuery(); const { mutateAsync } = api.server.remove.useMutation(); const { data: sshKeys } = api.sshKey.all.useQuery(); @@ -42,12 +46,26 @@ export const ShowServers = () => { return (
+ {query?.success && }
-
-

Servers

-

- Add servers to deploy your applications remotely. -

+
+
+

Servers

+

+ Add servers to deploy your applications remotely. +

+
+ + {isCloud && ( + { + router.push("/dashboard/settings/servers?success=true"); + }} + > + Reset Onboarding + + )}
{sshKeys && sshKeys?.length > 0 && ( @@ -100,7 +118,9 @@ export const ShowServers = () => { {data && data?.length > 0 && (
- See all servers + +
See all servers
+
Name diff --git a/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx new file mode 100644 index 000000000..39edad718 --- /dev/null +++ b/apps/dokploy/components/dashboard/settings/servers/welcome-stripe/create-server.tsx @@ -0,0 +1,284 @@ +import { AlertBlock } from "@/components/shared/alert-block"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { DialogFooter } from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { api } from "@/utils/api"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { PlusIcon } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import { z } from "zod"; + +const Schema = z.object({ + name: z.string().min(1, { + message: "Name is required", + }), + description: z.string().optional(), + ipAddress: z.string().min(1, { + message: "IP Address is required", + }), + port: z.number().optional(), + username: z.string().optional(), + sshKeyId: z.string().min(1, { + message: "SSH Key is required", + }), +}); + +type Schema = z.infer; + +interface Props { + stepper: any; +} + +export const CreateServer = ({ stepper }: Props) => { + const { data: sshKeys } = api.sshKey.all.useQuery(); + const [isOpen, setIsOpen] = useState(false); + const { data: canCreateMoreServers, refetch } = + api.stripe.canCreateMoreServers.useQuery(); + const { mutateAsync, error, isError } = api.server.create.useMutation(); + const cloudSSHKey = sshKeys?.find( + (sshKey) => sshKey.name === "dokploy-cloud-ssh-key", + ); + + const form = useForm({ + defaultValues: { + description: "Dokploy Cloud Server", + name: "My First Server", + ipAddress: "", + port: 22, + username: "root", + sshKeyId: cloudSSHKey?.sshKeyId || "", + }, + resolver: zodResolver(Schema), + }); + + useEffect(() => { + form.reset({ + description: "Dokploy Cloud Server", + name: "My First Server", + ipAddress: "", + port: 22, + username: "root", + sshKeyId: cloudSSHKey?.sshKeyId || "", + }); + }, [form, form.reset, form.formState.isSubmitSuccessful, sshKeys]); + + useEffect(() => { + refetch(); + }, [isOpen]); + + const onSubmit = async (data: Schema) => { + await mutateAsync({ + name: data.name, + description: data.description || "", + ipAddress: data.ipAddress || "", + port: data.port || 22, + username: data.username || "root", + sshKeyId: data.sshKeyId || "", + }) + .then(async (data) => { + toast.success("Server Created"); + stepper.next(); + }) + .catch(() => { + toast.error("Error to create a server"); + }); + }; + return ( + +
+ {!canCreateMoreServers && ( + + You cannot create more servers,{" "} + + Please upgrade your plan + + + )} +
+ + +
+ +
+ ( + + Name + + + + + + + )} + /> +
+ ( + + Description + +