diff --git a/web/src/app/images/dialogs/delete-image-dialog.tsx b/web/src/app/images/dialogs/delete-image-dialog.tsx deleted file mode 100644 index d1e375a..0000000 --- a/web/src/app/images/dialogs/delete-image-dialog.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Dispatch, SetStateAction, useState } from "react" -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" -import { Button } from "@/components/ui/button" -import { Icons } from "@/components/icons" -import { cn, toastFailed, toastSuccess } from "@/lib/utils" -import useImages from "@/hooks/useImages" -import { IImage } from "@/lib/api-models" -import apiBaseUrl from "@/lib/api-base-url" -import { useParams } from "react-router-dom" - -export default function DeleteImageDialog({ - openState, - setOpenState, - image, -}: { - openState: boolean - setOpenState: Dispatch> - image: IImage -}) { - const { nodeId } = useParams() - const { mutateImages } = useImages(nodeId!) - const [isSaving, setIsSaving] = useState(false) - - const handleDeleteImage = async () => { - setIsSaving(true) - - const response = await fetch( - `${apiBaseUrl()}/nodes/${nodeId}/images/remove`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id: image.id, force: true }), - } - ) - if (!response.ok) { - const r = await response.json() - setOpenState(false) - toastFailed(r.errors?.body) - } else { - mutateImages() - setTimeout(() => { - setOpenState(false) - toastSuccess("Image deleted.") - }, 500) - } - setIsSaving(false) - } - - return ( - - - - Delete Image - -
-

{`Are you sure you want to delete image '${image.name}:${image.tag}'?`}

-
- -
- - -
-
-
-
- ) -} diff --git a/web/src/app/images/images.tsx b/web/src/app/images/images.tsx index 42bebca..9998e3d 100644 --- a/web/src/app/images/images.tsx +++ b/web/src/app/images/images.tsx @@ -16,8 +16,7 @@ import { import { IImage } from "@/lib/api-models" import { useState } from "react" import useImages from "@/hooks/useImages" -import DeleteImageDialog from "./dialogs/delete-image-dialog" -import { convertByteToMb } from "@/lib/utils" +import { convertByteToMb, toastFailed, toastSuccess } from "@/lib/utils" import PruneImagesDialog from "./dialogs/prune-images-dialog" import MainArea from "@/components/widgets/main-area" import TopBar from "@/components/widgets/top-bar" @@ -27,28 +26,60 @@ import { useParams } from "react-router-dom" import useNodeHead from "@/hooks/useNodeHead" import TableButtonDelete from "@/components/widgets/table-button-delete" import { TableNoData } from "@/components/widgets/table-no-data" +import apiBaseUrl from "@/lib/api-base-url" +import DeleteDialog from "@/components/delete-dialog" export default function Images() { const { nodeId } = useParams() const { nodeHead } = useNodeHead(nodeId!) - const { isLoading, images } = useImages(nodeId!) - const [deleteImageOpen, setDeleteImageOpen] = useState(false) + const { isLoading, images, mutateImages } = useImages(nodeId!) const [image, setImage] = useState(null) + const [deleteImageConfirmationOpen, setDeleteImageConfirmationOpen] = + useState(false) + const [deleteInProgress, setDeleteInProgress] = useState(false) if (isLoading) return - const handleDeleteImage = (image: IImage) => { + const handleDeleteImageConfirmation = (image: IImage) => { setImage({ ...image }) - setDeleteImageOpen(true) + setDeleteImageConfirmationOpen(true) + } + + const handleDelete = async () => { + setDeleteInProgress(true) + const response = await fetch( + `${apiBaseUrl()}/nodes/${nodeId}/images/remove`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id: image?.id, force: true }), + } + ) + if (!response.ok) { + const r = await response.json() + setDeleteImageConfirmationOpen(false) + toastFailed(r.errors?.body) + } else { + mutateImages() + setTimeout(() => { + setDeleteImageConfirmationOpen(false) + toastSuccess("Image deleted.") + }, 500) + } + setDeleteInProgress(false) } return ( - {deleteImageOpen && ( - )} @@ -96,7 +127,7 @@ export default function Images() { { e.stopPropagation() - handleDeleteImage(item) + handleDeleteImageConfirmation(item) }} /> diff --git a/web/src/app/networks/dialogs/delete-network-dialog.tsx b/web/src/app/networks/dialogs/delete-network-dialog.tsx deleted file mode 100644 index 46a6205..0000000 --- a/web/src/app/networks/dialogs/delete-network-dialog.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Dispatch, SetStateAction, useState } from "react" -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" -import { Button } from "@/components/ui/button" -import { Icons } from "@/components/icons" -import { cn, toastFailed, toastSuccess } from "@/lib/utils" -import useNetworks from "@/hooks/useNetworks" -import { INetwork } from "@/lib/api-models" -import apiBaseUrl from "@/lib/api-base-url" -import { useParams } from "react-router-dom" - -export default function DeleteNetworkDialog({ - openState, - setOpenState, - network, -}: { - openState: boolean - setOpenState: Dispatch> - network: INetwork -}) { - const { nodeId } = useParams() - const { mutateNetworks } = useNetworks(nodeId!) - - const [isSaving, setIsSaving] = useState(false) - - const handleDeleteNetwork = async () => { - setIsSaving(true) - - const response = await fetch( - `${apiBaseUrl()}/nodes/${nodeId}/networks/remove`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id: network.id, force: true }), - } - ) - if (!response.ok) { - const r = await response.json() - setOpenState(false) - toastFailed(r.errors?.body) - } else { - mutateNetworks() - setTimeout(() => { - setOpenState(false) - toastSuccess("Network deleted.") - }, 500) - } - setIsSaving(false) - } - - return ( - - - - Delete Network - -
-

