From d1bd1013864cef91500651591420012205167fd7 Mon Sep 17 00:00:00 2001 From: Naji Amer Date: Sun, 22 Dec 2024 13:06:33 +0400 Subject: [PATCH] 0.0.8 Removing ibm/carbon design because of unresolved issues with ibm telemetry and installation failures --- README.md | 27 +- app/components/alert.tsx | 8 +- app/components/collection.tsx | 338 +- app/components/connection.tsx | 790 +-- app/components/copy_text.tsx | 82 +- app/components/csv_table.tsx | 147 +- app/components/database.tsx | 410 +- app/components/footer.tsx | 2 +- app/components/github_widget.tsx | 8 +- app/components/title.tsx | 52 +- app/components/tree/treenode.tsx | 28 +- app/root.tsx | 54 +- app/routes/_base.connections.tsx | 897 +-- app/routes/_base.create.tsx | 201 +- app/routes/_base.profile.tsx | 284 +- app/routes/_base.tsx | 100 +- app/routes/_user._index.tsx | 323 +- app/routes/_user.database.$db.$col.tsx | 929 ++- app/routes/_user.database.$db._index.tsx | 1030 +-- app/routes/_user.database._index.tsx | 449 +- app/routes/_user.tsx | 507 +- app/routes/error.tsx | 195 +- app/routes/login.tsx | 219 +- app/tailwind.css | 6 +- app/tailwind.css.map | 2 +- app/tailwind.scss | 37 +- app/ui/accordion.tsx | 43 + app/ui/actionable-notification.tsx | 45 + app/ui/alert-dialog.tsx | 141 + app/ui/alert.tsx | 39 + app/ui/aspect-ratio.tsx | 7 + app/ui/avatar.tsx | 25 + app/ui/badge.tsx | 36 + app/ui/breadcrumb.tsx | 115 + app/ui/button.tsx | 114 + app/ui/calendar.tsx | 67 + app/ui/card.tsx | 76 + app/ui/carousel.tsx | 200 + app/ui/chart.tsx | 365 + app/ui/checkbox.tsx | 138 + app/ui/collapsible.tsx | 11 + app/ui/command.tsx | 153 + app/ui/context-menu.tsx | 200 + app/ui/dialog.tsx | 73 + app/ui/drawer.tsx | 118 + app/ui/dropdown-menu.tsx | 201 + app/ui/header.tsx | 178 + app/ui/hooks/use-mobile.tsx | 19 + app/ui/hooks/use-toast.ts | 186 + app/ui/hover-card.tsx | 29 + app/ui/input-otp.tsx | 71 + app/ui/input.tsx | 72 + app/ui/label.tsx | 26 + app/ui/lib/utils.ts | 118 + app/ui/menubar.tsx | 236 + app/ui/modal.tsx | 77 + app/ui/navigation-menu.tsx | 128 + app/ui/pagination-old.tsx | 117 + app/ui/pagination.tsx | 371 ++ app/ui/popover.tsx | 33 + app/ui/progress.tsx | 28 + app/ui/radio-group.tsx | 44 + app/ui/resizable.tsx | 45 + app/ui/scroll-area.tsx | 48 + app/ui/select.tsx | 104 + app/ui/separator.tsx | 31 + app/ui/sheet.tsx | 140 + app/ui/sidebar.tsx | 567 ++ app/ui/skeleton.tsx | 15 + app/ui/skip-to-content.tsx | 16 + app/ui/slider.tsx | 28 + app/ui/sonner.tsx | 31 + app/ui/switch.tsx | 29 + app/ui/table.tsx | 47 + app/ui/tabs.tsx | 40 + app/ui/textarea.tsx | 22 + app/ui/toast.tsx | 91 + app/ui/toaster.tsx | 26 + app/ui/toggle-group.tsx | 61 + app/ui/toggle.tsx | 45 + app/ui/tooltip.tsx | 57 + app/ui/tree.tsx | 485 ++ app/utils/functions.server.ts | 102 +- app/utils/functions.ts | 292 +- build/client/assets/AccordionItem-DNYUtNot.js | 1 - build/client/assets/Button-Bl4KegFP.js | 1 - build/client/assets/Column-BatEsPZP.js | 1 - .../assets/HeaderMenuButton-CAm8d-jH.js | 1 - build/client/assets/Modal-K69mcxHf.js | 1 - build/client/assets/Notification-t-CAZq57.js | 1 - build/client/assets/Search-BNe1a6wo.js | 1 - build/client/assets/TableRow-Zi2gF0TG.js | 1 - build/client/assets/TextInput-CKh2Yhf_.js | 9 - build/client/assets/_base-DtrqugTP.js | 1 + build/client/assets/_base-UFt_NXRy.js | 1 - .../assets/_base.connections-Brm3bB66.js | 1 - .../assets/_base.connections-DFD7X86d.js | 1 + build/client/assets/_base.create-BRASXI-l.js | 1 + build/client/assets/_base.create-BTgmFHlR.js | 1 - build/client/assets/_base.profile-63As5LFj.js | 1 + build/client/assets/_base.profile-Ceng-Gaf.js | 1 - build/client/assets/_user-BiiY_KjC.js | 1 - build/client/assets/_user-Bt1gRWw-.js | 6 + build/client/assets/_user._index-AIFGYrER.js | 1 + build/client/assets/_user._index-gz7svUly.js | 1 - .../_user.database._db._col-CFSJQL9_.js | 39 - .../_user.database._db._col-Dmn7Omo_.js | 31 + .../_user.database._db._index-H-1x1CL0.js | 1 + .../_user.database._db._index-oCGZi3vN.js | 1 - .../assets/_user.database._index-C4SxA8eV.js | 1 - .../assets/_user.database._index-CRcUl7DD.js | 1 + build/client/assets/accordion-Cy3lx65E.js | 1 + .../actionable-notification-DDLqphrE.js | 1 + build/client/assets/alert-Do9tp9-j.js | 1 + build/client/assets/avatar-DdkXt1Wa.js | 1 + build/client/assets/bucket-10-BakHwRxf.js | 1 - build/client/assets/bucket-17-BFRBnihc.js | 1 - build/client/assets/bucket-3-B5cXRAyd.js | 1 - build/client/assets/bucket-4-DNu0uQyD.js | 1 - build/client/assets/button-ZrFu6W_T.js | 1 + build/client/assets/collection-CQRtENl6.js | 1 + build/client/assets/collection-D6bPwBCG.js | 1 - ...nts-Cj7Yk1Hh.js => components-BPOIHgDc.js} | 68 +- build/client/assets/connection-DQWmmeEw.js | 1 + build/client/assets/connection-DdvKsJfI.js | 9 - build/client/assets/database-D9MPVr0e.js | 1 + build/client/assets/database-Dl88dlG2.js | 1 - build/client/assets/entry.client-CiKnG_8o.js | 19 - build/client/assets/entry.client-D-yrOLDe.js | 19 + build/client/assets/error-2d9OmjR3.js | 1 + build/client/assets/error-BMPD2SHa.js | 1 - build/client/assets/footer-Di0okDi_.js | 1 - build/client/assets/functions-B7s41jYM.js | 1 - build/client/assets/functions-DMKVu1r-.js | 1 + build/client/assets/github_widget-R1gwNBdG.js | 1 + build/client/assets/github_widget-X-pkoqDF.js | 1 - build/client/assets/index-B37bIhWk.js | 1 - build/client/assets/index-BgfNTfNX.js | 1 - build/client/assets/index-CZUknZXM.js | 1 + build/client/assets/index-DCm1eCXF.js | 1 - build/client/assets/index-_rCr0AGk.js | 17 - build/client/assets/input-ChpOE9Lf.js | 1 + build/client/assets/login-BmnM70Yh.js | 1 - build/client/assets/login-DTEqUQMw.js | 1 + .../{logo-BYODAW8L.js => logo-COugf1Vw.js} | 2 +- build/client/assets/manifest-32adbcfa.js | 1 + build/client/assets/manifest-ab38ca53.js | 1 - build/client/assets/package-BHlkEMfo.js | 1 - build/client/assets/package-BtyHozD1.js | 1 + build/client/assets/root-C_ObZ1CP.js | 10 - build/client/assets/root-DYAgCu9X.js | 10 + build/client/assets/root-nUEo1Ulo.css | 1 - build/client/assets/root-sL7v7eY3.css | 1 + build/client/assets/select-Cem4k-Od.js | 60 + build/client/assets/table-CX2GO18n.js | 1 + build/client/assets/toast-hIcI356p.js | 1 + build/client/assets/useMatchMedia-DCVgUbq7.js | 1 - build/client/assets/utils-DcAaEMf9.js | 1 + build/client/assets/wrapComponent-DVKhoVmh.js | 1 - build/client/assets/x-OohQberg.js | 26 + build/server/index.js | 3791 ++++++++--- console/install.js | 95 +- console/telemetry.js | 1 - package-lock.json | 5850 ++++++++++++----- package.json | 56 +- tailwind.config.ts | 66 +- tsconfig.json | 4 +- vite.config.ts | 69 +- 168 files changed, 16956 insertions(+), 6710 deletions(-) create mode 100755 app/ui/accordion.tsx create mode 100644 app/ui/actionable-notification.tsx create mode 100755 app/ui/alert-dialog.tsx create mode 100755 app/ui/alert.tsx create mode 100755 app/ui/aspect-ratio.tsx create mode 100755 app/ui/avatar.tsx create mode 100755 app/ui/badge.tsx create mode 100755 app/ui/breadcrumb.tsx create mode 100755 app/ui/button.tsx create mode 100755 app/ui/calendar.tsx create mode 100755 app/ui/card.tsx create mode 100755 app/ui/carousel.tsx create mode 100755 app/ui/chart.tsx create mode 100755 app/ui/checkbox.tsx create mode 100755 app/ui/collapsible.tsx create mode 100755 app/ui/command.tsx create mode 100755 app/ui/context-menu.tsx create mode 100755 app/ui/dialog.tsx create mode 100755 app/ui/drawer.tsx create mode 100755 app/ui/dropdown-menu.tsx create mode 100644 app/ui/header.tsx create mode 100755 app/ui/hooks/use-mobile.tsx create mode 100755 app/ui/hooks/use-toast.ts create mode 100755 app/ui/hover-card.tsx create mode 100755 app/ui/input-otp.tsx create mode 100755 app/ui/input.tsx create mode 100755 app/ui/label.tsx create mode 100755 app/ui/lib/utils.ts create mode 100755 app/ui/menubar.tsx create mode 100644 app/ui/modal.tsx create mode 100755 app/ui/navigation-menu.tsx create mode 100755 app/ui/pagination-old.tsx create mode 100755 app/ui/pagination.tsx create mode 100755 app/ui/popover.tsx create mode 100755 app/ui/progress.tsx create mode 100755 app/ui/radio-group.tsx create mode 100755 app/ui/resizable.tsx create mode 100755 app/ui/scroll-area.tsx create mode 100755 app/ui/select.tsx create mode 100755 app/ui/separator.tsx create mode 100755 app/ui/sheet.tsx create mode 100755 app/ui/sidebar.tsx create mode 100755 app/ui/skeleton.tsx create mode 100644 app/ui/skip-to-content.tsx create mode 100755 app/ui/slider.tsx create mode 100755 app/ui/sonner.tsx create mode 100755 app/ui/switch.tsx create mode 100755 app/ui/table.tsx create mode 100755 app/ui/tabs.tsx create mode 100755 app/ui/textarea.tsx create mode 100755 app/ui/toast.tsx create mode 100755 app/ui/toaster.tsx create mode 100755 app/ui/toggle-group.tsx create mode 100755 app/ui/toggle.tsx create mode 100755 app/ui/tooltip.tsx create mode 100644 app/ui/tree.tsx delete mode 100644 build/client/assets/AccordionItem-DNYUtNot.js delete mode 100644 build/client/assets/Button-Bl4KegFP.js delete mode 100644 build/client/assets/Column-BatEsPZP.js delete mode 100644 build/client/assets/HeaderMenuButton-CAm8d-jH.js delete mode 100644 build/client/assets/Modal-K69mcxHf.js delete mode 100644 build/client/assets/Notification-t-CAZq57.js delete mode 100644 build/client/assets/Search-BNe1a6wo.js delete mode 100644 build/client/assets/TableRow-Zi2gF0TG.js delete mode 100644 build/client/assets/TextInput-CKh2Yhf_.js create mode 100644 build/client/assets/_base-DtrqugTP.js delete mode 100644 build/client/assets/_base-UFt_NXRy.js delete mode 100644 build/client/assets/_base.connections-Brm3bB66.js create mode 100644 build/client/assets/_base.connections-DFD7X86d.js create mode 100644 build/client/assets/_base.create-BRASXI-l.js delete mode 100644 build/client/assets/_base.create-BTgmFHlR.js create mode 100644 build/client/assets/_base.profile-63As5LFj.js delete mode 100644 build/client/assets/_base.profile-Ceng-Gaf.js delete mode 100644 build/client/assets/_user-BiiY_KjC.js create mode 100644 build/client/assets/_user-Bt1gRWw-.js create mode 100644 build/client/assets/_user._index-AIFGYrER.js delete mode 100644 build/client/assets/_user._index-gz7svUly.js delete mode 100644 build/client/assets/_user.database._db._col-CFSJQL9_.js create mode 100644 build/client/assets/_user.database._db._col-Dmn7Omo_.js create mode 100644 build/client/assets/_user.database._db._index-H-1x1CL0.js delete mode 100644 build/client/assets/_user.database._db._index-oCGZi3vN.js delete mode 100644 build/client/assets/_user.database._index-C4SxA8eV.js create mode 100644 build/client/assets/_user.database._index-CRcUl7DD.js create mode 100644 build/client/assets/accordion-Cy3lx65E.js create mode 100644 build/client/assets/actionable-notification-DDLqphrE.js create mode 100644 build/client/assets/alert-Do9tp9-j.js create mode 100644 build/client/assets/avatar-DdkXt1Wa.js delete mode 100644 build/client/assets/bucket-10-BakHwRxf.js delete mode 100644 build/client/assets/bucket-17-BFRBnihc.js delete mode 100644 build/client/assets/bucket-3-B5cXRAyd.js delete mode 100644 build/client/assets/bucket-4-DNu0uQyD.js create mode 100644 build/client/assets/button-ZrFu6W_T.js create mode 100644 build/client/assets/collection-CQRtENl6.js delete mode 100644 build/client/assets/collection-D6bPwBCG.js rename build/client/assets/{components-Cj7Yk1Hh.js => components-BPOIHgDc.js} (88%) create mode 100644 build/client/assets/connection-DQWmmeEw.js delete mode 100644 build/client/assets/connection-DdvKsJfI.js create mode 100644 build/client/assets/database-D9MPVr0e.js delete mode 100644 build/client/assets/database-Dl88dlG2.js delete mode 100644 build/client/assets/entry.client-CiKnG_8o.js create mode 100644 build/client/assets/entry.client-D-yrOLDe.js create mode 100644 build/client/assets/error-2d9OmjR3.js delete mode 100644 build/client/assets/error-BMPD2SHa.js delete mode 100644 build/client/assets/footer-Di0okDi_.js delete mode 100644 build/client/assets/functions-B7s41jYM.js create mode 100644 build/client/assets/functions-DMKVu1r-.js create mode 100644 build/client/assets/github_widget-R1gwNBdG.js delete mode 100644 build/client/assets/github_widget-X-pkoqDF.js delete mode 100644 build/client/assets/index-B37bIhWk.js delete mode 100644 build/client/assets/index-BgfNTfNX.js create mode 100644 build/client/assets/index-CZUknZXM.js delete mode 100644 build/client/assets/index-DCm1eCXF.js delete mode 100644 build/client/assets/index-_rCr0AGk.js create mode 100644 build/client/assets/input-ChpOE9Lf.js delete mode 100644 build/client/assets/login-BmnM70Yh.js create mode 100644 build/client/assets/login-DTEqUQMw.js rename build/client/assets/{logo-BYODAW8L.js => logo-COugf1Vw.js} (99%) create mode 100644 build/client/assets/manifest-32adbcfa.js delete mode 100644 build/client/assets/manifest-ab38ca53.js delete mode 100644 build/client/assets/package-BHlkEMfo.js create mode 100644 build/client/assets/package-BtyHozD1.js delete mode 100644 build/client/assets/root-C_ObZ1CP.js create mode 100644 build/client/assets/root-DYAgCu9X.js delete mode 100644 build/client/assets/root-nUEo1Ulo.css create mode 100644 build/client/assets/root-sL7v7eY3.css create mode 100644 build/client/assets/select-Cem4k-Od.js create mode 100644 build/client/assets/table-CX2GO18n.js create mode 100644 build/client/assets/toast-hIcI356p.js delete mode 100644 build/client/assets/useMatchMedia-DCVgUbq7.js create mode 100644 build/client/assets/utils-DcAaEMf9.js delete mode 100644 build/client/assets/wrapComponent-DVKhoVmh.js create mode 100644 build/client/assets/x-OohQberg.js delete mode 100644 console/telemetry.js diff --git a/README.md b/README.md index 51a80b3..978fb9f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@
-

