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 }) => (
+
+ )}
+
+
+
+ );
+}
+
+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 }) => (
+
+ )}
+
+ )}
+
+
+ );
+}
+
+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" })}
+
+ }
/>
);
}