mirror of
https://github.com/jc21/nginx-proxy-manager.git
synced 2024-08-30 18:22:48 +00:00
Certificates table compliance and other stuff
This commit is contained in:
parent
9a5cbbba49
commit
e890bfcf10
3
frontend/src/locale/src/HelpDoc/en/AccessLists.md
Normal file
3
frontend/src/locale/src/HelpDoc/en/AccessLists.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Access Lists Help
|
||||||
|
|
||||||
|
todo
|
3
frontend/src/locale/src/HelpDoc/en/Certificates.md
Normal file
3
frontend/src/locale/src/HelpDoc/en/Certificates.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Certificates Help
|
||||||
|
|
||||||
|
todo
|
3
frontend/src/locale/src/HelpDoc/en/NginxTemplates.md
Normal file
3
frontend/src/locale/src/HelpDoc/en/NginxTemplates.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Nginx Templates Help
|
||||||
|
|
||||||
|
todo
|
@ -1,2 +1,5 @@
|
|||||||
|
export * as AccessLists from "./AccessLists.md";
|
||||||
|
export * as Certificates from "./Certificates.md";
|
||||||
export * as CertificateAuthorities from "./CertificateAuthorities.md";
|
export * as CertificateAuthorities from "./CertificateAuthorities.md";
|
||||||
export * as DNSProviders from "./DNSProviders.md";
|
export * as DNSProviders from "./DNSProviders.md";
|
||||||
|
export * as NginxTemplates from "./NginxTemplates.md";
|
||||||
|
@ -311,6 +311,9 @@
|
|||||||
"capability.users.manage": {
|
"capability.users.manage": {
|
||||||
"defaultMessage": "Manage Users"
|
"defaultMessage": "Manage Users"
|
||||||
},
|
},
|
||||||
|
"certificate.create": {
|
||||||
|
"defaultMessage": "Create Certificate"
|
||||||
|
},
|
||||||
"certificate-authorities.title": {
|
"certificate-authorities.title": {
|
||||||
"defaultMessage": "Certificate Authorities"
|
"defaultMessage": "Certificate Authorities"
|
||||||
},
|
},
|
||||||
@ -380,9 +383,6 @@
|
|||||||
"create-access-list-title": {
|
"create-access-list-title": {
|
||||||
"defaultMessage": "There are no Access Lists"
|
"defaultMessage": "There are no Access Lists"
|
||||||
},
|
},
|
||||||
"create-certificate": {
|
|
||||||
"defaultMessage": "Create Certificate"
|
|
||||||
},
|
|
||||||
"create-certificate-title": {
|
"create-certificate-title": {
|
||||||
"defaultMessage": "There are no Certificates"
|
"defaultMessage": "There are no Certificates"
|
||||||
},
|
},
|
||||||
|
221
frontend/src/modals/CertificateCreateModal.tsx
Normal file
221
frontend/src/modals/CertificateCreateModal.tsx
Normal file
@ -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 (
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} closeOnOverlayClick={false}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<Formik
|
||||||
|
initialValues={
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
acmeshServer: "",
|
||||||
|
caBundle: "",
|
||||||
|
maxDomains: 5,
|
||||||
|
isWildcardSupported: false,
|
||||||
|
} as CertificateAuthority
|
||||||
|
}
|
||||||
|
onSubmit={onSubmit}>
|
||||||
|
{({ isSubmitting }) => (
|
||||||
|
<Form>
|
||||||
|
<ModalHeader>
|
||||||
|
{intl.formatMessage({ id: "certificate-authority.create" })}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Field name="name" validate={validateString(1, 100)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={form.errors.name && form.touched.name}>
|
||||||
|
<FormLabel htmlFor="name">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.name",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="name"
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: "certificate-authority.name",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="acmeshServer" validate={validateString(2, 255)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.acmeshServer && form.touched.acmeshServer
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="acmeshServer">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.acmesh-server",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="acmeshServer"
|
||||||
|
placeholder="https://example.com/acme/directory"
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.acmeshServer}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="caBundle" validate={validateString(2, 255)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.caBundle && form.touched.caBundle
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="caBundle">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.ca-bundle",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="caBundle"
|
||||||
|
placeholder="/path/to/certs/custom-ca-bundle.crt"
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.caBundle}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
name="maxDomains"
|
||||||
|
validate={validateNumber(1)}
|
||||||
|
type="number">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.maxDomains && form.touched.maxDomains
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="maxDomains">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.max-domains",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input {...field} id="maxDomains" type="number" />
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.maxDomains}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="isWildcardSupported" type="checkbox">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isInvalid={
|
||||||
|
form.errors.isWildcardSupported &&
|
||||||
|
form.touched.isWildcardSupported
|
||||||
|
}>
|
||||||
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
isChecked={field.checked}
|
||||||
|
size="md"
|
||||||
|
colorScheme="green">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.has-wildcard-support",
|
||||||
|
})}
|
||||||
|
</Checkbox>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.isWildcardSupported}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Stack>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<PrettyButton mr={3} isLoading={isSubmitting}>
|
||||||
|
{intl.formatMessage({ id: "form.save" })}
|
||||||
|
</PrettyButton>
|
||||||
|
<Button onClick={onClose} isLoading={isSubmitting}>
|
||||||
|
{intl.formatMessage({ id: "form.cancel" })}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CertificateCreateModal };
|
240
frontend/src/modals/CertificateEditModal.tsx
Normal file
240
frontend/src/modals/CertificateEditModal.tsx
Normal file
@ -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 (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
closeOnOverlayClick={false}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
{status === "loading" ? (
|
||||||
|
// todo nicer
|
||||||
|
<p>loading</p>
|
||||||
|
) : (
|
||||||
|
<Formik
|
||||||
|
initialValues={
|
||||||
|
{
|
||||||
|
id: data?.id,
|
||||||
|
name: data?.name,
|
||||||
|
acmeshServer: data?.acmeshServer,
|
||||||
|
caBundle: data?.caBundle,
|
||||||
|
maxDomains: data?.maxDomains,
|
||||||
|
isWildcardSupported: data?.isWildcardSupported,
|
||||||
|
} as CertificateAuthority
|
||||||
|
}
|
||||||
|
onSubmit={onSubmit}>
|
||||||
|
{({ isSubmitting }) => (
|
||||||
|
<Form>
|
||||||
|
<ModalHeader>
|
||||||
|
{intl.formatMessage({ id: "certificate-authority.edit" })}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
<Field name="name" validate={validateString(1, 100)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={form.errors.name && form.touched.name}>
|
||||||
|
<FormLabel htmlFor="name">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.name",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="name"
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: "certificate-authority.name",
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.name}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
name="acmeshServer"
|
||||||
|
validate={validateString(2, 255)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.acmeshServer &&
|
||||||
|
form.touched.acmeshServer
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="acmeshServer">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.acmesh-server",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="acmeshServer"
|
||||||
|
placeholder="https://example.com/acme/directory"
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.acmeshServer}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="caBundle" validate={validateString(2, 255)}>
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.caBundle && form.touched.caBundle
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="caBundle">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.ca-bundle",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
id="caBundle"
|
||||||
|
placeholder="/path/to/certs/custom-ca-bundle.crt"
|
||||||
|
/>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.caBundle}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
name="maxDomains"
|
||||||
|
validate={validateNumber(1)}
|
||||||
|
type="number">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isRequired
|
||||||
|
isInvalid={
|
||||||
|
form.errors.maxDomains && form.touched.maxDomains
|
||||||
|
}>
|
||||||
|
<FormLabel htmlFor="maxDomains">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.max-domains",
|
||||||
|
})}
|
||||||
|
</FormLabel>
|
||||||
|
<Input {...field} id="maxDomains" type="number" />
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.maxDomains}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="isWildcardSupported" type="checkbox">
|
||||||
|
{({ field, form }: any) => (
|
||||||
|
<FormControl
|
||||||
|
isInvalid={
|
||||||
|
form.errors.isWildcardSupported &&
|
||||||
|
form.touched.isWildcardSupported
|
||||||
|
}>
|
||||||
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
isChecked={field.checked}
|
||||||
|
size="md"
|
||||||
|
colorScheme="green">
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: "certificate-authority.has-wildcard-support",
|
||||||
|
})}
|
||||||
|
</Checkbox>
|
||||||
|
<FormErrorMessage>
|
||||||
|
{form.errors.isWildcardSupported}
|
||||||
|
</FormErrorMessage>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Stack>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<PrettyButton mr={3} isLoading={isSubmitting}>
|
||||||
|
{intl.formatMessage({ id: "form.save" })}
|
||||||
|
</PrettyButton>
|
||||||
|
<Button onClick={onClose} isLoading={isSubmitting}>
|
||||||
|
{intl.formatMessage({ id: "form.cancel" })}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
)}
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CertificateEditModal };
|
@ -1,6 +1,8 @@
|
|||||||
export * from "./AccessListCreateModal";
|
export * from "./AccessListCreateModal";
|
||||||
export * from "./CertificateAuthorityCreateModal";
|
export * from "./CertificateAuthorityCreateModal";
|
||||||
export * from "./CertificateAuthorityEditModal";
|
export * from "./CertificateAuthorityEditModal";
|
||||||
|
export * from "./CertificateCreateModal";
|
||||||
|
export * from "./CertificateEditModal";
|
||||||
export * from "./ChangePasswordModal";
|
export * from "./ChangePasswordModal";
|
||||||
export * from "./DNSProviderCreateModal";
|
export * from "./DNSProviderCreateModal";
|
||||||
// export * from "./DNSProviderEditModal.tsx.disabled";
|
// export * from "./DNSProviderEditModal.tsx.disabled";
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { useEffect, useReducer, useState } from "react";
|
import { useEffect, useReducer, useState } from "react";
|
||||||
|
|
||||||
import { Alert, AlertIcon } from "@chakra-ui/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 { useAccessLists } from "hooks";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
|
||||||
@ -63,6 +68,11 @@ function TableWrapper({ onCreateClick }: TableWrapperProps) {
|
|||||||
<EmptyList
|
<EmptyList
|
||||||
title={intl.formatMessage({ id: "create-access-list-title" })}
|
title={intl.formatMessage({ id: "create-access-list-title" })}
|
||||||
summary={intl.formatMessage({ id: "create-hint" })}
|
summary={intl.formatMessage({ id: "create-hint" })}
|
||||||
|
createButton={
|
||||||
|
<PrettyButton mt={5} onClick={onCreateClick}>
|
||||||
|
{intl.formatMessage({ id: "lets-go" })}
|
||||||
|
</PrettyButton>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ function AccessLists() {
|
|||||||
{intl.formatMessage({ id: "access-lists.title" })}
|
{intl.formatMessage({ id: "access-lists.title" })}
|
||||||
</Heading>
|
</Heading>
|
||||||
<HStack>
|
<HStack>
|
||||||
<HelpDrawer section="Access-Lists" />
|
<HelpDrawer section="AccessLists" />
|
||||||
<PrettyButton size="sm" onClick={() => setCreateShown(true)}>
|
<PrettyButton size="sm" onClick={() => setCreateShown(true)}>
|
||||||
{intl.formatMessage({ id: "access-list.create" })}
|
{intl.formatMessage({ id: "access-list.create" })}
|
||||||
</PrettyButton>
|
</PrettyButton>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
tableEvents,
|
tableEvents,
|
||||||
@ -15,34 +15,25 @@ import {
|
|||||||
TextFilter,
|
TextFilter,
|
||||||
} from "components";
|
} from "components";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
import { CertificateEditModal } from "modals";
|
||||||
import { FiEdit } from "react-icons/fi";
|
import { FiEdit } from "react-icons/fi";
|
||||||
import { useSortBy, useFilters, useTable, usePagination } from "react-table";
|
import { useSortBy, useFilters, useTable, usePagination } from "react-table";
|
||||||
|
|
||||||
const rowActions = [
|
export interface TableProps {
|
||||||
{
|
|
||||||
title: intl.formatMessage({ id: "action.edit" }),
|
|
||||||
onClick: (e: any, data: any) => {
|
|
||||||
alert(JSON.stringify(data, null, 2));
|
|
||||||
},
|
|
||||||
icon: <FiEdit />,
|
|
||||||
show: (data: any) => !data.isSystem,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export interface CertificatesTableProps {
|
|
||||||
data: any;
|
data: any;
|
||||||
pagination: TablePagination;
|
pagination: TablePagination;
|
||||||
sortBy: TableSortBy[];
|
sortBy: TableSortBy[];
|
||||||
filters: TableFilter[];
|
filters: TableFilter[];
|
||||||
onTableEvent: any;
|
onTableEvent: any;
|
||||||
}
|
}
|
||||||
function CertificatesTable({
|
function Table({
|
||||||
data,
|
data,
|
||||||
pagination,
|
pagination,
|
||||||
onTableEvent,
|
onTableEvent,
|
||||||
sortBy,
|
sortBy,
|
||||||
filters,
|
filters,
|
||||||
}: CertificatesTableProps) {
|
}: TableProps) {
|
||||||
|
const [editId, setEditId] = useState(0);
|
||||||
const [columns, tableData] = useMemo(() => {
|
const [columns, tableData] = useMemo(() => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -79,8 +70,17 @@ function CertificatesTable({
|
|||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
accessor: "id",
|
accessor: "id",
|
||||||
Cell: ActionsFormatter(rowActions),
|
|
||||||
className: "w-80",
|
className: "w-80",
|
||||||
|
Cell: ActionsFormatter([
|
||||||
|
{
|
||||||
|
title: intl.formatMessage({
|
||||||
|
id: "action.edit",
|
||||||
|
}),
|
||||||
|
onClick: (e: any, { id }: any) => setEditId(id),
|
||||||
|
icon: <FiEdit />,
|
||||||
|
disabled: (data: any) => data.isReadonly,
|
||||||
|
},
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return [columns, data];
|
return [columns, data];
|
||||||
@ -156,7 +156,18 @@ function CertificatesTable({
|
|||||||
});
|
});
|
||||||
}, [onTableEvent, tableInstance.state.filters]);
|
}, [onTableEvent, tableInstance.state.filters]);
|
||||||
|
|
||||||
return <TableLayout pagination={pagination} {...tableInstance} />;
|
return (
|
||||||
|
<>
|
||||||
|
<TableLayout pagination={pagination} {...tableInstance} />
|
||||||
|
{editId ? (
|
||||||
|
<CertificateEditModal
|
||||||
|
isOpen
|
||||||
|
editId={editId}
|
||||||
|
onClose={() => setEditId(0)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { CertificatesTable };
|
export default Table;
|
97
frontend/src/pages/Certificates/TableWrapper.tsx
Normal file
97
frontend/src/pages/Certificates/TableWrapper.tsx
Normal file
@ -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 <SpinnerPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<Alert status="error">
|
||||||
|
<AlertIcon />
|
||||||
|
{error?.message || "Unknown error"}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFetching || isLoading || !tableData) {
|
||||||
|
return <SpinnerPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there are no items and no filters active, show the nicer empty view
|
||||||
|
if (data?.total === 0 && filters?.length === 0) {
|
||||||
|
return (
|
||||||
|
<EmptyList
|
||||||
|
title={intl.formatMessage({ id: "create-certificate-title" })}
|
||||||
|
summary={intl.formatMessage({ id: "create-hint" })}
|
||||||
|
createButton={
|
||||||
|
<PrettyButton mt={5} onClick={onCreateClick}>
|
||||||
|
{intl.formatMessage({ id: "lets-go" })}
|
||||||
|
</PrettyButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pagination = {
|
||||||
|
offset: data?.offset || initialState.offset,
|
||||||
|
limit: data?.limit || initialState.limit,
|
||||||
|
total: data?.total || 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
data={data?.items || []}
|
||||||
|
pagination={pagination}
|
||||||
|
sortBy={sortBy}
|
||||||
|
filters={filters}
|
||||||
|
onTableEvent={dispatch}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableWrapper;
|
@ -1,79 +1,14 @@
|
|||||||
import { useEffect, useReducer, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { Alert, AlertIcon, Heading, HStack } from "@chakra-ui/react";
|
import { Heading, HStack } from "@chakra-ui/react";
|
||||||
import {
|
import { HelpDrawer, PrettyButton } from "components";
|
||||||
EmptyList,
|
|
||||||
PrettyButton,
|
|
||||||
SpinnerPage,
|
|
||||||
tableEventReducer,
|
|
||||||
} from "components";
|
|
||||||
import { useCertificates } from "hooks";
|
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
import { CertificateCreateModal } from "modals";
|
||||||
|
|
||||||
import { CertificatesTable } from "./CertificatesTable";
|
import TableWrapper from "./TableWrapper";
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
offset: 0,
|
|
||||||
limit: 10,
|
|
||||||
sortBy: [
|
|
||||||
{
|
|
||||||
id: "name",
|
|
||||||
desc: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
filters: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
function Certificates() {
|
function Certificates() {
|
||||||
const [{ offset, limit, sortBy, filters }, dispatch] = useReducer(
|
const [createShown, setCreateShown] = useState(false);
|
||||||
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 (
|
|
||||||
<Alert status="error">
|
|
||||||
<AlertIcon />
|
|
||||||
{error?.message || "Unknown error"}
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFetching || isLoading || !tableData) {
|
|
||||||
return <SpinnerPage />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data?.total === 0 && filters?.length === 0) {
|
|
||||||
return (
|
|
||||||
<EmptyList
|
|
||||||
title={intl.formatMessage({ id: "create-certificate-title" })}
|
|
||||||
summary={intl.formatMessage({ id: "create-hint" })}
|
|
||||||
createButton={
|
|
||||||
<PrettyButton mt={5}>
|
|
||||||
{intl.formatMessage({ id: "lets-go" })}
|
|
||||||
</PrettyButton>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
offset: data?.offset || initialState.offset,
|
|
||||||
limit: data?.limit || initialState.limit,
|
|
||||||
total: data?.total || 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -81,16 +16,17 @@ function Certificates() {
|
|||||||
<Heading mb={2}>
|
<Heading mb={2}>
|
||||||
{intl.formatMessage({ id: "certificates.title" })}
|
{intl.formatMessage({ id: "certificates.title" })}
|
||||||
</Heading>
|
</Heading>
|
||||||
<PrettyButton size="sm">
|
<HStack>
|
||||||
{intl.formatMessage({ id: "create-certificate" })}
|
<HelpDrawer section="Certificates" />
|
||||||
|
<PrettyButton size="sm" onClick={() => setCreateShown(true)}>
|
||||||
|
{intl.formatMessage({ id: "certificate.create" })}
|
||||||
</PrettyButton>
|
</PrettyButton>
|
||||||
</HStack>
|
</HStack>
|
||||||
<CertificatesTable
|
</HStack>
|
||||||
data={data?.items || []}
|
<TableWrapper onCreateClick={() => setCreateShown(true)} />
|
||||||
pagination={pagination}
|
<CertificateCreateModal
|
||||||
sortBy={sortBy}
|
isOpen={createShown}
|
||||||
filters={filters}
|
onClose={() => setCreateShown(false)}
|
||||||
onTableEvent={dispatch}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { useEffect, useReducer, useState } from "react";
|
import { useEffect, useReducer, useState } from "react";
|
||||||
|
|
||||||
import { Alert, AlertIcon } from "@chakra-ui/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 { useDNSProviders } from "hooks";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
|
||||||
@ -63,6 +68,11 @@ function TableWrapper({ onCreateClick }: TableWrapperProps) {
|
|||||||
<EmptyList
|
<EmptyList
|
||||||
title={intl.formatMessage({ id: "create-dns-provider-title" })}
|
title={intl.formatMessage({ id: "create-dns-provider-title" })}
|
||||||
summary={intl.formatMessage({ id: "create-hint" })}
|
summary={intl.formatMessage({ id: "create-hint" })}
|
||||||
|
createButton={
|
||||||
|
<PrettyButton mt={5} onClick={onCreateClick}>
|
||||||
|
{intl.formatMessage({ id: "lets-go" })}
|
||||||
|
</PrettyButton>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { useEffect, useReducer, useState } from "react";
|
import { useEffect, useReducer, useState } from "react";
|
||||||
|
|
||||||
import { Alert, AlertIcon, Heading, HStack } from "@chakra-ui/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 { useNginxTemplates } from "hooks";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
|
||||||
@ -62,10 +67,13 @@ function NginxTemplates() {
|
|||||||
<Heading mb={2}>
|
<Heading mb={2}>
|
||||||
{intl.formatMessage({ id: "nginx-templates.title" })}
|
{intl.formatMessage({ id: "nginx-templates.title" })}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
<HStack>
|
||||||
|
<HelpDrawer section="NginxTemplates" />
|
||||||
<PrettyButton size="sm">
|
<PrettyButton size="sm">
|
||||||
{intl.formatMessage({ id: "create-nginx-template" })}
|
{intl.formatMessage({ id: "create-nginx-template" })}
|
||||||
</PrettyButton>
|
</PrettyButton>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
</HStack>
|
||||||
<NginxTemplatesTable
|
<NginxTemplatesTable
|
||||||
data={data?.items || []}
|
data={data?.items || []}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { useEffect, useReducer, useState } from "react";
|
import { useEffect, useReducer, useState } from "react";
|
||||||
|
|
||||||
import { Alert, AlertIcon } from "@chakra-ui/react";
|
import { Alert, AlertIcon } from "@chakra-ui/react";
|
||||||
import { EmptyList, SpinnerPage, tableEventReducer } from "components";
|
import {
|
||||||
|
EmptyList,
|
||||||
|
PrettyButton,
|
||||||
|
SpinnerPage,
|
||||||
|
tableEventReducer,
|
||||||
|
} from "components";
|
||||||
import { useUpstreams } from "hooks";
|
import { useUpstreams } from "hooks";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
|
||||||
@ -63,6 +68,11 @@ function TableWrapper({ onCreateClick }: TableWrapperProps) {
|
|||||||
<EmptyList
|
<EmptyList
|
||||||
title={intl.formatMessage({ id: "create-upstream-title" })}
|
title={intl.formatMessage({ id: "create-upstream-title" })}
|
||||||
summary={intl.formatMessage({ id: "create-hint" })}
|
summary={intl.formatMessage({ id: "create-hint" })}
|
||||||
|
createButton={
|
||||||
|
<PrettyButton mt={5} onClick={onCreateClick}>
|
||||||
|
{intl.formatMessage({ id: "lets-go" })}
|
||||||
|
</PrettyButton>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user