A web-based MongoDB admin interface written with Remix, Vite, IBM Carbon Design, TailwindCSS and Prisma


+

A web-based MongoDB admin interface written with Remix, Vite, TailwindCSS and Prisma


MongoCarbon: Similar to Mongo Express, MongoDB Compass, MongoUi...

MongoCarbon offers database management for mongodb instances @@ -123,31 +123,6 @@ or if you installed it globally, you can immediately start mongocarbon like this You must be using https to login otherwise you need to add SECURE_COOKIE=0 to your .env file in the root of your project -## Installation fails with `ibmtelemetry: Permission Denied` - -- If you are installing the package as part of another project, make sure your package.json contains the a name and the version for the package -- If needed, create a new file named `telemetry.js` in your project's root directory. Add the following code to the file: - - ```javascript - #!/usr/bin/env node - - ``` - -- Update `package.json` with - ```json - { - "name": "your-project-name", - "version": "1.0.0", - "scripts": { - // ...other scripts - }, - "bin": { - "ibmtelemetry": "./telemetry.js" - } - } - ``` -- If still doesn't work, then create a new `package.json` with `npm init` and install mongocarbon then add your dependencies - ## Usage (Docker) Make sure you have a running [MongoDB container](https://hub.docker.com/_/mongo/) on a Docker network (`--network some-network` below) with `--name` or `--network-alias` set to `mongo` and then create the user diff --git a/app/components/alert.tsx b/app/components/alert.tsx index ebf85c9..7787356 100644 --- a/app/components/alert.tsx +++ b/app/components/alert.tsx @@ -1,4 +1,4 @@ -import { Close } from "@carbon/icons-react"; +import { XIcon } from "@primer/octicons-react"; export const AlertMessage = ({ message, type = "error", onClose }) => { if (!message || message.trim() == "") { @@ -6,10 +6,10 @@ export const AlertMessage = ({ message, type = "error", onClose }) => { } return (
-
+
{message} -
diff --git a/app/components/collection.tsx b/app/components/collection.tsx index 3b599d5..60a105f 100644 --- a/app/components/collection.tsx +++ b/app/components/collection.tsx @@ -1,194 +1,188 @@ -import { FormGroup, Stack, Button, Checkbox, NumberInput, FileUploader, RadioButtonGroup, Search, Select, SelectItem, TextInput, TextArea, Modal, Dropdown } from "@carbon/react"; -import { Form, useFetcher, useNavigate } from "@remix-run/react"; -import { useEffect, useRef, useState } from "react"; +import { Input } from "@ui/input"; +import { Form, useFetcher } from "@remix-run/react"; +import { useEffect, useState } from "react"; +import Modal from "~/ui/modal"; +import { toast } from "~/ui/hooks/use-toast"; interface CollectionAddModalProps { - open: boolean; - dbName: string; - onClose: () => void; - onSuccess: (dbName: string, collectionName: string) => void; + open: boolean; + dbName: string; + onClose: () => void; + onSuccess: (dbName: string, collectionName: string) => void; } export const CollectionAddModal = ({ dbName, open, onClose, onSuccess }: CollectionAddModalProps) => { - const [state, setState] = useState({ collectionName: "" }); + const [state, setState] = useState({ collectionName: "" }); - const [errors, setErrors] = useState({}); - const fetcher = useFetcher<{ - status: string; - errors?: any; - message?: string; - redirect?: string; - }>(); + const [errors, setErrors] = useState({}); + const fetcher = useFetcher<{ + status: string; + errors?: any; + message?: string; + redirect?: string; + }>(); - useEffect(() => { - if (fetcher.data && fetcher.data.status == "success") { - setDescription("Created!"); - setStatus("finished"); - setTimeout(() => { - onSuccess(dbName, state.collectionName.trim()); - onClose(); - }, 2000); - } else if (fetcher.data && fetcher.data.status == "error") { - setDescription(fetcher.data.message || ""); - setStatus("error"); - setErrors(fetcher.data.errors || {}); + useEffect(() => { + if (fetcher.data && fetcher.data.status == "success") { + setDescription("Created!"); + toast({ + title: "Connected!", + description: fetcher.data.message || "Connected!", + type: "background", + }); - setTimeout(() => { - resetStatus(); - }, 2000); - } - }, [fetcher.data]); + setTimeout(() => { + onSuccess(dbName, state.collectionName.trim()); + onClose(); + }, 2000); + } else if (fetcher.data && fetcher.data.status == "error") { + setDescription(fetcher.data.message || ""); + setErrors(fetcher.data.errors || {}); + toast({ + title: "Error", + description: fetcher.data.message || "An error occurred", + type: "background", + }); + } + }, [fetcher.data]); - const [status, setStatus] = useState("inactive"); - const [description, setDescription] = useState("Creating..."); + const [description, setDescription] = useState("Creating..."); - const submit = async () => { - setStatus("active"); - fetcher.submit( - { - create: state, - }, - { - method: "POST", - encType: "application/json", - action: `/database/${dbName}`, - } - ); - }; + const submit = async () => { + fetcher.submit( + { + create: state, + }, + { + method: "POST", + encType: "application/json", + action: `/database/${dbName}`, + } + ); + }; - const resetStatus = () => { - setStatus("inactive"); - setDescription("Creating..."); - }; - - return ( - <> - -
- - - { - setState({ ...state, collectionName: e.target.value }); - }} - /> - - -
-
- - ); + return ( + <> + +
+ { + setState({ ...state, collectionName: e.target.value }); + }} + /> +
+
+ + ); }; interface CollectionDeleteModalProps { - open: boolean; - onClose: () => void; - onSuccess: () => void; - dbName: string; - collectionName: string; + open: boolean; + onClose: () => void; + onSuccess: () => void; + dbName: string; + collectionName: string; } export const CollectionDeleteModal = ({ open, onClose, onSuccess, dbName, collectionName }: CollectionDeleteModalProps) => { - const fetcher = useFetcher<{ - status: string; - errors?: any; - message?: string; - redirect?: string; - }>(); + const fetcher = useFetcher<{ + status: string; + errors?: any; + message?: string; + redirect?: string; + }>(); - const [status, setStatus] = useState("inactive"); - const [description, setDescription] = useState("Deleting..."); - const [confirmName, setConfirmName] = useState(""); - const [errors, setErrors] = useState({}); + const [status, setStatus] = useState("inactive"); + const [description, setDescription] = useState("Deleting..."); + const [confirmName, setConfirmName] = useState(""); + const [errors, setErrors] = useState({}); - useEffect(() => { - if (fetcher.data && fetcher.data.status == "success") { - setDescription("Deleted!"); - setStatus("finished"); - setTimeout(() => { - onSuccess(); - onClose(); - }, 2000); - } else if (fetcher.data && fetcher.data.status == "error") { - setDescription(fetcher.data.message || ""); - setStatus("error"); + useEffect(() => { + if (fetcher.data && fetcher.data.status == "success") { + setDescription("Deleted!"); + setStatus("finished"); + setTimeout(() => { + onSuccess(); + onClose(); + }, 2000); + } else if (fetcher.data && fetcher.data.status == "error") { + setDescription(fetcher.data.message || ""); + setStatus("error"); - setTimeout(() => { - setStatus("inactive"); - setDescription("Deleting..."); - }, 2000); - } - }, [fetcher.data]); + setTimeout(() => { + setStatus("inactive"); + setDescription("Deleting..."); + }, 2000); + } + }, [fetcher.data]); - const submitDelete = async () => { - if (confirmName != collectionName) { - setErrors({ collectionName: "The name does not match the collection name" }); - return; - } - setStatus("active"); - fetcher.submit( - { - delete: { collectionName }, - }, - { - method: "POST", - encType: "application/json", - action: `/database/${dbName}`, - } - ); - }; - if (!collectionName || collectionName == "") return null; - return ( - -

- Are you sure you want to delete the collection "{collectionName}"? -

-
Deleting a collection will delete all documents in the collection and this action cannot be undone
- { - setErrors({}); - setConfirmName(e.target.value); - }} - /> -
- ); + const submitDelete = async () => { + if (confirmName != collectionName) { + setErrors({ collectionName: "The name does not match the collection name" }); + return; + } + setStatus("active"); + fetcher.submit( + { + delete: { collectionName }, + }, + { + method: "POST", + encType: "application/json", + action: `/database/${dbName}`, + } + ); + }; + if (!collectionName || collectionName == "") return null; + return ( + +

+ Are you sure you want to delete the collection "{collectionName}"? +

+
Deleting a collection will delete all documents in the collection and this action cannot be undone
+ { + setErrors({}); + setConfirmName(e.target.value); + }} + /> +
+ ); }; diff --git a/app/components/connection.tsx b/app/components/connection.tsx index 2ef7e7e..7ad7f10 100644 --- a/app/components/connection.tsx +++ b/app/components/connection.tsx @@ -1,407 +1,439 @@ -import { FormGroup, Stack, Button, Checkbox, NumberInput, FileUploader, RadioButtonGroup, Search, Select, SelectItem, TextInput, TextArea, Modal, Dropdown } from "@carbon/react"; -import { Form, useFetcher, useNavigate } from "@remix-run/react"; -import { useEffect, useRef, useState } from "react"; +import { Form, useFetcher } from "@remix-run/react"; +import { useEffect, useState } from "react"; +import { Checkbox } from "~/ui/checkbox"; +import { toast } from "~/ui/hooks/use-toast"; +import { Input } from "~/ui/input"; +import Modal from "~/ui/modal"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "~/ui/select"; interface ConnectionDeleteModalProps { - open: boolean; - onClose: () => void; - onSuccess: () => void; - id: string; + open: boolean; + onClose: () => void; + onSuccess: () => void; + id: string; } const ConnectionDeleteModal = ({ open, onClose, onSuccess, id }: ConnectionDeleteModalProps) => { - const fetcher = useFetcher<{ - status: string; - errors?: any; - message?: string; - redirect?: string; - }>(); + const fetcher = useFetcher<{ + status: string; + errors?: any; + message?: string; + redirect?: string; + }>(); - const [status, setStatus] = useState("inactive"); - const [description, setDescription] = useState("Deleting..."); + const [status, setStatus] = useState("inactive"); + const [description, setDescription] = useState("Deleting..."); - useEffect(() => { - if (fetcher.data && fetcher.data.status == "success") { - setDescription("Deleted!"); - setStatus("finished"); - setTimeout(() => { - onSuccess(); - onClose(); - }, 2000); - } else if (fetcher.data && fetcher.data.status == "error") { - setDescription(fetcher.data.message || ""); - setStatus("error"); + useEffect(() => { + if (fetcher.data && fetcher.data.status == "success") { + setDescription("Deleted!"); + setStatus("finished"); + setTimeout(() => { + onSuccess(); + onClose(); + }, 2000); + } else if (fetcher.data && fetcher.data.status == "error") { + setDescription(fetcher.data.message || ""); + setStatus("error"); - setTimeout(() => { - setStatus("inactive"); - setDescription("Deleting..."); - }, 2000); - } - }, [fetcher.data]); + setTimeout(() => { + setStatus("inactive"); + setDescription("Deleting..."); + }, 2000); + } + }, [fetcher.data]); - const submitDelete = async () => { - setStatus("active"); - fetcher.submit( - { - delete: { id }, - }, - { - method: "POST", - encType: "application/json", - action: `/connections`, - } - ); - }; - return ( - -

Are you sure you want to delete this connection?

-
- ); + const submitDelete = async () => { + setStatus("active"); + fetcher.submit( + { + delete: { id }, + }, + { + method: "POST", + encType: "application/json", + action: `/connections`, + } + ); + }; + return ( + +

Are you sure you want to delete this connection?

+
+ ); }; interface ConnectionModalProps { - open: boolean; - onClose: () => void; - onSuccess: () => void; - mode?: "add" | "edit"; - initialData?: any; + open: boolean; + onClose: () => void; + onSuccess: () => void; + mode?: "add" | "edit"; + initialData?: any; } export const ConnectionModal = ({ open, onClose, onSuccess, mode = "add", initialData = null }: ConnectionModalProps) => { - const [state, setState] = useState( - initialData ?? { - id: undefined, - allowDiskUse: true, - name: "", - connectionString: "", - tls: false, - tlsAllowInvalidCertificates: true, - tlsCAFile: "", - tlsCertificateKeyFile: "", - tlsCertificateKeyFilePassword: "", - maxPoolSize: 4, - whitelist: "", - blacklist: "", - } - ); + const [state, setState] = useState( + initialData ?? { + id: undefined, + allowDiskUse: true, + name: "", + connectionString: "", + tls: false, + tlsAllowInvalidCertificates: true, + tlsCAFile: "", + tlsCertificateKeyFile: "", + tlsCertificateKeyFilePassword: "", + maxPoolSize: 4, + whitelist: "", + blacklist: "", + } + ); - const [errors, setErrors] = useState({}); - const fetcher = useFetcher<{ - status: string; - errors?: any; - message?: string; - redirect?: string; - }>(); + const [errors, setErrors] = useState({}); + const fetcher = useFetcher<{ + status: string; + errors?: any; + message?: string; + redirect?: string; + }>(); - useEffect(() => { - setState( - initialData ?? { - id: undefined, - allowDiskUse: true, - name: "", - connectionString: "", - tls: false, - tlsAllowInvalidCertificates: true, - tlsCAFile: "", - tlsCertificateKeyFile: "", - tlsCertificateKeyFilePassword: "", - maxPoolSize: 4, - whitelist: "", - blacklist: "", - } - ); - }, [initialData]); + useEffect(() => { + setState( + initialData ?? { + id: undefined, + allowDiskUse: true, + name: "", + connectionString: "", + tls: false, + tlsAllowInvalidCertificates: true, + tlsCAFile: "", + tlsCertificateKeyFile: "", + tlsCertificateKeyFilePassword: "", + maxPoolSize: 4, + whitelist: "", + blacklist: "", + } + ); + }, [initialData]); - useEffect(() => { - if (fetcher.data && fetcher.data.status == "success") { - setDescription(mode == "edit" ? "Updated" : "Connected!"); - setStatus("finished"); - setTimeout(() => { - onSuccess(); - onClose(); - }, 2000); - } else if (fetcher.data && fetcher.data.status == "error") { - setDescription(fetcher.data.message || ""); - setStatus("error"); - setErrors(fetcher.data.errors || {}); + useEffect(() => { + if (fetcher.data && fetcher.data.status == "success") { + setDescription(mode == "edit" ? "Updated" : "Connected!"); + toast({ + title: mode == "edit" ? "Updated" : "Connected!", + type: "background", + }); - setTimeout(() => { - resetStatus(); - }, 2000); - } - }, [fetcher.data]); + setTimeout(() => { + onSuccess(); + onClose(); + }, 2000); + } else if (fetcher.data && fetcher.data.status == "error") { + setDescription(fetcher.data.message || ""); + setErrors(fetcher.data.errors || {}); + toast({ + title: "Error", + description: fetcher.data.message || "An error occurred", + variant: "error", + }); + } + }, [fetcher.data]); - const [status, setStatus] = useState("inactive"); - const [description, setDescription] = useState("Connecting..."); - const [modalDelete, setModalDelete] = useState(false); + const [description, setDescription] = useState("Connecting..."); + const [modalDelete, setModalDelete] = useState(false); - const submit = async () => { - setStatus("active"); - fetcher.submit( - { - [mode]: state, - }, - { - method: "POST", - encType: "application/json", - action: `/connections`, - } - ); - }; + const submit = async () => { + fetcher.submit( + { + [mode]: state, + }, + { + method: "POST", + encType: "application/json", + action: `/connections`, + } + ); + }; - const resetStatus = () => { - setStatus("inactive"); - setDescription("Connecting..."); - }; - - return ( - <> - { - setModalDelete(true); - } - : submit - } - loadingStatus={fetcher.state == "loading" || fetcher.state == "submitting" ? "active" : (status as any)} - loadingDescription={description} - onLoadingSuccess={resetStatus} - modalHeading={mode == "edit" ? `Update Connection` : "Create a new database connection"} - modalLabel="Database Connection" - onSecondarySubmit={mode == "edit" ? submit : undefined} - primaryButtonText={mode == "edit" ? "Delete" : "Create"} - secondaryButtonText={mode == "edit" ? "Update" : "Cancel"}> -
- - { - setState({ ...state, name: e.target.value }); - }} - /> -
- - { - setState({ ...state, connectionString: e.target.value }); - }} - /> + return ( + <> + { + setModalDelete(true); + } + : submit + } + loading={fetcher.state == "loading" || fetcher.state == "submitting" ? true : false} + loadingDescription={description} + modalHeading={mode == "edit" ? `Update Connection` : "Create a new database connection"} + modalLabel="Database Connection" + onSecondaryClick={mode == "edit" ? submit : undefined} + primaryButtonText={mode == "edit" ? "Delete" : "Create"} + secondaryButtonText={mode == "edit" ? "Update" : "Cancel"}> + +
+ { + setState({ ...state, name: e.target.value }); + }} + /> +
+
+ { + setState({ ...state, connectionString: e.target.value }); + }} + /> - View Documentation - - *Use "directConnection=true" parameter to run operations on host View More - - - { - setState({ ...state, tls: e.target.checked }); - }} - /> - - { - setState({ ...state, tlsAllowInvalidCertificates: e.target.checked }); - }} - disabled={!state.tls} - /> - { - setState({ ...state, tlsCAFile: e.target.value }); - }} - /> - { - setState({ ...state, tlsCertificateKeyFile: e.target.value }); - }} - /> - { - setState({ ...state, tlsCertificateKeyFilePassword: e.target.value }); - }} - /> - - { - setState({ ...state, maxPoolSize: Number((e.target as HTMLInputElement).value) }); - }} - /> - { - setState({ ...state, whitelist: e.target.value }); - }} - /> - { - setState({ ...state, blacklist: e.target.value }); - }} - /> + View Documentation + + + *Use "directConnection=true" parameter to run operations on host{" "} + + View More + + +
+ { + setState({ ...state, tls: checked }); + }} + /> +
+ { + setState({ ...state, tlsAllowInvalidCertificates: checked }); + }} + disabled={!state.tls} + /> + { + setState({ ...state, tlsCAFile: e.target.value }); + }} + /> + { + setState({ ...state, tlsCertificateKeyFile: e.target.value }); + }} + /> + { + setState({ ...state, tlsCertificateKeyFilePassword: e.target.value }); + }} + /> +
+ { + setState({ ...state, maxPoolSize: Number((e.target as HTMLInputElement).value) }); + }} + /> + { + setState({ ...state, whitelist: e.target.value }); + }} + /> + { + setState({ ...state, blacklist: e.target.value }); + }} + /> - { - setState({ ...state, allowDiskUse: e.target.checked }); - }} - /> - - - - setModalDelete(false)} - onSuccess={() => { - onSuccess(); - onClose(); - }} - id={state.id} - /> - - ); + { + setState({ ...state, allowDiskUse: checked }); + }} + /> +
+ +
+ setModalDelete(false)} + onSuccess={() => { + onSuccess(); + onClose(); + }} + id={state.id} + /> + + ); }; export const SwitchConnection = ({ current, connections }) => { - const fetcher = useFetcher<{ - status: string; - errors?: any; - message?: string; - redirect?: string; - }>(); - useEffect(() => {}, []); - // useEffect(() => { - // if (fetcher.data && fetcher.data.status == "success") { - // window.location.reload(); - // } - // }, [fetcher.data]); - const onChange = ({ selectedItem }) => { - if (!selectedItem) { - return; - } - if (selectedItem.id == "new") { - window.location.href = `/connections`; - return; - } - const connection = connections.find((c) => c.id == selectedItem.id); - if (connection) { - fetcher.submit( - { - connect: { id: connection.id }, - }, - { - method: "POST", - encType: "application/json", - action: `/connections`, - } - ); - } - }; - return ( - (item ? item.name : "")} - /> - ); + const fetcher = useFetcher<{ + status: string; + errors?: any; + message?: string; + redirect?: string; + }>(); + + const [selectedConnection, setSelectedConnection] = useState(current); + useEffect(() => { + if (selectedConnection && (fetcher.state == "submitting" || fetcher.state == "loading")) { + const connection = connections.find((c) => c.id == selectedConnection.id); + if (connection) { + toast({ + title: "Connecting, Please wait...", + description: `Connecting to "${connection.name}"`, + variant: "default", + }); + } + } + }, [fetcher.state]); + useEffect(() => { + if (fetcher.data && fetcher.data.status == "success") { + window.location.href = fetcher.data.redirect || "/"; + } else if (fetcher.data && fetcher.data.status == "error") { + toast({ + title: "Error", + description: fetcher.data.message, + variant: "error", + }); + } + }, [fetcher.data]); + const onChange = (selectedItem) => { + if (!selectedItem || !selectedItem.id) { + return; + } + const connection = connections.find((c) => c.id == selectedItem.id); + if (connection) { + setSelectedConnection(connection); + fetcher.submit( + { + connect: { id: connection.id }, + }, + { + method: "POST", + encType: "application/json", + action: `/connections`, + } + ); + } + }; + + let items = connections || []; + return ( + <> + + + ); }; diff --git a/app/components/copy_text.tsx b/app/components/copy_text.tsx index aad028e..97b1fa1 100644 --- a/app/components/copy_text.tsx +++ b/app/components/copy_text.tsx @@ -1,55 +1,55 @@ -import { Button } from "@carbon/react"; -import { Checkmark, Copy, CopyFile } from "@carbon/icons-react"; -import { SyntheticEvent, useState } from "react"; +import { Button } from "@ui/button"; +import { CheckIcon, CopyIcon } from "@primer/octicons-react"; +import { useState } from "react"; interface TitleWithCopy { - text?: string; - className?: string; + text?: string; + className?: string; } interface ButtonWithCopy { - text?: string; - [x: string]: any + text?: string; + [x: string]: any; } export const CopyText: React.FC = ({ text, className }) => { - const [copied, setCopied] = useState(false); - const handleCopy = () => { - if (!text) { - return; - } - navigator.clipboard.writeText(String(text)).then(() => { - setCopied(true); - setTimeout(() => { - setCopied(false); - }, 1000); - }); - }; + const [copied, setCopied] = useState(false); + const handleCopy = () => { + if (!text) { + return; + } + navigator.clipboard.writeText(String(text)).then(() => { + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 1000); + }); + }; - return ( - - {copied ? : } - - ); + return ( + + {copied ? : } + + ); }; export const CopyTextButton: React.FC = ({ text, children, ...rest }) => { - const [copied, setCopied] = useState(false); - const handleCopy = (e) => { + const [copied, setCopied] = useState(false); + const handleCopy = (e) => { e.preventDefault(); - if (!text) { - return; - } - navigator.clipboard.writeText(String(text)).then(() => { - setCopied(true); - setTimeout(() => { - setCopied(false); - }, 1000); - }); - }; + if (!text) { + return; + } + navigator.clipboard.writeText(String(text)).then(() => { + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 1000); + }); + }; - return ( - - ); + return ( + + ); }; diff --git a/app/components/csv_table.tsx b/app/components/csv_table.tsx index 8c6bb9e..65dd20e 100644 --- a/app/components/csv_table.tsx +++ b/app/components/csv_table.tsx @@ -1,82 +1,85 @@ -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@carbon/react"; import { JsonTreeEditor } from "./tree"; import { CopyText } from "./copy_text"; import { EJSON, ObjectId } from "bson"; +import { SortAscIcon, SortDescIcon } from "@primer/octicons-react"; interface CsvTableProps { - sort: { field: string; direction: number }; - rows: any[]; - allowEdit?: boolean; - onSort: (field: string) => void; + sort: { field: string; direction: number }; + rows: any[]; + allowEdit?: boolean; + onSort: (field: string) => void; } export const CsvTable = ({ sort, rows, allowEdit = false, onSort }: CsvTableProps) => { - const headers: string[] = []; - if (rows.length > 0) { - rows.forEach((row) => { - Object.keys(row).forEach((key: string) => { - if (!headers.includes(key)) headers.push(key); - }); - }); - } - return ( - - - - {headers.map((header, index) => { - return ( - 0 ? "ASC" : "DESC") : "NONE"} - isSortHeader={sort.field == header} - isSortable={true} - onClick={() => { - onSort(header); - }} - id={`${header}-${index}`} - key={header}> - {header} - - ); - })} - - - - {rows.map((row) => ( - - {Object.keys(row).map((key) => { - if ((Array.isArray(row[key]) || row[key] instanceof Object) && !(row[key] instanceof ObjectId || row[key] instanceof Date)) { - - return ( - -
- - -
-
- ); - } + const headers: string[] = []; + if (rows.length > 0) { + rows.forEach((row) => { + Object.keys(row).forEach((key: string) => { + if (!headers.includes(key)) headers.push(key); + }); + }); + } + return ( +
+
+ + + {headers.map((header, index) => { + return ( + + ); + })} + + + + {rows.map((row, rowIndex) => ( + + {Object.keys(row).map((key) => { + if ((Array.isArray(row[key]) || row[key] instanceof Object) && !(row[key] instanceof ObjectId || row[key] instanceof Date)) { + return ( + + ); + } - let output = ""; - if (row[key] instanceof ObjectId) { - output = `ObjectId("${row[key].toHexString()}")`; - } else if (row[key] instanceof Date) { - output = row[key].toISOString(); - } else { - output = EJSON.stringify(row[key]); - } - return ( - -
- {EJSON.stringify(row[key])} - -
-
- ); - })} - - ))} - -
{ + onSort(header); + }} + id={`${header}-${index}`} + key={header}> +
+ {header} + + {sort.field == header && sort.direction > 0 ? : } + +
+
+
+ + +
+
- ); + let output = ""; + if (row[key] instanceof ObjectId) { + output = `ObjectId("${row[key].toHexString()}")`; + } else if (row[key] instanceof Date) { + output = row[key].toISOString(); + } else { + output = EJSON.stringify(row[key]); + } + return ( + +
+ {EJSON.stringify(row[key])} + +
+ + ); + })} + + ))} + + +
+ ); }; diff --git a/app/components/database.tsx b/app/components/database.tsx index c23a0b7..9b0d605 100644 --- a/app/components/database.tsx +++ b/app/components/database.tsx @@ -1,213 +1,221 @@ -import { FormGroup, Stack, Button, Checkbox, NumberInput, FileUploader, RadioButtonGroup, Search, Select, SelectItem, TextInput, TextArea, Modal, Dropdown } from "@carbon/react"; -import { Form, useFetcher, useNavigate } from "@remix-run/react"; -import { useEffect, useRef, useState } from "react"; +import { Form, useFetcher } from "@remix-run/react"; +import { useEffect, useState } from "react"; +import { Input } from "@ui/input"; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "~/ui/dialog"; +import { Button } from "~/ui/button"; +import Modal from "~/ui/modal"; +import { toast } from "~/ui/hooks/use-toast"; interface DatabaseAddModalProps { - open: boolean; - onClose: () => void; - onSuccess: (dbName: string, collectionName: string) => void; + open: boolean; + onClose: () => void; + onSuccess: (dbName: string, collectionName: string) => void; } export const DatabaseAddModal = ({ open, onClose, onSuccess }: DatabaseAddModalProps) => { - const [state, setState] = useState({ dbName: "", collectionName: "" }); - - const [errors, setErrors] = useState({}); - const fetcher = useFetcher<{ - status: string; - errors?: any; - message?: string; - redirect?: string; - }>(); - - useEffect(() => { - if (fetcher.data && fetcher.data.status == "success") { - setDescription("Created!"); - setStatus("finished"); - setTimeout(() => { - onSuccess(state.dbName.trim(), state.collectionName.trim()); - onClose(); - }, 2000); - } else if (fetcher.data && fetcher.data.status == "error") { - setDescription(fetcher.data.message || ""); - setStatus("error"); - setErrors(fetcher.data.errors || {}); - - setTimeout(() => { - resetStatus(); - }, 2000); - } - }, [fetcher.data]); - - const [status, setStatus] = useState("inactive"); - const [description, setDescription] = useState("Creating..."); - - const submit = async () => { - setStatus("active"); - fetcher.submit( - { - create: state, - }, - { - method: "POST", - encType: "application/json", - action: `/database`, - } - ); - }; - - const resetStatus = () => { - setStatus("inactive"); - setDescription("Creating..."); - }; - - return ( - <> - -
- - { - setState({ ...state, dbName: e.target.value }); - }} - /> -
- - { - setState({ ...state, collectionName: e.target.value }); - }} - /> - - *MongoDb will only create a database if a collection is added. - - View More - - - -
-
-
- - ); + const [state, setState] = useState({ dbName: "", collectionName: "" }); + + const [errors, setErrors] = useState({}); + const fetcher = useFetcher<{ + status: string; + errors?: any; + message?: string; + redirect?: string; + }>(); + + useEffect(() => { + if (fetcher.data && fetcher.data.status == "success") { + setDescription("Created!"); + toast({ + title: "Database Created!", + type: "background", + }); + setTimeout(() => { + onSuccess(state.dbName.trim(), state.collectionName.trim()); + onClose(); + }, 2000); + } else if (fetcher.data && fetcher.data.status == "error") { + setDescription(fetcher.data.message || ""); + + setErrors(fetcher.data.errors || {}); + + toast({ + title: "Error", + description: fetcher.data.message || "An error occurred", + type: "background", + }); + } + }, [fetcher.data]); + + const [description, setDescription] = useState("Creating..."); + + const submit = async () => { + fetcher.submit( + { + create: state, + }, + { + method: "POST", + encType: "application/json", + action: `/database`, + } + ); + }; + + return ( + <> + +
+
+ { + setState({ ...state, dbName: e.target.value }); + }} + /> +
+
+ { + setState({ ...state, collectionName: e.target.value }); + }} + /> + + *MongoDb will only create a database if a collection is added. + + Learn More + + +
+
+
+
+ + ); }; interface DatabaseDeleteModalProps { - open: boolean; - onClose: () => void; - onSuccess: () => void; - name: string; + open: boolean; + onClose: () => void; + onSuccess: () => void; + name: string; } export const DatabaseDeleteModal = ({ open, onClose, onSuccess, name }: DatabaseDeleteModalProps) => { - const fetcher = useFetcher<{ - status: string; - errors?: any; - message?: string; - redirect?: string; - }>(); - - const [status, setStatus] = useState("inactive"); - const [description, setDescription] = useState("Deleting..."); - const [confirmName, setConfirmName] = useState(""); - const [errors, setErrors] = useState({}); - - useEffect(() => { - if (fetcher.data && fetcher.data.status == "success") { - setDescription("Deleted!"); - setStatus("finished"); - setTimeout(() => { - onSuccess(); - onClose(); - }, 2000); - } else if (fetcher.data && fetcher.data.status == "error") { - setDescription(fetcher.data.message || ""); - setStatus("error"); - - setTimeout(() => { - setStatus("inactive"); - setDescription("Deleting..."); - }, 2000); - } - }, [fetcher.data]); - - const submitDelete = async () => { - if (confirmName != name) { - setErrors({ name: "The name does not match the database name" }); - return; - } - setStatus("active"); - fetcher.submit( - { - delete: { name }, - }, - { - method: "POST", - encType: "application/json", - action: `/database`, - } - ); - }; - if (!name || name == "") return null; - return ( - -

- Are you sure you want to drop the database "{name}"? -

-
Dropping a database will delete all collections and documents in the database and this action cannot be undone
- { - setErrors(() => ({})); - setConfirmName(e.target.value); - }} - /> -
- ); + const fetcher = useFetcher<{ + status: string; + errors?: any; + message?: string; + redirect?: string; + }>(); + + const [description, setDescription] = useState("Deleting..."); + const [confirmName, setConfirmName] = useState(""); + const [errors, setErrors] = useState({}); + + useEffect(() => { + if (fetcher.data && fetcher.data.status == "success") { + setDescription("Deleted!"); + toast({ + title: "Database Deleted!", + type: "background", + }); + setTimeout(() => { + onSuccess(); + onClose(); + }, 2000); + } else if (fetcher.data && fetcher.data.status == "error") { + setDescription(fetcher.data.message || ""); + toast({ + title: "Error", + description: fetcher.data.message || "An error occurred", + type: "background", + }); + + setTimeout(() => { + setDescription("Deleting..."); + }, 2000); + } + }, [fetcher.data]); + + const submitDelete = async () => { + if (confirmName != name) { + setErrors({ name: "The name does not match the database name" }); + return; + } + + fetcher.submit( + { + delete: { name }, + }, + { + method: "POST", + encType: "application/json", + action: `/database`, + } + ); + }; + if (!name || name == "") return null; + return ( + +

+ Are you sure you want to drop the database "{name}"? +

+
Dropping a database will delete all collections and documents in the database and this action cannot be undone
+ { + setErrors(() => ({})); + setConfirmName(e.target.value); + }} + /> +
+ ); }; diff --git a/app/components/footer.tsx b/app/components/footer.tsx index 04997a3..c48a67d 100644 --- a/app/components/footer.tsx +++ b/app/components/footer.tsx @@ -1,7 +1,7 @@ import packageJson from "../../package.json"; export const Footer = () => { return ( -