{`Are you sure you want to delete network '${network.name}'?`}

-
- -
- - -
-
-
-
- ) -} diff --git a/web/src/app/networks/networks.tsx b/web/src/app/networks/networks.tsx index 5929b0f..d4eace7 100644 --- a/web/src/app/networks/networks.tsx +++ b/web/src/app/networks/networks.tsx @@ -16,7 +16,6 @@ import { import { INetwork } from "@/lib/api-models" import { useState } from "react" import useNetworks from "@/hooks/useNetworks" -import DeleteNetworkDialog from "./dialogs/delete-network-dialog" import PruneNetworksDialog from "./dialogs/prune-networks-dialog" import MainArea from "@/components/widgets/main-area" import TopBar from "@/components/widgets/top-bar" @@ -26,29 +25,62 @@ import { useParams } from "react-router-dom" import useNodeHead from "@/hooks/useNodeHead" import TableButtonDelete from "@/components/widgets/table-button-delete" import { TableNoData } from "@/components/widgets/table-no-data" +import { toastFailed, toastSuccess } from "@/lib/utils" +import apiBaseUrl from "@/lib/api-base-url" +import DeleteDialog from "@/components/delete-dialog" export default function Networks() { const { nodeId } = useParams() const { nodeHead } = useNodeHead(nodeId!) - const { isLoading, networks } = useNetworks(nodeId!) + const { isLoading, networks, mutateNetworks } = useNetworks(nodeId!) - const [deleteNetworkOpen, setDeleteNetworkOpen] = useState(false) const [network, setNetwork] = useState(null) + const [deleteNetworkOpenConfirmation, setDeleteNetworkOpenConfirmation] = + useState(false) + const [deleteInProgress, setDeleteInProgress] = useState(false) if (isLoading) return - const handleDeleteNetwork = (network: INetwork) => { + const handleDeleteNetworkConfirmation = (network: INetwork) => { setNetwork({ ...network }) - setDeleteNetworkOpen(true) + setDeleteNetworkOpenConfirmation(true) + } + + const handleDelete = async () => { + setDeleteInProgress(true) + const response = await fetch( + `${apiBaseUrl()}/nodes/${nodeId}/networks/remove`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id: network?.id, force: true }), + } + ) + if (!response.ok) { + const r = await response.json() + setDeleteNetworkOpenConfirmation(false) + toastFailed(r.errors?.body) + } else { + mutateNetworks() + setTimeout(() => { + setDeleteNetworkOpenConfirmation(false) + toastSuccess("Network deleted.") + }, 500) + } + setDeleteInProgress(false) } return ( - {deleteNetworkOpen && ( - )} @@ -89,7 +121,7 @@ export default function Networks() { { e.stopPropagation() - handleDeleteNetwork(item) + handleDeleteNetworkConfirmation(item) }} /> diff --git a/web/src/app/variables/dialogs/delete-variable-dialog.tsx b/web/src/app/variables/dialogs/delete-variable-dialog.tsx deleted file mode 100644 index b462bd8..0000000 --- a/web/src/app/variables/dialogs/delete-variable-dialog.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Dispatch, SetStateAction, useState } from "react" -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" -import { Button } from "@/components/ui/button" -import { Icons } from "@/components/icons" -import { cn } from "@/lib/utils" -import useVariables from "@/hooks/useVariables" -import apiBaseUrl from "@/lib/api-base-url" -import { IVariableHead } from "@/lib/api-models" - -export default function DeleteVariableDialog({ - openState, - setOpenState, - variableHead, -}: { - openState: boolean - setOpenState: Dispatch> - variableHead: IVariableHead -}) { - const [isSaving, setIsSaving] = useState(false) - const { mutateVariables } = useVariables() - - const handleDeleteVariable = async () => { - setIsSaving(true) - - const response = await fetch( - `${apiBaseUrl()}/variables/${variableHead.id}`, - { - method: "DELETE", - headers: { "Content-Type": "application/json" }, - } - ) - if (!response.ok) { - setOpenState(false) - } else { - mutateVariables() - setTimeout(() => { - setOpenState(false) - }, 500) - } - setIsSaving(false) - } - - return ( - - - - Delete Variable - -
-

