diff --git a/frontend/src/components/ResourcesView.tsx b/frontend/src/components/ResourcesView.tsx index 74e8be40..464cc15c 100644 --- a/frontend/src/components/ResourcesView.tsx +++ b/frontend/src/components/ResourcesView.tsx @@ -6,8 +6,7 @@ import { springs } from '@/lib/motion'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; +import { Modal, ModalHeader, ModalBody, ModalFooter, ConfirmModal } from "@/components/ui/modal"; import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Combobox } from "@/components/ui/combobox"; @@ -515,7 +514,6 @@ export default function ResourcesView() { throw new Error(data?.error || 'Failed to purge selected containers'); } toast.success(`Purged ${selectedOrphans.length} unmanaged container(s)`); - setBulkPurgeConfirm(false); await fetchAllData(); } catch (error) { const err = error as Record; @@ -523,6 +521,7 @@ export default function ResourcesView() { } finally { toast.dismiss(loadingId); setIsActioning(false); + setBulkPurgeConfirm(false); } }; @@ -1208,158 +1207,156 @@ export default function ResourcesView() { {/* ── Dialogs ── */} {/* Prune Confirm */} - !open && setConfirmPrune(null)}> - - - {confirmPrune?.scope === 'all' ? ( - <> - - - Prune All Docker {confirmPrune?.target} - - - This will prune all unused {confirmPrune?.target} from the Docker daemon - - including those from external projects not managed by Sencho. This cannot be undone. - - - ) : ( - <> - Prune Sencho-Managed {confirmPrune?.target} - - Only unused {confirmPrune?.target} belonging to your Sencho stacks will be removed. - External Docker resources are not affected. - - - )} - - - Cancel - - {isActioning ? 'Pruning...' : confirmPrune?.scope === 'all' ? 'Prune All' : 'Prune'} - - - - + !open && setConfirmPrune(null)} + variant="destructive" + kicker="RESOURCES · PRUNE · IRREVERSIBLE" + title={ + confirmPrune?.scope === 'all' + ? `Prune all Docker ${confirmPrune?.target}` + : `Prune Sencho-managed ${confirmPrune?.target}` + } + hint={confirmPrune?.scope === 'all' ? 'AFFECTS external Docker resources' : 'KEEPS external resources'} + confirmLabel={isActioning ? 'Pruning...' : (confirmPrune?.scope === 'all' ? 'Prune all' : 'Prune')} + confirming={isActioning} + onConfirm={handlePrune} + > +

+ {confirmPrune?.scope === 'all' ? ( + <> + Prunes all unused {confirmPrune?.target} from the Docker daemon, including those from{' '} + external projects not managed by Sencho. + + ) : ( + <> + Removes only unused {confirmPrune?.target} belonging to your Sencho stacks. External Docker resources are{' '} + not affected. + + )} +

+
{/* Delete Confirm */} - !open && setConfirmDelete(null)}> - - - Delete {confirmDelete?.type.slice(0, -1)} - - Permanently delete {confirmDelete?.name || confirmDelete?.id.substring(0, 12)}? This cannot be undone. - - - - Cancel - - {isActioning ? 'Deleting...' : 'Delete'} - - - - + !open && setConfirmDelete(null)} + variant="destructive" + kicker="RESOURCES · DELETE · IRREVERSIBLE" + title={`Delete ${confirmDelete?.type.slice(0, -1) ?? ''}`} + confirmLabel={isActioning ? 'Deleting...' : 'Delete'} + confirming={isActioning} + onConfirm={handleDelete} + > +

+ Permanently deletes{' '} + + {confirmDelete?.name || confirmDelete?.id.substring(0, 12)} + . +

+
{/* Unmanaged Container Purge Confirm */} - - - - Purge Selected Unmanaged Containers - - Permanently remove {selectedOrphans.length} container{selectedOrphans.length !== 1 ? 's' : ''} from external projects? - This will force-stop and remove them. This cannot be undone. - - - - Cancel - - {isActioning ? 'Purging...' : 'Purge'} - - - - + +

+ Force-stops and removes {selectedOrphans.length} container{selectedOrphans.length !== 1 ? 's' : ''} from external projects not managed by Sencho. +

+
- {/* Create Network Dialog */} - - - - Create Network - Create a new Docker network for inter-container communication. - -
+ {/* Create Network Modal */} + + + +
+ + setCreateNetworkForm(f => ({ ...f, name: e.target.value }))} + /> +
+
+ + ({ value: d, label: d }))} + value={createNetworkForm.driver} + onValueChange={v => setCreateNetworkForm(f => ({ ...f, driver: (v || 'bridge') as NetworkDriver }))} + placeholder="Select driver..." + searchPlaceholder="Search drivers..." + emptyText="No matching driver." + /> +
+
- + setCreateNetworkForm(f => ({ ...f, name: e.target.value }))} + value={createNetworkForm.subnet} + onChange={e => setCreateNetworkForm(f => ({ ...f, subnet: e.target.value }))} />
- - ({ value: d, label: d }))} - value={createNetworkForm.driver} - onValueChange={v => setCreateNetworkForm(f => ({ ...f, driver: (v || 'bridge') as NetworkDriver }))} - placeholder="Select driver..." - searchPlaceholder="Search drivers..." - emptyText="No matching driver." + + setCreateNetworkForm(f => ({ ...f, gateway: e.target.value }))} />
-
-
- - setCreateNetworkForm(f => ({ ...f, subnet: e.target.value }))} - /> -
-
- - setCreateNetworkForm(f => ({ ...f, gateway: e.target.value }))} - /> -
-
-
-
- setCreateNetworkForm(f => ({ ...f, internal: v }))} - /> - -
-
- setCreateNetworkForm(f => ({ ...f, attachable: v }))} - /> - -
-
- - - - - -
+ } + primary={ + + } + /> + {/* Image Details Sheet */} setInspectImageId(null)} /> diff --git a/frontend/src/components/ui/modal.tsx b/frontend/src/components/ui/modal.tsx index 566b791c..8928184d 100644 --- a/frontend/src/components/ui/modal.tsx +++ b/frontend/src/components/ui/modal.tsx @@ -83,7 +83,7 @@ function HeaderShell({ return (
-
+
{kicker}