From af9349d4a7b33c0264bb49ce24ef3f76fa9ebff2 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Mon, 16 Jan 2023 15:33:04 +1000 Subject: [PATCH] Conform hosts table --- frontend/src/locale/src/de.json | 6 +- frontend/src/locale/src/en.json | 6 +- frontend/src/locale/src/fa.json | 6 +- frontend/src/modals/HostCreateModal.tsx | 218 ++++++++++++++++++ frontend/src/modals/index.ts | 1 + .../pages/Hosts/{HostsTable.tsx => Table.tsx} | 31 ++- frontend/src/pages/Hosts/TableWrapper.tsx | 97 ++++++++ frontend/src/pages/Hosts/index.tsx | 97 ++------ 8 files changed, 355 insertions(+), 107 deletions(-) create mode 100644 frontend/src/modals/HostCreateModal.tsx rename frontend/src/pages/Hosts/{HostsTable.tsx => Table.tsx} (89%) create mode 100644 frontend/src/pages/Hosts/TableWrapper.tsx diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json index ae247e3f..fa56e497 100644 --- a/frontend/src/locale/src/de.json +++ b/frontend/src/locale/src/de.json @@ -119,9 +119,6 @@ "create-hint": { "defaultMessage": "Warum erstellen Sie nicht eine?" }, - "create-host": { - "defaultMessage": "Host erstellen" - }, "create-host-title": { "defaultMessage": "Es gibt keine Proxy-Hosts" }, @@ -242,6 +239,9 @@ "general-settings.title": { "defaultMessage": "Allgemeine Einstellungen" }, + "host.create": { + "defaultMessage": "Host erstellen" + }, "hosts.title": { "defaultMessage": "Gastgeber" }, diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index 1d6c226e..c539c5e2 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -395,9 +395,6 @@ "create-hint": { "defaultMessage": "Why don't you create one?" }, - "create-host": { - "defaultMessage": "Create Host" - }, "create-host-title": { "defaultMessage": "There are no Hosts" }, @@ -518,6 +515,9 @@ "general-settings.title": { "defaultMessage": "General Settings" }, + "host.create": { + "defaultMessage": "Create Host" + }, "hosts.title": { "defaultMessage": "Hosts" }, diff --git a/frontend/src/locale/src/fa.json b/frontend/src/locale/src/fa.json index 7922bc1c..92d45efd 100644 --- a/frontend/src/locale/src/fa.json +++ b/frontend/src/locale/src/fa.json @@ -119,9 +119,6 @@ "create-hint": { "defaultMessage": "چرا یکی را ایجاد نمی کنید؟" }, - "create-host": { - "defaultMessage": "هاست ایجاد کنید" - }, "create-nginx-template": { "defaultMessage": "قالب هاست ایجاد کنید" }, @@ -245,6 +242,9 @@ "nginx-templates.title": { "defaultMessage": "قالب های میزبان" }, + "host.create": { + "defaultMessage": "هاست ایجاد کنید" + }, "hosts.title": { "defaultMessage": "میزبان" }, diff --git a/frontend/src/modals/HostCreateModal.tsx b/frontend/src/modals/HostCreateModal.tsx new file mode 100644 index 00000000..dad69c54 --- /dev/null +++ b/frontend/src/modals/HostCreateModal.tsx @@ -0,0 +1,218 @@ +import { + Button, + Checkbox, + FormControl, + FormErrorMessage, + FormLabel, + Input, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, + Stack, + useToast, +} from "@chakra-ui/react"; +import { CertificateAuthority } from "api/npm"; +import { PrettyButton } from "components"; +import { Formik, Form, Field } from "formik"; +import { useSetCertificateAuthority } from "hooks"; +import { intl } from "locale"; +import { validateNumber, validateString } from "modules/Validations"; + +interface HostCreateModalProps { + isOpen: boolean; + onClose: () => void; +} +function HostCreateModal({ isOpen, onClose }: HostCreateModalProps) { + const toast = useToast(); + const { mutate: setCertificateAuthority } = useSetCertificateAuthority(); + + const onSubmit = async ( + payload: CertificateAuthority, + { setErrors, setSubmitting }: any, + ) => { + const showErr = (msg: string) => { + toast({ + description: intl.formatMessage({ + id: `error.${msg}`, + }), + status: "error", + position: "top", + duration: 3000, + isClosable: true, + }); + }; + + setCertificateAuthority(payload, { + onError: (err: any) => { + if (err.message === "ca-bundle-does-not-exist") { + setErrors({ + caBundle: intl.formatMessage({ + id: `error.${err.message}`, + }), + }); + } else { + showErr(err.message); + } + }, + onSuccess: () => onClose(), + onSettled: () => setSubmitting(false), + }); + }; + + return ( + + + + + {({ isSubmitting }) => ( +
+ + {intl.formatMessage({ id: "certificate-authority.create" })} + + + + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.name", + })} + + + {form.errors.name} + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.acmesh-server", + })} + + + + {form.errors.acmeshServer} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.ca-bundle", + })} + + + + {form.errors.caBundle} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.max-domains", + })} + + + + {form.errors.maxDomains} + + + )} + + + {({ field, form }: any) => ( + + + {intl.formatMessage({ + id: "certificate-authority.has-wildcard-support", + })} + + + {form.errors.isWildcardSupported} + + + )} + + + + + + {intl.formatMessage({ id: "form.save" })} + + + + + )} +
+
+
+ ); +} + +export { HostCreateModal }; diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts index 31af6ca0..dbfecf83 100644 --- a/frontend/src/modals/index.ts +++ b/frontend/src/modals/index.ts @@ -6,6 +6,7 @@ export * from "./CertificateEditModal"; export * from "./ChangePasswordModal"; export * from "./DNSProviderCreateModal"; // export * from "./DNSProviderEditModal.tsx.disabled"; +export * from "./HostCreateModal"; export * from "./ProfileModal"; export * from "./SetPasswordModal"; export * from "./UpstreamCreateModal"; diff --git a/frontend/src/pages/Hosts/HostsTable.tsx b/frontend/src/pages/Hosts/Table.tsx similarity index 89% rename from frontend/src/pages/Hosts/HostsTable.tsx rename to frontend/src/pages/Hosts/Table.tsx index 3c4ed8fd..63a05210 100644 --- a/frontend/src/pages/Hosts/HostsTable.tsx +++ b/frontend/src/pages/Hosts/Table.tsx @@ -18,33 +18,22 @@ import { intl } from "locale"; import { FiEdit } from "react-icons/fi"; import { useSortBy, useFilters, useTable, usePagination } from "react-table"; -const rowActions = [ - { - title: intl.formatMessage({ id: "action.edit" }), - onClick: (e: any, data: any) => { - alert(JSON.stringify(data, null, 2)); - }, - icon: , - show: (data: any) => !data.isSystem, - }, -]; - -export interface HostsTableProps { +export interface TableProps { data: any; pagination: TablePagination; sortBy: TableSortBy[]; filters: TableFilter[]; onTableEvent: any; } -function HostsTable({ +function Table({ data, pagination, onTableEvent, sortBy, filters, -}: HostsTableProps) { +}: TableProps) { const [columns, tableData] = useMemo(() => { - const columns: any[] = [ + const columns: any = [ { accessor: "user.gravatarUrl", Cell: GravatarFormatter(), @@ -82,7 +71,15 @@ function HostsTable({ { id: "actions", accessor: "id", - Cell: ActionsFormatter(rowActions), + Cell: ActionsFormatter([ + { + title: intl.formatMessage({ id: "action.edit" }), + onClick: (e: any, data: any) => { + alert(JSON.stringify(data, null, 2)); + }, + icon: , + }, + ]), className: "w-80", }, ]; @@ -162,4 +159,4 @@ function HostsTable({ return ; } -export { HostsTable }; +export default Table; diff --git a/frontend/src/pages/Hosts/TableWrapper.tsx b/frontend/src/pages/Hosts/TableWrapper.tsx new file mode 100644 index 00000000..c712b624 --- /dev/null +++ b/frontend/src/pages/Hosts/TableWrapper.tsx @@ -0,0 +1,97 @@ +import { useEffect, useReducer, useState } from "react"; + +import { Alert, AlertIcon } from "@chakra-ui/react"; +import { + EmptyList, + PrettyButton, + SpinnerPage, + tableEventReducer, +} from "components"; +import { useHosts } from "hooks"; +import { intl } from "locale"; + +import Table from "./Table"; + +const initialState = { + offset: 0, + limit: 10, + sortBy: [ + { + id: "name", + desc: false, + }, + ], + filters: [], +}; + +interface TableWrapperProps { + onCreateClick?: () => void; +} +function TableWrapper({ onCreateClick }: TableWrapperProps) { + const [{ offset, limit, sortBy, filters }, dispatch] = useReducer( + tableEventReducer, + initialState, + ); + + const [tableData, setTableData] = useState(null); + const { isFetching, isLoading, isError, error, data } = useHosts( + offset, + limit, + sortBy, + filters, + ); + + useEffect(() => { + setTableData(data as any); + }, [data]); + + if (isFetching || isLoading || !tableData) { + return ; + } + + if (isError) { + return ( + + + {error?.message || "Unknown error"} + + ); + } + + if (isFetching || isLoading || !tableData) { + return ; + } + + // When there are no items and no filters active, show the nicer empty view + if (data?.total === 0 && filters?.length === 0) { + return ( + + {intl.formatMessage({ id: "lets-go" })} + + } + /> + ); + } + + const pagination = { + offset: data?.offset || initialState.offset, + limit: data?.limit || initialState.limit, + total: data?.total || 0, + }; + + return ( + + ); +} + +export default TableWrapper; diff --git a/frontend/src/pages/Hosts/index.tsx b/frontend/src/pages/Hosts/index.tsx index 65e4b2c0..6a02e8c2 100644 --- a/frontend/src/pages/Hosts/index.tsx +++ b/frontend/src/pages/Hosts/index.tsx @@ -1,95 +1,30 @@ -import { useEffect, useReducer, useState } from "react"; +import { useState } from "react"; -import { Alert, AlertIcon, Heading, HStack } from "@chakra-ui/react"; -import { - EmptyList, - PrettyButton, - SpinnerPage, - tableEventReducer, -} from "components"; -import { useHosts } from "hooks"; +import { Heading, HStack } from "@chakra-ui/react"; +import { HelpDrawer, PrettyButton } from "components"; import { intl } from "locale"; +import { HostCreateModal } from "modals"; -import { HostsTable } from "./HostsTable"; - -const initialState = { - offset: 0, - limit: 10, - sortBy: [ - { - id: "domain_names", - desc: false, - }, - ], - filters: [], -}; +import TableWrapper from "./TableWrapper"; function Hosts() { - const [{ offset, limit, sortBy, filters }, dispatch] = useReducer( - tableEventReducer, - initialState, - ); - - const [tableData, setTableData] = useState(null); - const { isFetching, isLoading, error, data } = useHosts( - offset, - limit, - sortBy, - filters, - ); - - useEffect(() => { - setTableData(data as any); - }, [data]); - - if (error || (!tableData && !isFetching && !isLoading)) { - return ( - - - {error?.message || "Unknown error"} - - ); - } - - if (isFetching || isLoading || !tableData) { - return ; - } - - // When there are no items and no filters active, show the nicer empty view - if (data?.total === 0 && filters?.length === 0) { - return ( - - {intl.formatMessage({ id: "lets-go" })} - - } - /> - ); - } - - const pagination = { - offset: data?.offset || initialState.offset, - limit: data?.limit || initialState.limit, - total: data?.total || 0, - }; + const [createShown, setCreateShown] = useState(false); return ( <> {intl.formatMessage({ id: "hosts.title" })} - - {intl.formatMessage({ id: "create-host" })} - + + + setCreateShown(true)}> + {intl.formatMessage({ id: "host.create" })} + + - setCreateShown(true)} /> + setCreateShown(false)} /> );