{`Are you sure you want to delete variable '${variableHead.name}'?`}

-
- -
- - -
-
-
-
- ) -} diff --git a/web/src/app/variables/variables.tsx b/web/src/app/variables/variables.tsx index 0fe8052..22d753b 100644 --- a/web/src/app/variables/variables.tsx +++ b/web/src/app/variables/variables.tsx @@ -16,7 +16,6 @@ import useVariables from "@/hooks/useVariables" import AddVariableDialog from "./dialogs/add-variable-dialog" import { useState } from "react" import { IVariableHead } from "@/lib/api-models" -import DeleteVariableDialog from "./dialogs/delete-variable-dialog" import useEnvironmentsMap from "@/hooks/useEnvironmentsMap" import { Checkbox } from "@/components/ui/checkbox" import EditVariableDialog from "./dialogs/edit-variable-dialog" @@ -24,16 +23,20 @@ import EditVariableValueDialog from "./dialogs/edit-variable-value-dialog" import TableButtonDelete from "@/components/widgets/table-button-delete" import TableButtonEdit from "@/components/widgets/table-button-edit" import { TableNoData } from "@/components/widgets/table-no-data" +import DeleteDialog from "@/components/delete-dialog" +import apiBaseUrl from "@/lib/api-base-url" export default function Variables() { const { isLoading: mapIsLoading, environmentsMap } = useEnvironmentsMap() - const { isLoading, variables } = useVariables() + const { isLoading, variables, mutateVariables } = useVariables() const [editVariableOpen, setEditVariableOpen] = useState(false) const [editVariableValueOpen, setEditVariableValueOpen] = useState(false) const [editVariableValueEnvironmentId, setEditVariableValueEnvironmentId] = useState(null) - const [deleteVariableOpen, setDeleteVariableOpen] = useState(false) const [variableHead, setVariableHead] = useState(null) + const [deleteVariableOpenConfirmation, setDeleteVariableOpenConfirmation] = + useState(false) + const [deleteInProgress, setDeleteInProgress] = useState(false) if (mapIsLoading || isLoading) return @@ -51,9 +54,29 @@ export default function Variables() { setEditVariableValueEnvironmentId(envId) } - const handleDeleteVariable = (variableHead: IVariableHead) => { + const handleDeleteVariableConfirmation = (variableHead: IVariableHead) => { setVariableHead({ ...variableHead }) - setDeleteVariableOpen(true) + setDeleteVariableOpenConfirmation(true) + } + + const handleDelete = async () => { + setDeleteInProgress(true) + const response = await fetch( + `${apiBaseUrl()}/variables/${variableHead?.id}`, + { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + } + ) + if (!response.ok) { + setDeleteVariableOpenConfirmation(false) + } else { + mutateVariables() + setTimeout(() => { + setDeleteVariableOpenConfirmation(false) + }, 500) + } + setDeleteInProgress(false) } return ( @@ -73,11 +96,15 @@ export default function Variables() { environmentId={editVariableValueEnvironmentId!} /> )} - {deleteVariableOpen && ( - )} @@ -110,7 +137,7 @@ export default function Variables() { { e.stopPropagation() - handleDeleteVariable(item) + handleDeleteVariableConfirmation(item) }} /> diff --git a/web/src/app/volumes/dialogs/delete-volume-dialog.tsx b/web/src/app/volumes/dialogs/delete-volume-dialog.tsx deleted file mode 100644 index d58ad0e..0000000 --- a/web/src/app/volumes/dialogs/delete-volume-dialog.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Dispatch, SetStateAction, useState } from "react" -import { - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog" -import { Button } from "@/components/ui/button" -import { Icons } from "@/components/icons" -import { cn, toastFailed, toastSuccess } from "@/lib/utils" -import useVolumes from "@/hooks/useVolumes" -import { IVolume } from "@/lib/api-models" -import apiBaseUrl from "@/lib/api-base-url" -import { useParams } from "react-router-dom" - -export default function DeleteVolumeDialog({ - openState, - setOpenState, - volume, -}: { - openState: boolean - setOpenState: Dispatch> - volume: IVolume -}) { - const { nodeId } = useParams() - const { mutateVolumes } = useVolumes(nodeId!) - - const [isSaving, setIsSaving] = useState(false) - - const handleDeleteVolume = async () => { - setIsSaving(true) - - const response = await fetch( - `${apiBaseUrl()}/nodes/${nodeId}/volumes/remove`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ name: volume.name }), - } - ) - if (!response.ok) { - const r = await response.json() - setOpenState(false) - toastFailed(r.errors?.body) - } else { - mutateVolumes() - setTimeout(() => { - setOpenState(false) - toastSuccess("Volume deleted.") - }, 500) - } - setIsSaving(false) - } - - function shortName(name: string) { - if (name?.length > 20) { - return name.substring(0, 30) + "..." - } - return name - } - - return ( - - - - Delete Volume - -
-

{`Are you sure you want to delete volume '${shortName( - volume.name - )}'?`}

-
- -
- - -
-
-
-
- ) -} diff --git a/web/src/app/volumes/volumes.tsx b/web/src/app/volumes/volumes.tsx index 261b0b3..b54db6a 100644 --- a/web/src/app/volumes/volumes.tsx +++ b/web/src/app/volumes/volumes.tsx @@ -20,35 +20,67 @@ import TopBar from "@/components/widgets/top-bar" import TopBarActions from "@/components/widgets/top-bar-actions" import MainContent from "@/components/widgets/main-content" import useVolumes from "@/hooks/useVolumes" -import DeleteVolumeDialog from "./dialogs/delete-volume-dialog" import PruneVolumesDialog from "./dialogs/prune-volumes-dialog" import { useParams } from "react-router-dom" import useNodeHead from "@/hooks/useNodeHead" import TableButtonDelete from "@/components/widgets/table-button-delete" import { TableNoData } from "@/components/widgets/table-no-data" +import DeleteDialog from "@/components/delete-dialog" +import { toastFailed, toastSuccess } from "@/lib/utils" +import apiBaseUrl from "@/lib/api-base-url" export default function Volumes() { const { nodeId } = useParams() const { nodeHead } = useNodeHead(nodeId!) - const { isLoading, volumes } = useVolumes(nodeId!) + const { isLoading, volumes, mutateVolumes } = useVolumes(nodeId!) - const [deleteVolumeOpen, setDeleteVolumeOpen] = useState(false) const [volume, setVolume] = useState(null) + const [deleteVolumeOpenConfirmation, setDeleteVolumeOpenConfirmation] = + useState(false) + const [deleteInProgress, setDeleteInProgress] = useState(false) if (isLoading) return - const handleDeleteVolume = (volume: IVolume) => { + const handleDeleteVolumeConfirmation = (volume: IVolume) => { setVolume({ ...volume }) - setDeleteVolumeOpen(true) + setDeleteVolumeOpenConfirmation(true) + } + + const handleDelete = async () => { + setDeleteInProgress(true) + const response = await fetch( + `${apiBaseUrl()}/nodes/${nodeId}/volumes/remove`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: volume?.name }), + } + ) + if (!response.ok) { + const r = await response.json() + setDeleteVolumeOpenConfirmation(false) + toastFailed(r.errors?.body) + } else { + mutateVolumes() + setTimeout(() => { + setDeleteVolumeOpenConfirmation(false) + toastSuccess("Volume deleted.") + }, 500) + } + setDeleteInProgress(false) } return ( - {deleteVolumeOpen && ( - )} @@ -85,7 +117,7 @@ export default function Volumes() { { e.stopPropagation() - handleDeleteVolume(item) + handleDeleteVolumeConfirmation(item) }} />