Merge pull request #1221 from Dokploy/1170-0176-copy-to-clipboard-stopped-working-on-the-project-delete-confirm-dialog

refactor: add back delete with confirmation
This commit is contained in:
Mauricio Siu 2025-01-26 17:45:03 -06:00 committed by GitHub
commit 46ddaa68fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 102 additions and 216 deletions

View File

@ -98,8 +98,12 @@ export const ShowDomains = ({ applicationId }: Props) => {
applicationId={applicationId}
domainId={item.domainId}
>
<Button variant="ghost">
<PenBoxIcon className="size-4 text-muted-foreground" />
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</AddDomain>
<DialogAction

View File

@ -20,9 +20,10 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { api } from "@/utils/api";
import type { ServiceType } from "@dokploy/server/db/schema";
import { zodResolver } from "@hookform/resolvers/zod";
import copy from "copy-to-clipboard";
import { Copy, Trash2 } from "lucide-react";
import { TrashIcon } from "lucide-react";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
@ -39,16 +40,42 @@ const deleteComposeSchema = z.object({
type DeleteCompose = z.infer<typeof deleteComposeSchema>;
interface Props {
composeId: string;
id: string;
type: ServiceType | "application";
}
export const DeleteCompose = ({ composeId }: Props) => {
export const DeleteService = ({ id, type }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const { mutateAsync, isLoading } = api.compose.delete.useMutation();
const { data } = api.compose.one.useQuery(
{ composeId },
{ enabled: !!composeId },
);
const queryMap = {
postgres: () =>
api.postgres.one.useQuery({ postgresId: id }, { enabled: !!id }),
redis: () => api.redis.one.useQuery({ redisId: id }, { enabled: !!id }),
mysql: () => api.mysql.one.useQuery({ mysqlId: id }, { enabled: !!id }),
mariadb: () =>
api.mariadb.one.useQuery({ mariadbId: id }, { enabled: !!id }),
application: () =>
api.application.one.useQuery({ applicationId: id }, { enabled: !!id }),
mongo: () => api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id }),
compose: () =>
api.compose.one.useQuery({ composeId: id }, { enabled: !!id }),
};
const { data, refetch } = queryMap[type]
? queryMap[type]()
: api.mongo.one.useQuery({ mongoId: id }, { enabled: !!id });
const mutationMap = {
postgres: () => api.postgres.remove.useMutation(),
redis: () => api.redis.remove.useMutation(),
mysql: () => api.mysql.remove.useMutation(),
mariadb: () => api.mariadb.remove.useMutation(),
application: () => api.application.delete.useMutation(),
mongo: () => api.mongo.remove.useMutation(),
compose: () => api.compose.delete.useMutation(),
};
const { mutateAsync, isLoading } = mutationMap[type]
? mutationMap[type]()
: api.mongo.remove.useMutation();
const { push } = useRouter();
const form = useForm<DeleteCompose>({
defaultValues: {
@ -62,14 +89,23 @@ export const DeleteCompose = ({ composeId }: Props) => {
const expectedName = `${data?.name}/${data?.appName}`;
if (formData.projectName === expectedName) {
const { deleteVolumes } = formData;
await mutateAsync({ composeId, deleteVolumes })
await mutateAsync({
mongoId: id || "",
postgresId: id || "",
redisId: id || "",
mysqlId: id || "",
mariadbId: id || "",
applicationId: id || "",
composeId: id || "",
deleteVolumes,
})
.then((result) => {
push(`/dashboard/project/${result?.projectId}`);
toast.success("Compose deleted successfully");
toast.success("deleted successfully");
setIsOpen(false);
})
.catch(() => {
toast.error("Error deleting the compose");
toast.error("Error deleting the service");
});
} else {
form.setError("projectName", {
@ -95,8 +131,8 @@ export const DeleteCompose = ({ composeId }: Props) => {
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete the
compose. If you are sure please enter the compose name to delete
this compose.
service. If you are sure please enter the service name to delete
this service.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4">
@ -119,9 +155,7 @@ export const DeleteCompose = ({ composeId }: Props) => {
variant="outline"
onClick={() => {
if (data?.name && data?.appName) {
navigator.clipboard.writeText(
`${data.name}/${data.appName}`,
);
copy(`${data.name}/${data.appName}`);
toast.success("Copied to clipboard. Be careful!");
}
}}
@ -142,27 +176,29 @@ export const DeleteCompose = ({ composeId }: Props) => {
</FormItem>
)}
/>
<FormField
control={form.control}
name="deleteVolumes"
render={({ field }) => (
<FormItem>
<div className="flex items-center">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
{type === "compose" && (
<FormField
control={form.control}
name="deleteVolumes"
render={({ field }) => (
<FormItem>
<div className="flex items-center">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel className="ml-2">
Delete volumes associated with this compose
</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
<FormLabel className="ml-2">
Delete volumes associated with this compose
</FormLabel>
</div>
<FormMessage />
</FormItem>
)}
/>
)}
</form>
</Form>
</div>

View File

@ -97,8 +97,12 @@ export const ShowDomainsCompose = ({ composeId }: Props) => {
composeId={composeId}
domainId={item.domainId}
>
<Button variant="ghost">
<PenBoxIcon className="size-4 text-muted-foreground" />
<Button
variant="ghost"
size="icon"
className="group hover:bg-blue-500/10 "
>
<PenBoxIcon className="size-3.5 text-primary group-hover:text-blue-500" />
</Button>
</AddDomainCompose>
<DialogAction

View File

@ -247,7 +247,7 @@ const MENU: Menu = {
settings: [
{
isSingle: true,
title: "Server",
title: "Web Server",
url: "/dashboard/settings/server",
icon: Activity,
// Only enabled for admins in non-cloud environments
@ -262,7 +262,7 @@ const MENU: Menu = {
},
{
isSingle: true,
title: "Servers",
title: "Remote Servers",
url: "/dashboard/settings/servers",
icon: Server,
// Only enabled for admins

View File

@ -13,6 +13,7 @@ import { ShowGeneralApplication } from "@/components/dashboard/application/gener
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { ShowPreviewDeployments } from "@/components/dashboard/application/preview-deployments/show-preview-deployments";
import { UpdateApplication } from "@/components/dashboard/application/update-application";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { ProjectLayout } from "@/components/layouts/project-layout";
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
@ -82,8 +83,6 @@ const Service = (
},
);
const { mutateAsync, isLoading: isRemoving } =
api.application.delete.useMutation();
const { data: auth } = api.auth.get.useQuery();
const { data: user } = api.user.byAuthId.useQuery(
{
@ -177,34 +176,7 @@ const Service = (
<div className="flex flex-row gap-2 justify-end">
<UpdateApplication applicationId={applicationId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
<DialogAction
title="Delete Application"
description="Are you sure you want to delete this application?"
type="destructive"
onClick={async () => {
await mutateAsync({
applicationId: applicationId,
})
.then(() => {
router.push(
`/dashboard/project/${data?.projectId}`,
);
toast.success("Application deleted successfully");
})
.catch(() => {
toast.error("Error deleting application");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
<DeleteService id={applicationId} type="application" />
)}
</div>
</div>

View File

@ -1,7 +1,7 @@
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { AddCommandCompose } from "@/components/dashboard/compose/advanced/add-command";
import { DeleteCompose } from "@/components/dashboard/compose/delete-compose";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
import { ShowDeploymentsCompose } from "@/components/dashboard/compose/deployments/show-deployments-compose";
import { ShowDomainsCompose } from "@/components/dashboard/compose/domains/show-domains";
import { ShowGeneralCompose } from "@/components/dashboard/compose/general/show";
@ -168,7 +168,7 @@ const Service = (
<UpdateCompose composeId={composeId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
<DeleteCompose composeId={composeId} />
<DeleteService id={composeId} type="compose" />
)}
</div>
</div>

View File

@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
import { ShowExternalMariadbCredentials } from "@/components/dashboard/mariadb/general/show-external-mariadb-credentials";
import { ShowGeneralMariadb } from "@/components/dashboard/mariadb/general/show-general-mariadb";
@ -67,8 +68,7 @@ const Mariadb = (
enabled: !!auth?.id && auth?.rol === "user",
},
);
const { mutateAsync: remove, isLoading: isRemoving } =
api.mariadb.remove.useMutation();
return (
<div className="pb-10">
<BreadcrumbSidebar
@ -148,35 +148,10 @@ const Mariadb = (
</TooltipProvider>
)}
</div>
<div className="flex flex-row gap-2">
<div className="flex flex-row gap-2 justify-end">
<UpdateMariadb mariadbId={mariadbId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
<DialogAction
title="Remove Mariadb"
description="Are you sure you want to delete this mariadb?"
type="destructive"
onClick={async () => {
await remove({ mariadbId })
.then(() => {
router.push(
`/dashboard/project/${data?.projectId}`,
);
toast.success("Mariadb deleted successfully");
})
.catch(() => {
toast.error("Error deleting the mariadb");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
<DeleteService id={mariadbId} type="mariadb" />
)}
</div>
</div>

View File

@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
import { ShowExternalMongoCredentials } from "@/components/dashboard/mongo/general/show-external-mongo-credentials";
import { ShowGeneralMongo } from "@/components/dashboard/mongo/general/show-general-mongo";
@ -69,8 +70,6 @@ const Mongo = (
enabled: !!auth?.id && auth?.rol === "user",
},
);
const { mutateAsync: remove, isLoading: isRemoving } =
api.mongo.remove.useMutation();
return (
<div className="pb-10">
@ -155,32 +154,7 @@ const Mongo = (
<div className="flex flex-row gap-2 justify-end">
<UpdateMongo mongoId={mongoId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
<DialogAction
title="Remove mongo"
description="Are you sure you want to delete this mongo?"
type="destructive"
onClick={async () => {
await remove({ mongoId })
.then(() => {
router.push(
`/dashboard/project/${data?.projectId}`,
);
toast.success("Mongo deleted successfully");
})
.catch(() => {
toast.error("Error deleting the mongo");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
<DeleteService id={mongoId} type="mongo" />
)}
</div>
</div>

View File

@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { ShowExternalMysqlCredentials } from "@/components/dashboard/mysql/general/show-external-mysql-credentials";
@ -68,8 +69,6 @@ const MySql = (
},
);
const { mutateAsync: remove, isLoading: isRemoving } =
api.mysql.remove.useMutation();
return (
<div className="pb-10">
<BreadcrumbSidebar
@ -154,32 +153,7 @@ const MySql = (
<div className="flex flex-row gap-2 justify-end">
<UpdateMysql mysqlId={mysqlId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
<DialogAction
title="Remove Mysql"
description="Are you sure you want to delete this mysql?"
type="destructive"
onClick={async () => {
await remove({ mysqlId })
.then(() => {
router.push(
`/dashboard/project/${data?.projectId}`,
);
toast.success("Mysql deleted successfully");
})
.catch(() => {
toast.error("Error deleting the mysql");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
<DeleteService id={mysqlId} type="mysql" />
)}
</div>
</div>

View File

@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
import { ShowBackups } from "@/components/dashboard/database/backups/show-backups";
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
@ -70,9 +71,6 @@ const Postgresql = (
},
);
const { mutateAsync: remove, isLoading: isRemoving } =
api.postgres.remove.useMutation();
return (
<div className="pb-10">
<BreadcrumbSidebar
@ -156,32 +154,7 @@ const Postgresql = (
<div className="flex flex-row gap-2 justify-end">
<UpdatePostgres postgresId={postgresId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
<DialogAction
title="Remove Postgres"
description="Are you sure you want to delete this postgres?"
type="destructive"
onClick={async () => {
await remove({ postgresId })
.then(() => {
router.push(
`/dashboard/project/${data?.projectId}`,
);
toast.success("Postgres deleted successfully");
})
.catch(() => {
toast.error("Error deleting the postgres");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
<DeleteService id={postgresId} type="postgres" />
)}
</div>
</div>

View File

@ -2,6 +2,7 @@ import { ShowResources } from "@/components/dashboard/application/advanced/show-
import { ShowVolumes } from "@/components/dashboard/application/advanced/volumes/show-volumes";
import { ShowEnvironment } from "@/components/dashboard/application/environment/show-enviroment";
import { ShowDockerLogs } from "@/components/dashboard/application/logs/show";
import { DeleteService } from "@/components/dashboard/compose/delete-service";
import { DockerMonitoring } from "@/components/dashboard/monitoring/docker/show";
import { ShowCustomCommand } from "@/components/dashboard/postgres/advanced/show-custom-command";
import { ShowExternalRedisCredentials } from "@/components/dashboard/redis/general/show-external-redis-credentials";
@ -68,8 +69,6 @@ const Redis = (
},
);
const { mutateAsync: remove, isLoading: isRemoving } =
api.redis.remove.useMutation();
return (
<div className="pb-10">
<BreadcrumbSidebar
@ -153,32 +152,7 @@ const Redis = (
<div className="flex flex-row gap-2 justify-end">
<UpdateRedis redisId={redisId} />
{(auth?.rol === "admin" || user?.canDeleteServices) && (
<DialogAction
title="Remove Redis"
description="Are you sure you want to delete this redis?"
type="destructive"
onClick={async () => {
await remove({ redisId })
.then(() => {
router.push(
`/dashboard/project/${data?.projectId}`,
);
toast.success("Redis deleted successfully");
})
.catch(() => {
toast.error("Error deleting the redis");
});
}}
>
<Button
variant="ghost"
size="icon"
className="group hover:bg-red-500/10 "
isLoading={isRemoving}
>
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
</Button>
</DialogAction>
<DeleteService id={redisId} type="redis" />
)}
</div>
</div>