diff --git a/frontend/src/locale/src/HelpDoc/en/AccessLists.md b/frontend/src/locale/src/HelpDoc/en/AccessLists.md new file mode 100644 index 00000000..d215e7ee --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/AccessLists.md @@ -0,0 +1,3 @@ +# Access Lists Help + +todo diff --git a/frontend/src/locale/src/HelpDoc/en/Certificates.md b/frontend/src/locale/src/HelpDoc/en/Certificates.md new file mode 100644 index 00000000..64889369 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/Certificates.md @@ -0,0 +1,3 @@ +# Certificates Help + +todo diff --git a/frontend/src/locale/src/HelpDoc/en/NginxTemplates.md b/frontend/src/locale/src/HelpDoc/en/NginxTemplates.md new file mode 100644 index 00000000..903837b6 --- /dev/null +++ b/frontend/src/locale/src/HelpDoc/en/NginxTemplates.md @@ -0,0 +1,3 @@ +# Nginx Templates Help + +todo diff --git a/frontend/src/locale/src/HelpDoc/en/index.ts b/frontend/src/locale/src/HelpDoc/en/index.ts index a01d1cae..d79fb272 100644 --- a/frontend/src/locale/src/HelpDoc/en/index.ts +++ b/frontend/src/locale/src/HelpDoc/en/index.ts @@ -1,2 +1,5 @@ +export * as AccessLists from "./AccessLists.md"; +export * as Certificates from "./Certificates.md"; export * as CertificateAuthorities from "./CertificateAuthorities.md"; export * as DNSProviders from "./DNSProviders.md"; +export * as NginxTemplates from "./NginxTemplates.md"; diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json index e937eb58..20d23192 100644 --- a/frontend/src/locale/src/en.json +++ b/frontend/src/locale/src/en.json @@ -311,6 +311,9 @@ "capability.users.manage": { "defaultMessage": "Manage Users" }, + "certificate.create": { + "defaultMessage": "Create Certificate" + }, "certificate-authorities.title": { "defaultMessage": "Certificate Authorities" }, @@ -380,9 +383,6 @@ "create-access-list-title": { "defaultMessage": "There are no Access Lists" }, - "create-certificate": { - "defaultMessage": "Create Certificate" - }, "create-certificate-title": { "defaultMessage": "There are no Certificates" }, diff --git a/frontend/src/modals/CertificateCreateModal.tsx b/frontend/src/modals/CertificateCreateModal.tsx new file mode 100644 index 00000000..51f68078 --- /dev/null +++ b/frontend/src/modals/CertificateCreateModal.tsx @@ -0,0 +1,221 @@ +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 CertificateCreateModalProps { + isOpen: boolean; + onClose: () => void; +} +function CertificateCreateModal({ + isOpen, + onClose, +}: CertificateCreateModalProps) { + 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 { CertificateCreateModal }; diff --git a/frontend/src/modals/CertificateEditModal.tsx b/frontend/src/modals/CertificateEditModal.tsx new file mode 100644 index 00000000..93447a9b --- /dev/null +++ b/frontend/src/modals/CertificateEditModal.tsx @@ -0,0 +1,240 @@ +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 { useCertificateAuthority, useSetCertificateAuthority } from "hooks"; +import { intl } from "locale"; +import { validateNumber, validateString } from "modules/Validations"; + +interface CertificateEditModalProps { + editId: number; + isOpen: boolean; + onClose: () => void; +} +function CertificateEditModal({ + editId, + isOpen, + onClose, +}: CertificateEditModalProps) { + const toast = useToast(); + const { status, data } = useCertificateAuthority(editId); + 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 ( + { + onClose(); + }} + closeOnOverlayClick={false}> + + + {status === "loading" ? ( + // todo nicer +

loading

