mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge branch 'canary' into 379-preview-deployment
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
@@ -150,7 +150,7 @@ export const AddVolumes = ({
|
||||
<DialogTrigger className="" asChild>
|
||||
<Button>{children}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Volumes / Mounts</DialogTitle>
|
||||
</DialogHeader>
|
||||
@@ -303,9 +303,12 @@ export const AddVolumes = ({
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Any content"
|
||||
className="h-64"
|
||||
<CodeEditor
|
||||
language="properties"
|
||||
placeholder={`NODE_ENV=production
|
||||
PORT=3000
|
||||
`}
|
||||
className="h-96 font-mono"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { CodeEditor } from "@/components/shared/code-editor";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { api } from "@/utils/api";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Pencil } from "lucide-react";
|
||||
@@ -119,7 +118,7 @@ export const UpdateVolume = ({
|
||||
} else if (typeForm === "file") {
|
||||
form.reset({
|
||||
content: data.content || "",
|
||||
mountPath: "/",
|
||||
mountPath: serviceType === "compose" ? "/" : data.mountPath,
|
||||
filePath: data.filePath || "",
|
||||
type: "file",
|
||||
});
|
||||
@@ -182,7 +181,7 @@ export const UpdateVolume = ({
|
||||
<Pencil className="size-4 text-muted-foreground" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update</DialogTitle>
|
||||
<DialogDescription>Update the mount</DialogDescription>
|
||||
@@ -247,9 +246,12 @@ export const UpdateVolume = ({
|
||||
<FormLabel>Content</FormLabel>
|
||||
<FormControl>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="Any content"
|
||||
className="h-64"
|
||||
<CodeEditor
|
||||
language="properties"
|
||||
placeholder={`NODE_ENV=production
|
||||
PORT=3000
|
||||
`}
|
||||
className="h-96 font-mono"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -41,6 +41,7 @@ const mySchema = z.discriminatedUnion("buildType", [
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("heroku_buildpacks"),
|
||||
herokuVersion: z.string().nullable().default(""),
|
||||
}),
|
||||
z.object({
|
||||
buildType: z.literal("paketo_buildpacks"),
|
||||
@@ -90,6 +91,13 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
dockerBuildStage: data.dockerBuildStage || "",
|
||||
}),
|
||||
});
|
||||
} else if (data.buildType === "heroku_buildpacks") {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
...(data.buildType && {
|
||||
herokuVersion: data.herokuVersion || "",
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
form.reset({
|
||||
buildType: data.buildType,
|
||||
@@ -110,6 +118,8 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
data.buildType === "dockerfile" ? data.dockerContextPath : null,
|
||||
dockerBuildStage:
|
||||
data.buildType === "dockerfile" ? data.dockerBuildStage : null,
|
||||
herokuVersion:
|
||||
data.buildType === "heroku_buildpacks" ? data.herokuVersion : null,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Build type saved");
|
||||
@@ -200,6 +210,28 @@ export const ShowBuildChooseForm = ({ applicationId }: Props) => {
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{buildType === "heroku_buildpacks" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="herokuVersion"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Heroku Version (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={"Heroku Version (Default: 24)"}
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{buildType === "dockerfile" && (
|
||||
<>
|
||||
<FormField
|
||||
|
||||
@@ -2,9 +2,9 @@ import { ShowBuildChooseForm } from "@/components/dashboard/application/build/sh
|
||||
import { ShowProviderForm } from "@/components/dashboard/application/general/generic/show";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { CheckCircle2, Terminal } from "lucide-react";
|
||||
import { Terminal } from "lucide-react";
|
||||
import React from "react";
|
||||
import { toast } from "sonner";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
@@ -39,27 +39,6 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
||||
appName={data?.appName || ""}
|
||||
/>
|
||||
|
||||
<Toggle
|
||||
aria-label="Toggle italic"
|
||||
pressed={data?.autoDeploy || false}
|
||||
onPressedChange={async (enabled) => {
|
||||
await update({
|
||||
applicationId,
|
||||
autoDeploy: enabled,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Auto Deploy Updated");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update Auto Deploy");
|
||||
});
|
||||
}}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
>
|
||||
Autodeploy
|
||||
{data?.autoDeploy && <CheckCircle2 className="size-4" />}
|
||||
</Toggle>
|
||||
<RedbuildApplication applicationId={applicationId} />
|
||||
{data?.applicationStatus === "idle" ? (
|
||||
<StartApplication applicationId={applicationId} />
|
||||
@@ -75,6 +54,27 @@ export const ShowGeneralApplication = ({ applicationId }: Props) => {
|
||||
Open Terminal
|
||||
</Button>
|
||||
</DockerTerminalModal>
|
||||
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
||||
<span className="text-sm font-medium">Autodeploy</span>
|
||||
<Switch
|
||||
aria-label="Toggle italic"
|
||||
checked={data?.autoDeploy || false}
|
||||
onCheckedChange={async (enabled) => {
|
||||
await update({
|
||||
applicationId,
|
||||
autoDeploy: enabled,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Auto Deploy Updated");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update Auto Deploy");
|
||||
});
|
||||
}}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<ShowProviderForm applicationId={applicationId} />
|
||||
|
||||
@@ -8,12 +8,13 @@ import {
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Toggle } from "@/components/ui/toggle";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { api } from "@/utils/api";
|
||||
import { CheckCircle2, ExternalLink, Globe, Terminal } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { toast } from "sonner";
|
||||
import { DockerTerminalModal } from "../../settings/web-server/docker-terminal-modal";
|
||||
import { StartCompose } from "../start-compose";
|
||||
import { DeployCompose } from "./deploy-compose";
|
||||
import { RedbuildCompose } from "./rebuild-compose";
|
||||
import { StopCompose } from "./stop-compose";
|
||||
@@ -50,28 +51,11 @@ export const ComposeActions = ({ composeId }: Props) => {
|
||||
return (
|
||||
<div className="flex flex-row gap-4 w-full flex-wrap ">
|
||||
<DeployCompose composeId={composeId} />
|
||||
<Toggle
|
||||
aria-label="Toggle italic"
|
||||
pressed={data?.autoDeploy || false}
|
||||
onPressedChange={async (enabled) => {
|
||||
await update({
|
||||
composeId,
|
||||
autoDeploy: enabled,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Auto Deploy Updated");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update Auto Deploy");
|
||||
});
|
||||
}}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
>
|
||||
Autodeploy {data?.autoDeploy && <CheckCircle2 className="size-4" />}
|
||||
</Toggle>
|
||||
<RedbuildCompose composeId={composeId} />
|
||||
{data?.composeType === "docker-compose" && (
|
||||
{data?.composeType === "docker-compose" &&
|
||||
data?.composeStatus === "idle" ? (
|
||||
<StartCompose composeId={composeId} />
|
||||
) : (
|
||||
<StopCompose composeId={composeId} />
|
||||
)}
|
||||
|
||||
@@ -84,6 +68,27 @@ export const ComposeActions = ({ composeId }: Props) => {
|
||||
Open Terminal
|
||||
</Button>
|
||||
</DockerTerminalModal>
|
||||
<div className="flex flex-row items-center gap-2 rounded-md px-4 py-2 border">
|
||||
<span className="text-sm font-medium">Autodeploy</span>
|
||||
<Switch
|
||||
aria-label="Toggle italic"
|
||||
checked={data?.autoDeploy || false}
|
||||
onCheckedChange={async (enabled) => {
|
||||
await update({
|
||||
composeId,
|
||||
autoDeploy: enabled,
|
||||
})
|
||||
.then(async () => {
|
||||
toast.success("Auto Deploy Updated");
|
||||
await refetch();
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to update Auto Deploy");
|
||||
});
|
||||
}}
|
||||
className="flex flex-row gap-2 items-center"
|
||||
/>
|
||||
</div>
|
||||
{domains.length > 0 && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
||||
65
apps/dokploy/components/dashboard/compose/start-compose.tsx
Normal file
65
apps/dokploy/components/dashboard/compose/start-compose.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { CheckCircle2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
export const StartCompose = ({ composeId }: Props) => {
|
||||
const { mutateAsync, isLoading } = api.compose.start.useMutation();
|
||||
const utils = api.useUtils();
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="secondary" isLoading={isLoading}>
|
||||
Start
|
||||
<CheckCircle2 className="size-4" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
Are you sure to start the compose?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will start the compose
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
composeId,
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.compose.one.invalidate({
|
||||
composeId,
|
||||
});
|
||||
toast.success("Compose started succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to start the Compose");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
65
apps/dokploy/components/dashboard/compose/stop-compose.tsx
Normal file
65
apps/dokploy/components/dashboard/compose/stop-compose.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { api } from "@/utils/api";
|
||||
import { Ban } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Props {
|
||||
composeId: string;
|
||||
}
|
||||
|
||||
export const StopCompose = ({ composeId }: Props) => {
|
||||
const { mutateAsync, isLoading } = api.compose.stop.useMutation();
|
||||
const utils = api.useUtils();
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="destructive" isLoading={isLoading}>
|
||||
Stop
|
||||
<Ban className="size-4" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
Are you absolutely sure to stop the compose?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will stop the compose
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
await mutateAsync({
|
||||
composeId,
|
||||
})
|
||||
.then(async () => {
|
||||
await utils.compose.one.invalidate({
|
||||
composeId,
|
||||
});
|
||||
toast.success("Compose stopped succesfully");
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Error to stop the Compose");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useState } from "react";
|
||||
|
||||
const Terminal = dynamic(
|
||||
() => import("./docker-terminal").then((e) => e.DockerTerminal),
|
||||
@@ -27,8 +30,27 @@ export const DockerTerminalModal = ({
|
||||
containerId,
|
||||
serverId,
|
||||
}: Props) => {
|
||||
const [mainDialogOpen, setMainDialogOpen] = useState(false);
|
||||
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
||||
|
||||
const handleMainDialogOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
setConfirmDialogOpen(true);
|
||||
} else {
|
||||
setMainDialogOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
setConfirmDialogOpen(false);
|
||||
setMainDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setConfirmDialogOpen(false);
|
||||
};
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="w-full cursor-pointer space-x-3"
|
||||
@@ -50,6 +72,24 @@ export const DockerTerminalModal = ({
|
||||
containerId={containerId}
|
||||
serverId={serverId || ""}
|
||||
/>
|
||||
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Are you sure you want to close the terminal?
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
By clicking the confirm button, the terminal will be closed.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleConfirm}>Confirm</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -49,8 +49,11 @@ export const columns: ColumnDef<LogEntry>[] = [
|
||||
const log = row.original;
|
||||
return (
|
||||
<div className=" flex flex-col gap-2">
|
||||
<div className="flex flex-row gap-3 ">
|
||||
<div className="flex items-center flex-row gap-3 ">
|
||||
{log.RequestMethod}{" "}
|
||||
<div className="inline-flex items-center gap-2 bg-muted p-1 rounded">
|
||||
<span>{log.RequestAddr}</span>
|
||||
</div>
|
||||
{log.RequestPath.length > 100
|
||||
? `${log.RequestPath.slice(0, 82)}...`
|
||||
: log.RequestPath}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Languages } from "@/lib/languages";
|
||||
import useLocale from "@/utils/hooks/use-locale";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useTheme } from "next-themes";
|
||||
@@ -37,12 +38,9 @@ const appearanceFormSchema = z.object({
|
||||
theme: z.enum(["light", "dark", "system"], {
|
||||
required_error: "Please select a theme.",
|
||||
}),
|
||||
language: z.enum(
|
||||
["en", "pl", "ru", "fr", "de", "tr", "zh-Hant", "zh-Hans", "fa"],
|
||||
{
|
||||
required_error: "Please select a language.",
|
||||
},
|
||||
),
|
||||
language: z.nativeEnum(Languages, {
|
||||
required_error: "Please select a language.",
|
||||
}),
|
||||
});
|
||||
|
||||
type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||
@@ -50,7 +48,7 @@ type AppearanceFormValues = z.infer<typeof appearanceFormSchema>;
|
||||
// This can come from your database or API.
|
||||
const defaultValues: Partial<AppearanceFormValues> = {
|
||||
theme: "system",
|
||||
language: "en",
|
||||
language: Languages.English,
|
||||
};
|
||||
|
||||
export function AppearanceForm() {
|
||||
@@ -175,24 +173,15 @@ export function AppearanceForm() {
|
||||
<SelectValue placeholder="No preset selected" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "Polski", value: "pl" },
|
||||
{ label: "Русский", value: "ru" },
|
||||
{ label: "Français", value: "fr" },
|
||||
{ label: "Deutsch", value: "de" },
|
||||
{ label: "繁體中文", value: "zh-Hant" },
|
||||
{ label: "简体中文", value: "zh-Hans" },
|
||||
{ label: "Türkçe", value: "tr" },
|
||||
{
|
||||
label: "Persian",
|
||||
value: "fa",
|
||||
},
|
||||
].map((preset) => (
|
||||
<SelectItem key={preset.label} value={preset.value}>
|
||||
{preset.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
{Object.keys(Languages).map((preset) => {
|
||||
const value =
|
||||
Languages[preset as keyof typeof Languages];
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{preset}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
@@ -667,7 +667,7 @@ export const AddNotification = () => {
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>Dokploy Restart</FormLabel>
|
||||
<FormDescription>
|
||||
Trigger the action when a dokploy is restarted.
|
||||
Trigger the action when dokploy is restarted.
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
@@ -49,14 +51,34 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
|
||||
},
|
||||
);
|
||||
const [containerId, setContainerId] = useState<string | undefined>();
|
||||
const [mainDialogOpen, setMainDialogOpen] = useState(false);
|
||||
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
||||
|
||||
const handleMainDialogOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
setConfirmDialogOpen(true);
|
||||
} else {
|
||||
setMainDialogOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
setConfirmDialogOpen(false);
|
||||
setMainDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setConfirmDialogOpen(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data?.length > 0) {
|
||||
setContainerId(data[0]?.containerId);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog open={mainDialogOpen} onOpenChange={handleMainDialogOpenChange}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-7xl">
|
||||
<DialogHeader>
|
||||
@@ -96,6 +118,24 @@ export const DockerTerminalModal = ({ children, appName, serverId }: Props) => {
|
||||
id="terminal"
|
||||
containerId={containerId || "select-a-container"}
|
||||
/>
|
||||
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Are you sure you want to close the terminal?
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
By clicking the confirm button, the terminal will be closed.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleConfirm}>Confirm</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user