mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge branch 'Dokploy:canary' into canary
This commit is contained in:
commit
7e29e9e0d6
3
.gitignore
vendored
3
.gitignore
vendored
@ -53,4 +53,5 @@ yarn-error.log*
|
||||
/.data
|
||||
/.main
|
||||
|
||||
*.lockb
|
||||
*.lockb
|
||||
*.rdb
|
||||
|
15
LICENSE.MD
15
LICENSE.MD
@ -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.
|
||||
|
@ -61,7 +61,7 @@ export const Login2FA = ({ authId }: Props) => {
|
||||
id: authId,
|
||||
})
|
||||
.then(() => {
|
||||
toast.success("Signin succesfully", {
|
||||
toast.success("Signin successfully", {
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
|
@ -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(() => {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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)}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 () => {
|
||||
|
@ -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]">
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 && (
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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"
|
||||
|
@ -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>
|
||||
|
||||
|
51
components/shared/alert-block.tsx
Normal file
51
components/shared/alert-block.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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
59
components/ui/alert.tsx
Normal 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 }
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -2,11 +2,6 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
*,
|
||||
html {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user