+ ) : ( + + {({ isSubmitting }) => ( +
+ + {intl.formatMessage({ id: "certificate-authority.edit" })} + + + + + + {({ 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 { CertificateEditModal }; diff --git a/frontend/src/modals/index.ts b/frontend/src/modals/index.ts index b4a3bc2d..31af6ca0 100644 --- a/frontend/src/modals/index.ts +++ b/frontend/src/modals/index.ts @@ -1,6 +1,8 @@ export * from "./AccessListCreateModal"; export * from "./CertificateAuthorityCreateModal"; export * from "./CertificateAuthorityEditModal"; +export * from "./CertificateCreateModal"; +export * from "./CertificateEditModal"; export * from "./ChangePasswordModal"; export * from "./DNSProviderCreateModal"; // export * from "./DNSProviderEditModal.tsx.disabled"; diff --git a/frontend/src/pages/AccessLists/TableWrapper.tsx b/frontend/src/pages/AccessLists/TableWrapper.tsx index 5597576a..e762435f 100644 --- a/frontend/src/pages/AccessLists/TableWrapper.tsx +++ b/frontend/src/pages/AccessLists/TableWrapper.tsx @@ -1,7 +1,12 @@ import { useEffect, useReducer, useState } from "react"; import { Alert, AlertIcon } from "@chakra-ui/react"; -import { EmptyList, SpinnerPage, tableEventReducer } from "components"; +import { + EmptyList, + PrettyButton, + SpinnerPage, + tableEventReducer, +} from "components"; import { useAccessLists } from "hooks"; import { intl } from "locale"; @@ -63,6 +68,11 @@ function TableWrapper({ onCreateClick }: TableWrapperProps) { + {intl.formatMessage({ id: "lets-go" })} + + } /> ); } diff --git a/frontend/src/pages/AccessLists/index.tsx b/frontend/src/pages/AccessLists/index.tsx index 7c957e47..7ca9871e 100644 --- a/frontend/src/pages/AccessLists/index.tsx +++ b/frontend/src/pages/AccessLists/index.tsx @@ -17,7 +17,7 @@ function AccessLists() { {intl.formatMessage({ id: "access-lists.title" })} - + setCreateShown(true)}> {intl.formatMessage({ id: "access-list.create" })} diff --git a/frontend/src/pages/Certificates/CertificatesTable.tsx b/frontend/src/pages/Certificates/Table.tsx similarity index 81% rename from frontend/src/pages/Certificates/CertificatesTable.tsx rename to frontend/src/pages/Certificates/Table.tsx index 7a30e0e4..966654e2 100644 --- a/frontend/src/pages/Certificates/CertificatesTable.tsx +++ b/frontend/src/pages/Certificates/Table.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { tableEvents, @@ -15,34 +15,25 @@ import { TextFilter, } from "components"; import { intl } from "locale"; +import { CertificateEditModal } from "modals"; 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 CertificatesTableProps { +export interface TableProps { data: any; pagination: TablePagination; sortBy: TableSortBy[]; filters: TableFilter[]; onTableEvent: any; } -function CertificatesTable({ +function Table({ data, pagination, onTableEvent, sortBy, filters, -}: CertificatesTableProps) { +}: TableProps) { + const [editId, setEditId] = useState(0); const [columns, tableData] = useMemo(() => { const columns = [ { @@ -79,8 +70,17 @@ function CertificatesTable({ { id: "actions", accessor: "id", - Cell: ActionsFormatter(rowActions), className: "w-80", + Cell: ActionsFormatter([ + { + title: intl.formatMessage({ + id: "action.edit", + }), + onClick: (e: any, { id }: any) => setEditId(id), + icon: , + disabled: (data: any) => data.isReadonly, + }, + ]), }, ]; return [columns, data]; @@ -156,7 +156,18 @@ function CertificatesTable({ }); }, [onTableEvent, tableInstance.state.filters]); - return ; + return ( + <> + + {editId ? ( + setEditId(0)} + /> + ) : null} + + ); } -export { CertificatesTable }; +export default Table; diff --git a/frontend/src/pages/Certificates/TableWrapper.tsx b/frontend/src/pages/Certificates/TableWrapper.tsx new file mode 100644 index 00000000..b1f51c55 --- /dev/null +++ b/frontend/src/pages/Certificates/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 { useCertificates } 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 } = useCertificates( + 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/Certificates/index.tsx b/frontend/src/pages/Certificates/index.tsx index 8471f345..e76f006f 100644 --- a/frontend/src/pages/Certificates/index.tsx +++ b/frontend/src/pages/Certificates/index.tsx @@ -1,79 +1,14 @@ -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 { useCertificates } from "hooks"; +import { Heading, HStack } from "@chakra-ui/react"; +import { HelpDrawer, PrettyButton } from "components"; import { intl } from "locale"; +import { CertificateCreateModal } from "modals"; -import { CertificatesTable } from "./CertificatesTable"; - -const initialState = { - offset: 0, - limit: 10, - sortBy: [ - { - id: "name", - desc: false, - }, - ], - filters: [], -}; +import TableWrapper from "./TableWrapper"; function Certificates() { - const [{ offset, limit, sortBy, filters }, dispatch] = useReducer( - tableEventReducer, - initialState, - ); - - const [tableData, setTableData] = useState(null); - const { isFetching, isLoading, error, data } = useCertificates( - 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 ; - } - - 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 ( <> @@ -81,16 +16,17 @@ function Certificates() { {intl.formatMessage({ id: "certificates.title" })} - - {intl.formatMessage({ id: "create-certificate" })} - + + + setCreateShown(true)}> + {intl.formatMessage({ id: "certificate.create" })} + + - setCreateShown(true)} /> + setCreateShown(false)} /> ); diff --git a/frontend/src/pages/DNSProviders/TableWrapper.tsx b/frontend/src/pages/DNSProviders/TableWrapper.tsx index f1f2d04c..0af0c70a 100644 --- a/frontend/src/pages/DNSProviders/TableWrapper.tsx +++ b/frontend/src/pages/DNSProviders/TableWrapper.tsx @@ -1,7 +1,12 @@ import { useEffect, useReducer, useState } from "react"; import { Alert, AlertIcon } from "@chakra-ui/react"; -import { EmptyList, SpinnerPage, tableEventReducer } from "components"; +import { + EmptyList, + PrettyButton, + SpinnerPage, + tableEventReducer, +} from "components"; import { useDNSProviders } from "hooks"; import { intl } from "locale"; @@ -63,6 +68,11 @@ function TableWrapper({ onCreateClick }: TableWrapperProps) { + {intl.formatMessage({ id: "lets-go" })} + + } /> ); } diff --git a/frontend/src/pages/NginxTemplates/index.tsx b/frontend/src/pages/NginxTemplates/index.tsx index d5d2d8b3..6c0a9e18 100644 --- a/frontend/src/pages/NginxTemplates/index.tsx +++ b/frontend/src/pages/NginxTemplates/index.tsx @@ -1,7 +1,12 @@ import { useEffect, useReducer, useState } from "react"; import { Alert, AlertIcon, Heading, HStack } from "@chakra-ui/react"; -import { PrettyButton, SpinnerPage, tableEventReducer } from "components"; +import { + HelpDrawer, + PrettyButton, + SpinnerPage, + tableEventReducer, +} from "components"; import { useNginxTemplates } from "hooks"; import { intl } from "locale"; @@ -62,9 +67,12 @@ function NginxTemplates() { {intl.formatMessage({ id: "nginx-templates.title" })} - - {intl.formatMessage({ id: "create-nginx-template" })} - + + + + {intl.formatMessage({ id: "create-nginx-template" })} + + + {intl.formatMessage({ id: "lets-go" })} + + } /> ); }