Merge branch 'Dokploy:canary' into canary

This commit is contained in:
Mr1Blaze 2024-05-22 22:30:40 +03:00 committed by GitHub
commit 7e29e9e0d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 521 additions and 549 deletions

3
.gitignore vendored
View File

@ -53,4 +53,5 @@ yarn-error.log*
/.data
/.main
*.lockb
*.lockb
*.rdb

View File

@ -1,3 +1,5 @@
# License
Copyright 2024 Mauricio Siu.
Licensed under the Apache License, Version 2.0 (the "License");
@ -9,16 +11,13 @@ You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
See the License for the specific language governing permissions and limitations under the License.
Appendix:
## Appendix
In cases of conflict, the provisions in this appendix supersede those in the general Apache License.
In the event of a conflict, the provisions in this appendix shall take precedence over those in the Apache License.
- **Modification of Paid Features:** Written consent or a formal agreement is required for modifying any paid features of Dokploy.
- **Prohibition of Unauthorized Resale:** No party is permitted to sell, resell, or otherwise distribute for commercial gain, the software or any of its components, including both original and modified versions, without prior written consent from the copyright holder.
- **Commercial Distribution:** Any distribution of Dokploy, whether for direct profit or indirect financial benefit, through commercial channels is strictly prohibited without a separate commercial agreement negotiated with the copyright holder.
- **Open Source Distribution of Free Features:** Any modifications to the free features of Dokploy must be distributed freely and must not be included in any paid or commercial package unless they comply with the license and appendix terms specified in this agreement.
- **Modification Distribution:** Any modifications to the software must be distributed freely.
- **Future Paid Features:** Any future paid features of Dokploy cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
For further inquiries or permissions, please contact us directly.

View File

@ -61,7 +61,7 @@ export const Login2FA = ({ authId }: Props) => {
id: authId,
})
.then(() => {
toast.success("Signin succesfully", {
toast.success("Signin successfully", {
duration: 2000,
});

View File

@ -26,11 +26,11 @@ interface Props {
applicationId: string;
}
const AddRedirectchema = z.object({
const AddRedirectSchema = z.object({
command: z.string(),
});
type AddCommand = z.infer<typeof AddRedirectchema>;
type AddCommand = z.infer<typeof AddRedirectSchema>;
export const AddCommand = ({ applicationId }: Props) => {
const { data } = api.application.one.useQuery(
@ -48,7 +48,7 @@ export const AddCommand = ({ applicationId }: Props) => {
defaultValues: {
command: "",
},
resolver: zodResolver(AddRedirectchema),
resolver: zodResolver(AddRedirectSchema),
});
useEffect(() => {

View File

@ -18,8 +18,8 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -33,7 +33,7 @@ import {
} from "@/components/ui/select";
import { z } from "zod";
const AddPortchema = z.object({
const AddPortSchema = z.object({
publishedPort: z.number().int().min(1).max(65535),
targetPort: z.number().int().min(1).max(65535),
protocol: z.enum(["tcp", "udp"], {
@ -41,7 +41,7 @@ const AddPortchema = z.object({
}),
});
type AddPort = z.infer<typeof AddPortchema>;
type AddPort = z.infer<typeof AddPortSchema>;
interface Props {
applicationId: string;
@ -62,7 +62,7 @@ export const AddPort = ({
publishedPort: 0,
targetPort: 0,
},
resolver: zodResolver(AddPortchema),
resolver: zodResolver(AddPortSchema),
});
useEffect(() => {
@ -100,14 +100,7 @@ export const AddPort = ({
Ports are used to expose your application to the internet.
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -18,8 +18,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Pencil } from "lucide-react";
import { Pencil } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -106,14 +107,7 @@ export const UpdatePort = ({ portId }: Props) => {
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the port</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -19,8 +19,8 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -98,14 +98,7 @@ export const AddRedirect = ({
Redirects are used to redirect requests to another url.
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -19,8 +19,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Pencil } from "lucide-react";
import { Pencil } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -101,14 +102,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => {
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the redirect</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -18,8 +18,8 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -90,14 +90,7 @@ export const AddSecurity = ({
Add security to your application
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -18,8 +18,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Pencil } from "lucide-react";
import { Pencil } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -96,14 +97,7 @@ export const UpdateSecurity = ({ securityId }: Props) => {
<DialogTitle>Update</DialogTitle>
<DialogDescription>Update the security</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -18,8 +18,8 @@ import {
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -116,14 +116,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
<DialogTitle>Update traefik config</DialogTitle>
<DialogDescription>Update the traefik config</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
@ -140,7 +133,7 @@ export const UpdateTraefikConfig = ({ applicationId }: Props) => {
<FormLabel>Traefik config</FormLabel>
<FormControl>
<Textarea
className="h-[35rem]"
className="h-[35rem] font-mono"
placeholder={`http:
routers:
router-name:

View File

@ -147,7 +147,7 @@ export const AddVolumes = ({
<DialogTitle>Volumes / Mounts</DialogTitle>
</DialogHeader>
{/* {isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<div className="flex items-center flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}

View File

@ -27,8 +27,9 @@ import {
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, PlusIcon } from "lucide-react";
import { PlusIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -106,14 +107,7 @@ export const AddDomain = ({
In this section you can add custom domains
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -27,8 +27,9 @@ import {
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, PenBoxIcon } from "lucide-react";
import { PenBoxIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -125,14 +126,7 @@ export const UpdateDomain = ({ domainId }: Props) => {
In this section you can add custom domains
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -96,7 +96,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
<FormControl>
<Textarea
placeholder="NODE_ENV=production"
className="h-96"
className="h-96 font-mono"
{...field}
/>
</FormControl>

View File

@ -18,6 +18,7 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
@ -97,14 +98,7 @@ export const UpdateApplication = ({ applicationId }: Props) => {
<DialogTitle>Modify Application</DialogTitle>
<DialogDescription>Update the application data</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -32,14 +32,14 @@ export const ShowContainerConfig = ({ containerId }: Props) => {
View Config
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className={"sm:max-w-5xl overflow-y-auto max-h-screen"}>
<DialogContent className={"w-full md:w-[70vw] max-w-max"}>
<DialogHeader>
<DialogTitle>Container Config</DialogTitle>
<DialogDescription>
See in detail the config of this container
</DialogDescription>
</DialogHeader>
<div className="text-wrap rounded-lg border p-4 text-sm sm:max-w-[59rem] bg-card">
<div className="text-wrap rounded-lg border p-4 text-sm bg-card overflow-y-auto max-h-[80vh]">
<code>
<pre className="whitespace-pre-wrap break-words">
{JSON.stringify(data, null, 2)}

View File

@ -24,11 +24,12 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
cols: 80,
rows: 30,
lineHeight: 1.4,
fontWeight: 400,
convertEol: true,
theme: {
cursor: "transparent",
background: "#19191A",
background: "rgba(0, 0, 0, 0)",
},
});
@ -81,8 +82,8 @@ export const DockerLogsId: React.FC<Props> = ({ id, containerId }) => {
/>
</div>
<div className="w-full h-full bg-input rounded-lg p-2 ">
<div id={id} className="rounded-xl" />
<div className="w-full h-full rounded-lg p-2 bg-[#19191A]">
<div id={id} />
</div>
</div>
);

View File

@ -1,4 +1,3 @@
import React from "react";
import {
Dialog,
DialogContent,

View File

@ -3,7 +3,7 @@ import { Terminal } from "@xterm/xterm";
import { FitAddon } from "xterm-addon-fit";
import "@xterm/xterm/css/xterm.css";
import { AttachAddon } from "@xterm/addon-attach";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
interface Props {
id: string;
@ -12,7 +12,7 @@ interface Props {
export const DockerTerminal: React.FC<Props> = ({ id, containerId }) => {
const termRef = useRef(null);
const [activeWay, setActiveWay] = React.useState<string | null>("bash");
const [activeWay, setActiveWay] = React.useState<string | undefined>("bash");
useEffect(() => {
const container = document.getElementById(id);
if (container) {
@ -26,7 +26,7 @@ export const DockerTerminal: React.FC<Props> = ({ id, containerId }) => {
convertEol: true,
theme: {
cursor: "transparent",
background: "#19191A",
background: "rgba(0, 0, 0, 0)",
},
});
const addonFit = new FitAddon();
@ -54,14 +54,15 @@ export const DockerTerminal: React.FC<Props> = ({ id, containerId }) => {
<span>
Select way to connect to <b>{containerId}</b>
</span>
<div className="flex flex-row gap-4 w-fit">
<Button onClick={() => setActiveWay("sh")}>SH</Button>
<Button onClick={() => setActiveWay("bash")}>Bash</Button>
<Button onClick={() => setActiveWay("sh")}>/bin/sh</Button>
</div>
<Tabs value={activeWay} onValueChange={setActiveWay}>
<TabsList>
<TabsTrigger value="bash">Bash</TabsTrigger>
<TabsTrigger value="sh">/bin/sh</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div className="w-full h-full bg-input rounded-lg p-2 ">
<div id={id} ref={termRef} className="rounded-xl" />
<div className="w-full h-full rounded-lg p-2 bg-[#19191A]">
<div id={id} ref={termRef} />
</div>
</div>
);

View File

@ -11,6 +11,7 @@ import {
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect, useState } from "react";
@ -86,19 +87,12 @@ export const ShowTraefikFile = ({ path }: Props) => {
return (
<div>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full py-4 relative"
className="grid w-full relative"
>
<div className="flex flex-col">
<FormField
@ -107,10 +101,12 @@ export const ShowTraefikFile = ({ path }: Props) => {
render={({ field }) => (
<FormItem className="relative">
<FormLabel>Traefik config</FormLabel>
<FormDescription>{path}</FormDescription>
<FormDescription className="break-all">
{path}
</FormDescription>
<FormControl>
<Textarea
className="h-[35rem]"
className="h-[35rem] font-mono"
placeholder={`http:
routers:
router-name:
@ -128,8 +124,9 @@ routers:
<pre>
<FormMessage />
</pre>
<div className="flex justify-end absolute z-50 right-6 top-10">
<div className="flex justify-end absolute z-50 right-6 top-8">
<Button
className="shadow-sm"
variant="secondary"
type="button"
onClick={async () => {

View File

@ -13,7 +13,7 @@ export const ShowTraefikSystem = () => {
const { data: directories } = api.settings.readDirectories.useQuery();
return (
<div className={cn("mt-6 grid gap-4 pb-20")}>
<div className={cn("mt-6 md:grid gap-4")}>
<div className="flex flex-col md:flex-row gap-4 md:gap-10 w-full">
{directories?.length === 0 && (
<div className="w-full flex-col gap-2 flex items-center justify-center h-[55vh]">

View File

@ -95,7 +95,7 @@ export const ShowMariadbEnvironment = ({ mariadbId }: Props) => {
<FormControl>
<Textarea
placeholder="MARIADB_PASSWORD=1234567678"
className="h-96"
className="h-96 font-mono"
{...field}
/>
</FormControl>

View File

@ -18,6 +18,7 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
@ -97,14 +98,7 @@ export const UpdateMariadb = ({ mariadbId }: Props) => {
<DialogTitle>Modify MariaDB</DialogTitle>
<DialogDescription>Update the MariaDB data</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -95,7 +95,7 @@ export const ShowMongoEnvironment = ({ mongoId }: Props) => {
<FormControl>
<Textarea
placeholder="MONGO_PASSWORD=1234567678"
className="h-96"
className="h-96 font-mono"
{...field}
/>
</FormControl>

View File

@ -18,6 +18,7 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
@ -97,14 +98,7 @@ export const UpdateMongo = ({ mongoId }: Props) => {
<DialogTitle>Modify MongoDB</DialogTitle>
<DialogDescription>Update the MongoDB data</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -95,7 +95,7 @@ export const ShowMysqlEnvironment = ({ mysqlId }: Props) => {
<FormControl>
<Textarea
placeholder="MYSQL_PASSWORD=1234567678"
className="h-96"
className="h-96 font-mono"
{...field}
/>
</FormControl>

View File

@ -18,6 +18,7 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
@ -97,14 +98,7 @@ export const UpdateMysql = ({ mysqlId }: Props) => {
<DialogTitle>Modify MySQL</DialogTitle>
<DialogDescription>Update the MySQL data</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -95,7 +95,7 @@ export const ShowPostgresEnvironment = ({ postgresId }: Props) => {
<FormControl>
<Textarea
placeholder="POSTGRES_PASSWORD=1234567678"
className="h-96"
className="h-96 font-mono"
{...field}
/>
</FormControl>

View File

@ -18,6 +18,7 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
@ -97,14 +98,7 @@ export const UpdatePostgres = ({ postgresId }: Props) => {
<DialogTitle>Modify Postgres</DialogTitle>
<DialogDescription>Update the Postgres data</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -18,9 +18,10 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { AlertBlock } from "@/components/shared/alert-block";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, Folder } from "lucide-react";
import { Folder } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -93,14 +94,7 @@ export const AddApplication = ({ projectId }: Props) => {
Assign a name and description to your application
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -235,7 +235,7 @@ export const AddDatabase = ({ projectId }: Props) => {
<DialogTitle>Databases</DialogTitle>
</DialogHeader>
{/* {isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<div className="flex items-center flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}

View File

@ -19,8 +19,9 @@ import {
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, PlusIcon } from "lucide-react";
import { PlusIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
@ -85,14 +86,7 @@ export const AddProject = () => {
<DialogTitle>Add a project</DialogTitle>
<DialogDescription>The home of something big!</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-add-project"

View File

@ -48,9 +48,9 @@ export const ShowProjects = () => {
return (
<>
{data?.length === 0 && (
<div className="mt-6 flex h-[50vh] w-full flex-col items-center justify-center ">
<FolderInput className="size-10 md:size-28 text-muted" />
<span className="text-center font-medium text-muted-foreground">
<div className="mt-6 flex h-[50vh] w-full flex-col items-center justify-center space-y-4">
<FolderInput className="size-10 md:size-28 text-muted-foreground" />
<span className="text-center font-medium text-muted-foreground">
No projects added yet. Click on Create project.
</span>
</div>

View File

@ -18,6 +18,7 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
@ -101,14 +102,7 @@ export const UpdateProject = ({ projectId }: Props) => {
Update the details of the project
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -18,6 +18,7 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
@ -97,14 +98,7 @@ export const UpdateRedis = ({ redisId }: Props) => {
<DialogTitle>Modify Redis</DialogTitle>
<DialogDescription>Update the redis data</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -1,24 +1,25 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect } from "react";
@ -26,140 +27,135 @@ import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
const certificateDataHolder = `-----BEGIN CERTIFICATE-----\nMIIFRDCCAyygAwIBAgIUEPOR47ys6VDwMVB9tYoeEka83uQwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAwwObWktZG9taW5pby5jb20wHhcNMjQwMzExMDQyNzU3WhcN\n------END CERTIFICATE-----`;
const addCertificate = z.object({
name: z.string().min(1, "Name is required"),
certificateData: z.string().min(1, "Certificate data is required"),
privateKey: z.string().min(1, "Private key is required"),
autoRenew: z.boolean().optional(),
name: z.string().min(1, "Name is required"),
certificateData: z.string().min(1, "Certificate data is required"),
privateKey: z.string().min(1, "Private key is required"),
autoRenew: z.boolean().optional(),
});
type AddCertificate = z.infer<typeof addCertificate>;
export const AddCertificate = () => {
const utils = api.useUtils();
const utils = api.useUtils();
const { mutateAsync, isError, error, isLoading } =
api.certificates.create.useMutation();
const { mutateAsync, isError, error, isLoading } =
api.certificates.create.useMutation();
const form = useForm<AddCertificate>({
defaultValues: {
name: "",
certificateData: "",
privateKey: "",
autoRenew: false,
},
resolver: zodResolver(addCertificate),
});
useEffect(() => {
form.reset();
}, [form, form.formState.isSubmitSuccessful, form.reset]);
const form = useForm<AddCertificate>({
defaultValues: {
name: "",
certificateData: "",
privateKey: "",
autoRenew: false,
},
resolver: zodResolver(addCertificate),
});
useEffect(() => {
form.reset();
}, [form, form.formState.isSubmitSuccessful, form.reset]);
const onSubmit = async (data: AddCertificate) => {
await mutateAsync({
name: data.name,
certificateData: data.certificateData,
privateKey: data.privateKey,
autoRenew: data.autoRenew,
})
.then(async () => {
toast.success("Certificate Created");
await utils.certificates.all.invalidate();
})
.catch(() => {
toast.error("Error to create the Certificate");
});
};
return (
<Dialog>
<DialogTrigger className="" asChild>
<Button>Add Certificate</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Add Certificate</DialogTitle>
<DialogDescription>Add a new certificate</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
const onSubmit = async (data: AddCertificate) => {
await mutateAsync({
name: data.name,
certificateData: data.certificateData,
privateKey: data.privateKey,
autoRenew: data.autoRenew,
})
.then(async () => {
toast.success("Certificate Created");
await utils.certificates.all.invalidate();
})
.catch(() => {
toast.error("Error to create the Certificate");
});
};
return (
<Dialog>
<DialogTrigger className="" asChild>
<Button>Add Certificate</Button>
</DialogTrigger>
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
<DialogHeader>
<DialogTitle>Add Certificate</DialogTitle>
<DialogDescription>Add a new certificate</DialogDescription>
</DialogHeader>
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
id="hook-form-add-certificate"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4 "
>
<FormField
control={form.control}
name="name"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Certificate Name</FormLabel>
<FormControl>
<Input placeholder={"My Certificate"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="certificateData"
render={({ field }) => (
<FormItem>
<div className="space-y-0.5">
<FormLabel>Certificate Data</FormLabel>
</div>
<FormControl>
<Textarea
className="h-32"
placeholder={`-----BEGIN CERTIFICATE-----\nMIIFRDCCAyygAwIBAgIUEPOR47ys6VDwMVB9tYoeEka83uQwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAwwObWktZG9taW5pby5jb20wHhcNMjQwMzExMDQyNzU3WhcN\n------END CERTIFICATE-----`}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="privateKey"
render={({ field }) => (
<FormItem>
<div className="space-y-0.5">
<FormLabel>Private Key</FormLabel>
</div>
<FormControl>
<Textarea
className="h-32"
placeholder={`-----BEGIN CERTIFICATE-----\nMIIFRDCCAyygAwIBAgIUEPOR47ys6VDwMVB9tYoeEka83uQwDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAwwObWktZG9taW5pby5jb20wHhcNMjQwMzExMDQyNzU3WhcN\n------END CERTIFICATE-----`}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
<Form {...form}>
<form
id="hook-form-add-certificate"
onSubmit={form.handleSubmit(onSubmit)}
className="grid w-full gap-4 "
>
<FormField
control={form.control}
name="name"
render={({ field }) => {
return (
<FormItem>
<FormLabel>Certificate Name</FormLabel>
<FormControl>
<Input placeholder={"My Certificate"} {...field} />
</FormControl>
<FormMessage />
</FormItem>
);
}}
/>
<FormField
control={form.control}
name="certificateData"
render={({ field }) => (
<FormItem>
<div className="space-y-0.5">
<FormLabel>Certificate Data</FormLabel>
</div>
<FormControl>
<Textarea
className="h-32"
placeholder={certificateDataHolder}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="privateKey"
render={({ field }) => (
<FormItem>
<div className="space-y-0.5">
<FormLabel>Private Key</FormLabel>
</div>
<FormControl>
<Textarea
className="h-32"
placeholder={certificateDataHolder}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
<DialogFooter className="flex w-full flex-row !justify-between pt-3">
<Button
isLoading={isLoading}
form="hook-form-add-certificate"
type="submit"
>
Create
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
<DialogFooter className="flex w-full flex-row !justify-between pt-3">
<Button
isLoading={isLoading}
form="hook-form-add-certificate"
type="submit"
>
Create
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@ -18,8 +18,8 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -87,14 +87,7 @@ export const AddDestination = () => {
In this section you can add destinations for your backups.
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -18,8 +18,9 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, PenBoxIcon } from "lucide-react";
import { PenBoxIcon } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -111,14 +112,7 @@ export const UpdateDestination = ({ destinationId }: Props) => {
Update the current destination config
</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -21,8 +21,9 @@ import {
import { Switch } from "@/components/ui/switch";
import { extractServices } from "@/pages/dashboard/project/[projectId]";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle, ListTodo } from "lucide-react";
import { ListTodo } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -115,14 +116,7 @@ export const AddUserPermissions = ({ userId }: Props) => {
<DialogTitle>Permissions</DialogTitle>
<DialogDescription>Add or remove permissions</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -19,8 +19,8 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -73,14 +73,7 @@ export const AddUser = () => {
<DialogTitle>Add User</DialogTitle>
<DialogDescription>Invite a new user</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form

View File

@ -47,7 +47,7 @@ export const ShowUsers = () => {
key={user.userId}
className="flex gap-2 flex-col justify-start border p-4 rounded-lg"
>
<span className="text-sm text-muted-foreground">
<span className="text-sm text-foreground">
{user.auth.email}
</span>
{!user.isRegistered && (

View File

@ -18,11 +18,12 @@ import {
} from "@/components/ui/form";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect } from "react";
import { toast } from "sonner";
import { Input } from "@/components/ui/input";
import { AlertTriangle, SquarePen } from "lucide-react";
import { SquarePen } from "lucide-react";
import { api } from "@/utils/api";
const updateUserSchema = z.object({
@ -100,14 +101,7 @@ export const UpdateUser = ({ authId }: Props) => {
<DialogTitle>Update User</DialogTitle>
<DialogDescription>Update the user</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<div className="grid gap-4">
<div className="grid items-center gap-4">

View File

@ -18,8 +18,8 @@ import {
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -89,14 +89,7 @@ export const ShowMainTraefikConfig = ({ children }: Props) => {
<DialogTitle>Update traefik config</DialogTitle>
<DialogDescription>Update the traefik config</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
@ -113,7 +106,7 @@ export const ShowMainTraefikConfig = ({ children }: Props) => {
<FormLabel>Traefik config</FormLabel>
<FormControl>
<Textarea
className="h-[35rem]"
className="h-[35rem] font-mono"
placeholder={`providers:
docker:
defaultRule: 'Host('dokploy.com')'
@ -136,8 +129,10 @@ export const ShowMainTraefikConfig = ({ children }: Props) => {
<pre>
<FormMessage />
</pre>
<div className="flex justify-end absolute z-50 right-6 top-10">
<div className="flex justify-end absolute z-50 right-6 top-0">
<Button
className="shadow-sm"
variant="secondary"
type="button"
onClick={async () => {
setCanEdit(!canEdit);

View File

@ -18,8 +18,8 @@ import {
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -92,14 +92,7 @@ export const ShowServerMiddlewareConfig = ({ children }: Props) => {
<DialogTitle>Update Middleware config</DialogTitle>
<DialogDescription>Update the middleware config</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
@ -116,7 +109,7 @@ export const ShowServerMiddlewareConfig = ({ children }: Props) => {
<FormLabel>Traefik config</FormLabel>
<FormControl>
<Textarea
className="h-[35rem]"
className="h-[35rem] font-mono"
placeholder={`http:
routers:
router-name:
@ -134,8 +127,10 @@ routers:
<pre>
<FormMessage />
</pre>
<div className="flex justify-end absolute z-50 right-6 top-10">
<div className="flex justify-end absolute z-50 right-6 top-0">
<Button
className="shadow-sm"
variant="secondary"
type="button"
onClick={async () => {
setCanEdit(!canEdit);

View File

@ -18,8 +18,8 @@ import {
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { api } from "@/utils/api";
import { AlertBlock } from "@/components/shared/alert-block";
import { zodResolver } from "@hookform/resolvers/zod";
import { AlertTriangle } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
@ -92,14 +92,7 @@ export const ShowServerTraefikConfig = ({ children }: Props) => {
<DialogTitle>Update traefik config</DialogTitle>
<DialogDescription>Update the traefik config</DialogDescription>
</DialogHeader>
{isError && (
<div className="flex flex-row gap-4 rounded-lg bg-red-50 p-2 dark:bg-red-950">
<AlertTriangle className="text-red-600 dark:text-red-400" />
<span className="text-sm text-red-600 dark:text-red-400">
{error?.message}
</span>
</div>
)}
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
<Form {...form}>
<form
@ -116,7 +109,7 @@ export const ShowServerTraefikConfig = ({ children }: Props) => {
<FormLabel>Traefik config</FormLabel>
<FormControl>
<Textarea
className="h-[35rem]"
className="h-[35rem] font-mono"
placeholder={`http:
routers:
router-name:
@ -134,8 +127,10 @@ routers:
<pre>
<FormMessage />
</pre>
<div className="flex justify-end absolute z-50 right-6 top-10">
<div className="flex justify-end absolute z-50 right-6 top-0">
<Button
className="shadow-sm"
variant="secondary"
type="button"
onClick={async () => {
setCanEdit(!canEdit);

View File

@ -10,19 +10,15 @@ export const DashboardLayout = ({ children, tab }: Props) => {
return (
<div>
<div
className="bg-radial relative flex flex-col bg-background pt-6"
className="bg-radial relative flex flex-col bg-background min-h-screen w-full"
id="app-container"
>
<div className="flex items-center justify-center">
<div className="w-full">
<Navbar />
<main className="mt-6 flex w-full flex-col items-center">
<div className="w-full max-w-8xl px-4 lg:px-8">
<NavigationTabs tab={tab}>{children}</NavigationTabs>
</div>
</main>
<Navbar />
<main className="pt-6 flex w-full flex-col items-center">
<div className="w-full max-w-8xl px-4 lg:px-8">
<NavigationTabs tab={tab}>{children}</NavigationTabs>
</div>
</div>
</main>
</div>
</div>
);

View File

@ -29,7 +29,7 @@ export const Navbar = () => {
const { mutateAsync } = api.auth.logout.useMutation();
return (
<nav className="border-divider sticky inset-x-0 top-0 z-40 flex h-auto w-full items-center justify-center border-b bg-background/70 backdrop-blur-lg backdrop-saturate-150 data-[menu-open=true]:border-none data-[menu-open=true]:backdrop-blur-xl">
<header className="relative z-40 flex h-[var(--navbar-height)] w-full max-w-8xl flex-row flex-nowrap items-center justify-between gap-4 px-4 sm:px-6">
<header className="relative z-40 flex w-full max-w-8xl flex-row flex-nowrap items-center justify-between gap-4 px-4 sm:px-6 h-16">
<div className="text-medium box-border flex flex-grow basis-0 flex-row flex-nowrap items-center justify-start whitespace-nowrap bg-transparent no-underline">
<Link
href="/dashboard/projects"

View File

@ -1,8 +1,18 @@
import { AddProject } from "@/components/dashboard/projects/add";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useRouter } from "next/router";
import { api } from "@/utils/api";
import type { Auth } from "@/server/api/services/auth";
import type { User } from "@/server/api/services/user";
interface TabInfo {
label: string;
tabLabel?: string;
description: string;
index: string;
isShow?: ({ rol, user }: { rol?: Auth["rol"]; user?: User }) => boolean;
}
export type TabState =
| "projects"
@ -11,6 +21,41 @@ export type TabState =
| "traefik"
| "docker";
const tabMap: Record<TabState, TabInfo> = {
projects: {
label: "Projects",
description: "Manage your projects",
index: "/dashboard/projects",
},
monitoring: {
label: "Monitoring",
description: "Monitor your projects",
index: "/dashboard/monitoring",
},
traefik: {
label: "Traefik",
tabLabel: "Traefik File System",
description: "Manage your traefik",
index: "/dashboard/traefik",
isShow: ({ rol, user }) => {
return Boolean(rol === "admin" || user?.canAccessToTraefikFiles);
},
},
docker: {
label: "Docker",
description: "Manage your docker",
index: "/dashboard/docker",
isShow: ({ rol, user }) => {
return Boolean(rol === "admin" || user?.canAccessToDocker);
},
},
settings: {
label: "Settings",
description: "Manage your settings",
index: "/dashboard/settings/server",
},
};
interface Props {
tab: TabState;
children: React.ReactNode;
@ -34,23 +79,19 @@ export const NavigationTabs = ({ tab, children }: Props) => {
setActiveTab(tab);
}, [tab]);
const activeTabInfo = useMemo(() => {
return tabMap[activeTab];
}, [activeTab]);
return (
<div className="gap-12 min-h-screen">
<div className="gap-12">
<header className="mb-6 flex w-full items-center gap-2 justify-between flex-wrap">
<div className="flex flex-col gap-2">
<h1 className="text-xl font-bold lg:text-3xl">
{tab === "projects" && "Projects"}
{tab === "monitoring" && "Monitoring"}
{tab === "settings" && "Settings"}
{tab === "traefik" && "Traefik"}
{tab === "docker" && "Docker"}
{activeTabInfo.label}
</h1>
<p className="lg:text-medium text-muted-foreground">
{tab === "projects" && "Manage your deployments"}
{tab === "monitoring" && "Watch the usage of your server"}
{tab === "settings" && "Check the configuration"}
{tab === "traefik" && "Read the traefik config and update it"}
{tab === "docker" && "Manage the docker containers"}
{activeTabInfo.description}
</p>
</div>
{tab === "projects" &&
@ -60,54 +101,36 @@ export const NavigationTabs = ({ tab, children }: Props) => {
<Tabs
value={activeTab}
className="w-full"
onValueChange={(e) => {
if (e === "settings") {
router.push("/dashboard/settings/server");
} else {
router.push(`/dashboard/${e}`);
}
onValueChange={async (e) => {
setActiveTab(e as TabState);
router.push(tabMap[e as TabState].index);
}}
>
{/* className="grid w-fit grid-cols-4 bg-transparent" */}
<div className="flex flex-row items-center justify-between w-full gap-4 max-sm:overflow-x-auto">
<TabsList className="md:grid md:w-fit md:grid-cols-5 justify-start bg-transparent">
<TabsTrigger
value="projects"
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
Projects
</TabsTrigger>
<TabsTrigger
value="monitoring"
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
Monitoring
</TabsTrigger>
{(data?.rol === "admin" || user?.canAccessToTraefikFiles) && (
<TabsTrigger
value="traefik"
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
Traefik File System
</TabsTrigger>
)}
{(data?.rol === "admin" || user?.canAccessToDocker) && (
<TabsTrigger
value="docker"
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
Docker
</TabsTrigger>
)}
<TabsTrigger
value="settings"
className="rounded-none border-b-2 border-b-transparent data-[state=active]:border-b-2 data-[state=active]:border-b-border"
>
Settings
</TabsTrigger>
<div className="flex flex-row items-center justify-between w-full gap-4 max-sm:overflow-x-auto border-b border-b-divider pb-1">
<TabsList className="bg-transparent relative px-0">
{Object.keys(tabMap).map((key) => {
const tab = tabMap[key as TabState];
if (tab.isShow && !tab.isShow?.({ rol: data?.rol, user })) {
return null;
}
return (
<TabsTrigger
key={key}
value={key}
className="relative py-2.5 md:px-5 data-[state=active]:shadow-none data-[state=active]:bg-transparent rounded-md hover:bg-zinc-100 hover:dark:bg-zinc-800 data-[state=active]:hover:bg-zinc-100 data-[state=active]:hover:dark:bg-zinc-800"
>
<span className="relative z-[1] w-full">
{tab.tabLabel || tab.label}
</span>
{key === activeTab && (
<div className="absolute -bottom-[5.5px] w-full">
<div className="h-0.5 bg-foreground rounded-t-md" />
</div>
)}
</TabsTrigger>
);
})}
</TabsList>
</div>

View File

@ -0,0 +1,51 @@
interface Props extends React.ComponentPropsWithoutRef<"div"> {
icon?: React.ReactNode;
type?: "info" | "success" | "warning" | "error";
}
import { cn } from "@/lib/utils";
import { AlertTriangle, AlertCircle, CheckCircle2, Info } from "lucide-react";
const iconMap = {
info: {
className: "bg-blue-50 dark:bg-blue-950 text-blue-600 dark:text-blue-400",
icon: Info,
},
success: {
className:
"bg-green-50 dark:bg-green-950 text-green-600 dark:text-green-400",
icon: CheckCircle2,
},
warning: {
className:
"bg-orange-50 dark:bg-orange-950 text-orange-600 dark:text-orange-400",
icon: AlertCircle,
},
error: {
className: "bg-red-50 dark:bg-red-950 text-red-600 dark:text-red-400",
icon: AlertTriangle,
},
};
export function AlertBlock({
type = "info",
icon,
children,
className,
...props
}: Props) {
const { className: iconClassName, icon: Icon } = iconMap[type];
return (
<div
{...props}
className={cn(
"flex items-center flex-row gap-4 rounded-lg p-2",
iconClassName,
className,
)}
>
{icon || <Icon className="text-current" />}
<span className="text-sm text-current">{children}</span>
</div>
);
}

View File

@ -17,21 +17,21 @@ export const StatusTooltip = ({ status, className }: Props) => {
<Tooltip>
<TooltipTrigger>
{status === "idle" && (
<div className={cn(" size-3.5 rounded-full bg-card", className)} />
<div className={cn("size-3.5 rounded-full bg-muted-foreground dark:bg-card", className)} />
)}
{status === "error" && (
<div
className={cn(" size-3.5 rounded-full bg-destructive", className)}
className={cn("size-3.5 rounded-full bg-destructive", className)}
/>
)}
{status === "done" && (
<div
className={cn(" size-3.5 rounded-full bg-primary", className)}
className={cn("size-3.5 rounded-full bg-primary", className)}
/>
)}
{status === "running" && (
<div
className={cn(" size-3.5 rounded-full bg-yellow-500", className)}
className={cn("size-3.5 rounded-full bg-yellow-500", className)}
/>
)}
</TooltipTrigger>

59
components/ui/alert.tsx Normal file
View File

@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }

View File

@ -165,7 +165,7 @@ const TreeItem = React.forwardRef<HTMLDivElement, TreeItemProps>(
aria-hidden="true"
/>
)}
<span className="text-sm truncate">{item.name}</span>
<span className="text-sm truncate font-mono">{item.name}</span>
</AccordionTrigger>
<AccordionContent className="pl-6">
{item.children.length === 0 && (
@ -244,7 +244,7 @@ const Leaf = React.forwardRef<
aria-hidden="true"
/>
)}
<p className=" text-sm whitespace-normal">{item.name}</p>
<p className=" text-sm whitespace-normal font-mono">{item.name}</p>
</div>
);
});

View File

@ -103,7 +103,6 @@
"zod": "^3.23.4"
},
"devDependencies": {
"xterm-readline": "1.1.1",
"@biomejs/biome": "1.7.1",
"@types/bcrypt": "5.0.2",
"@types/dockerode": "3.3.23",
@ -125,7 +124,8 @@
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1",
"tsx": "^4.7.0",
"typescript": "^5.4.2"
"typescript": "^5.4.2",
"xterm-readline": "1.1.1"
},
"ct3aMetadata": {
"initVersion": "7.25.2"

View File

@ -1,44 +1,53 @@
import { Toaster } from "@/components/ui/sonner";
import "@/styles/globals.css";
import { Toaster } from "@/components/ui/sonner";
import { api } from "@/utils/api";
import type { NextPage } from "next";
import { ThemeProvider } from "next-themes";
import type { AppProps } from "next/app";
import { Inter } from "next/font/google";
import Head from "next/head";
import type { ReactElement, ReactNode } from "react";
const inter = Inter({ subsets: ["latin"] });
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
// session: Session | null;
theme?: string;
getLayout?: (page: ReactElement) => ReactNode;
// session: Session | null;
theme?: string;
};
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
Component: NextPageWithLayout;
};
const MyApp = ({
Component,
pageProps: { ...pageProps },
Component,
pageProps: { ...pageProps },
}: AppPropsWithLayout) => {
const getLayout = Component.getLayout ?? ((page) => page);
return (
<>
<Head>
<title>Dokploy</title>
</Head>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
forcedTheme={Component.theme}
>
<Toaster richColors />
{getLayout(<Component {...pageProps} />)}
</ThemeProvider>
</>
);
const getLayout = Component.getLayout ?? ((page) => page);
return (
<>
<style jsx global>{`
:root {
--font-inter: ${inter.style.fontFamily};
}
`}</style>
<Head>
<title>Dokploy</title>
</Head>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
forcedTheme={Component.theme}
>
<Toaster richColors />
{getLayout(<Component {...pageProps} />)}
</ThemeProvider>
</>
);
};
export default api.withTRPC(MyApp);

View File

@ -1,18 +1,11 @@
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head>
<link rel="shortcut icon" href="/icon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@100;400;500;600;700&display=swap"
rel="stylesheet"
/>
</Head>
<Html lang="en" className="font-sans">
<Head />
<body className="flex h-full flex-col ">
<body className="flex h-full flex-col font-sans">
<Main />
<NextScript />
</body>

View File

@ -152,11 +152,18 @@ export const adminRouter = createTRPCRouter({
installationId: admin.githubInstallationId,
},
});
const branches = await octokit.rest.repos.listBranches({
owner: input.owner,
repo: input.repo,
});
return branches.data;
const branches = (await octokit.paginate(
octokit.rest.repos.listBranches,
{
owner: input.owner,
repo: input.repo,
},
)) as unknown as Awaited<
ReturnType<typeof octokit.rest.repos.listBranches>
>["data"];
return branches;
}),
haveGithubConfigured: protectedProcedure.query(async () => {
const adminResponse = await findAdmin();

View File

@ -2,11 +2,6 @@
@tailwind components;
@tailwind utilities;
*,
html {
font-family: "Inter", sans-serif;
}
@layer base {
:root {
--background: 0 0% 100%;

View File

@ -1,4 +1,5 @@
import type { Config } from "tailwindcss";
import defaultTheme from "tailwindcss/defaultTheme";
const config = {
darkMode: ["class"],
@ -18,6 +19,9 @@ const config = {
},
},
extend: {
fontFamily: {
sans: ["var(--font-inter)", ...defaultTheme.fontFamily.sans],
},
maxWidth: {
"2xl": "40rem",
"8xl": "85rem",