Table improvements, add modals

This commit is contained in:
Jamie Curnow 2023-01-16 14:02:36 +10:00
parent 306ac20457
commit b877bea86c
17 changed files with 1149 additions and 121 deletions

View File

@ -15,6 +15,7 @@
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
"@types/react-router-dom": "5.3.3",
"@types/react-syntax-highlighter": "^15.5.6",
"@types/react-table": "^7.7.12",
"@types/styled-components": "5.1.25",
"@typescript-eslint/eslint-plugin": "^5.30.6",
@ -57,6 +58,7 @@
"react-query": "^3.39.1",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-table": "7.8.0",
"rooks": "5.11.8",
"tmp": "^0.2.1",

View File

@ -0,0 +1,12 @@
import * as api from "./base";
export async function getUpstreamNginxConfig(
id: number,
params = {},
): Promise<string> {
const { result } = await api.get({
url: `/upstreams/${id}/nginx-config`,
params,
});
return result;
}

View File

@ -13,6 +13,7 @@ export * from "./getHosts";
export * from "./getNginxTemplates";
export * from "./getSettings";
export * from "./getToken";
export * from "./getUpstreamNginxConfig";
export * from "./getUpstreams";
export * from "./getUser";
export * from "./getUsers";

View File

@ -13,6 +13,8 @@ import { Monospace, RowAction, RowActionsMenu } from "components";
import { intl } from "locale";
import getNiceDNSProvider from "modules/Acmesh";
const errorColor = "red.400";
function ActionsFormatter(rowActions: RowAction[]) {
const formatCell = (instance: any) => {
return <RowActionsMenu data={instance.row.original} actions={rowActions} />;
@ -24,7 +26,7 @@ function ActionsFormatter(rowActions: RowAction[]) {
function BooleanFormatter() {
const formatCell = ({ value }: any) => {
return (
<Badge color={value ? "cyan.500" : "red.400"}>
<Badge color={value ? "cyan.500" : errorColor}>
{value ? "true" : "false"}
</Badge>
);
@ -86,7 +88,7 @@ function CertificateStatusFormatter() {
let color = "cyan.500";
switch (value) {
case "failed":
color = "red.400";
color = errorColor;
break;
case "provided":
color = "green.400";
@ -137,7 +139,7 @@ function DisabledFormatter() {
const formatCell = ({ value, row }: any) => {
if (row?.original?.isDisabled) {
return (
<Text color="red.500">
<Text color={errorColor}>
<Tooltip label={intl.formatMessage({ id: "user.disabled" })}>
{value}
</Tooltip>
@ -173,7 +175,7 @@ function DomainsFormatter() {
</>
);
}
return <Badge color="red.400">No domains!</Badge>;
return <Badge color={errorColor}>No domains!</Badge>;
};
return formatCell;
@ -191,7 +193,9 @@ function HostStatusFormatter() {
const formatCell = ({ row }: any) => {
if (row.original.isDisabled) {
return (
<Badge color="red.400">{intl.formatMessage({ id: "disabled" })}</Badge>
<Badge color={errorColor}>
{intl.formatMessage({ id: "disabled" })}
</Badge>
);
}
@ -209,7 +213,9 @@ function HostStatusFormatter() {
if (row.original.certificate.status === "error") {
return (
<Tooltip label={row.original.certificate.errorMessage}>
<Badge color="red.400">{intl.formatMessage({ id: "error" })}</Badge>
<Badge color={errorColor}>
{intl.formatMessage({ id: "error" })}
</Badge>
</Tooltip>
);
}
@ -255,9 +261,21 @@ function UpstreamStatusFormatter() {
}
if (value === "error") {
return (
<Tooltip label={row.original.errorMessage}>
<Badge color="red.500">{intl.formatMessage({ id: "error" })}</Badge>
</Tooltip>
<Popover>
<PopoverTrigger>
<Badge color={errorColor} style={{ cursor: "pointer" }}>
{intl.formatMessage({ id: "error" })}
</Badge>
</PopoverTrigger>
<PopoverContent>
<PopoverArrow />
<PopoverBody>
<pre className="wrappable error">
{row?.original?.errorMessage}
</pre>
</PopoverBody>
</PopoverContent>
</Popover>
);
}
};

View File

@ -9,6 +9,7 @@ export * from "./useHealth";
export * from "./useHosts";
export * from "./useNginxTemplates";
export * from "./useSettings";
export * from "./useUpstreamNginxConfig";
export * from "./useUpstreams";
export * from "./useUser";
export * from "./useUsers";

View File

@ -0,0 +1,19 @@
import { getUpstreamNginxConfig } from "api/npm";
import { useQuery } from "react-query";
const fetchUpstreamNginxConfig = (id: any) => {
return getUpstreamNginxConfig(id);
};
const useUpstreamNginxConfig = (id: number, options = {}) => {
return useQuery<string, Error>(
["upstream-nginx-config", id],
() => fetchUpstreamNginxConfig(id),
{
staleTime: 30 * 1000, // 30 seconds
...options,
},
);
};
export { useUpstreamNginxConfig };

View File

@ -1,4 +1,7 @@
{
"access-list.create": {
"defaultMessage": "Create Access List"
},
"access-lists.title": {
"defaultMessage": "Access Lists"
},
@ -242,6 +245,9 @@
"action.edit": {
"defaultMessage": "Edit"
},
"action.nginx-config": {
"defaultMessage": "View Nginx Config"
},
"action.set-password": {
"defaultMessage": "Set Password"
},
@ -372,7 +378,7 @@
"defaultMessage": "Wildcard Support"
},
"create-access-list-title": {
"defaultMessage": "Create Access List"
"defaultMessage": "There are no Access Lists"
},
"create-certificate": {
"defaultMessage": "Create Certificate"
@ -398,9 +404,6 @@
"create-host-title": {
"defaultMessage": "There are no Proxy Hosts"
},
"create-upstream": {
"defaultMessage": "Create Upstream"
},
"create-upstream-title": {
"defaultMessage": "There are no Upstreams"
},
@ -521,6 +524,9 @@
"general-settings.title": {
"defaultMessage": "General Settings"
},
"nginx-config": {
"defaultMessage": "Nginx Config"
},
"nginx-templates.title": {
"defaultMessage": "Nginx Templates"
},
@ -653,6 +659,9 @@
"unhealthy.title": {
"defaultMessage": "Nginx Proxy Manager is unhealthy"
},
"upstream.create": {
"defaultMessage": "Create Upstream"
},
"upstreams.title": {
"defaultMessage": "Upstreams"
},

View File

@ -0,0 +1,222 @@
// TODO
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 AccessListCreateModalProps {
isOpen: boolean;
onClose: () => void;
}
function AccessListCreateModal({
isOpen,
onClose,
}: AccessListCreateModalProps) {
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 { AccessListCreateModal };

View File

@ -0,0 +1,219 @@
// TODO
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 UpstreamCreateModalProps {
isOpen: boolean;
onClose: () => void;
}
function UpstreamCreateModal({ isOpen, onClose }: UpstreamCreateModalProps) {
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 { UpstreamCreateModal };

View File

@ -0,0 +1,241 @@
// TODO
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 UpstreamEditModalProps {
editId: number;
isOpen: boolean;
onClose: () => void;
}
function UpstreamEditModal({
editId,
isOpen,
onClose,
}: UpstreamEditModalProps) {
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 { UpstreamEditModal };

View File

@ -0,0 +1,62 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
} from "@chakra-ui/react";
import { useUpstreamNginxConfig } from "hooks";
import { intl } from "locale";
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import sh from "react-syntax-highlighter/dist/esm/languages/hljs/bash";
import nord from "react-syntax-highlighter/dist/esm/styles/hljs/nord";
interface UpstreamNginxConfigModalProps {
upstreamId: number;
isOpen: boolean;
onClose: () => void;
}
function UpstreamNginxConfigModal({
isOpen,
onClose,
upstreamId,
}: UpstreamNginxConfigModalProps) {
const { isLoading, data } = useUpstreamNginxConfig(upstreamId);
SyntaxHighlighter.registerLanguage("bash", sh);
return (
<Modal isOpen={isOpen} onClose={onClose} closeOnOverlayClick={false}>
<ModalOverlay />
<ModalContent maxW="34rem">
{isLoading ? (
"loading"
) : (
<>
<ModalHeader>
{intl.formatMessage({ id: "nginx-config" })}
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<SyntaxHighlighter
language="bash"
style={nord}
customStyle={{
maxWidth: "31rem",
fontSize: "80%",
borderWidth: "1px",
borderColor: "#3C4960",
borderRadius: "5px",
marginBottom: "16px",
}}>
{data || ""}
</SyntaxHighlighter>
</ModalBody>
</>
)}
</ModalContent>
</Modal>
);
}
export { UpstreamNginxConfigModal };

View File

@ -1,3 +1,4 @@
export * from "./AccessListCreateModal";
export * from "./CertificateAuthorityCreateModal";
export * from "./CertificateAuthorityEditModal";
export * from "./ChangePasswordModal";
@ -5,5 +6,8 @@ export * from "./DNSProviderCreateModal";
// export * from "./DNSProviderEditModal.tsx.disabled";
export * from "./ProfileModal";
export * from "./SetPasswordModal";
export * from "./UpstreamCreateModal";
export * from "./UpstreamEditModal";
export * from "./UpstreamNginxConfigModal";
export * from "./UserCreateModal";
export * from "./UserEditModal";

View File

@ -1,15 +1,33 @@
import { Heading } from "@chakra-ui/react";
import { useState } from "react";
import { Heading, HStack } from "@chakra-ui/react";
import { HelpDrawer, PrettyButton } from "components";
import { intl } from "locale";
import { AccessListCreateModal } from "modals";
import TableWrapper from "./TableWrapper";
function AccessLists() {
const [createShown, setCreateShown] = useState(false);
return (
<>
<Heading mb={2}>
{intl.formatMessage({ id: "access-lists.title" })}
</Heading>
<TableWrapper />
<HStack mx={6} my={4} justifyContent="space-between">
<Heading mb={2}>
{intl.formatMessage({ id: "access-lists.title" })}
</Heading>
<HStack>
<HelpDrawer section="Access-Lists" />
<PrettyButton size="sm" onClick={() => setCreateShown(true)}>
{intl.formatMessage({ id: "access-list.create" })}
</PrettyButton>
</HStack>
</HStack>
<TableWrapper onCreateClick={() => setCreateShown(true)} />
<AccessListCreateModal
isOpen={createShown}
onClose={() => setCreateShown(false)}
/>
</>
);
}

View File

@ -1,48 +1,40 @@
import { useEffect, useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import {
tableEvents,
ActionsFormatter,
GravatarFormatter,
UpstreamStatusFormatter,
IDFormatter,
TableFilter,
TableLayout,
TablePagination,
TableSortBy,
TextFilter,
UpstreamStatusFormatter,
} from "components";
import { intl } from "locale";
import { FiEdit } from "react-icons/fi";
import { UpstreamEditModal, UpstreamNginxConfigModal } from "modals";
import { FiEdit, FiHardDrive } 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: <FiEdit />,
show: (data: any) => !data.isSystem,
},
];
export interface UpstreamsTableProps {
export interface TableProps {
data: any;
pagination: TablePagination;
sortBy: TableSortBy[];
filters: TableFilter[];
onTableEvent: any;
}
function UpstreamsTable({
function Table({
data,
pagination,
onTableEvent,
sortBy,
filters,
}: UpstreamsTableProps) {
}: TableProps) {
const [editId, setEditId] = useState(0);
const [configId, setConfigId] = useState(0);
const [columns, tableData] = useMemo(() => {
const columns: any[] = [
const columns: any = [
{
accessor: "user.gravatarUrl",
Cell: GravatarFormatter(),
@ -73,8 +65,19 @@ function UpstreamsTable({
{
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: <FiEdit />,
},
{
title: intl.formatMessage({ id: "action.nginx-config" }),
onClick: (e: any, { id }: any) => setConfigId(id),
icon: <FiHardDrive />,
},
]),
},
];
return [columns, data];
@ -150,7 +153,25 @@ function UpstreamsTable({
});
}, [onTableEvent, tableInstance.state.filters]);
return <TableLayout pagination={pagination} {...tableInstance} />;
return (
<>
<TableLayout pagination={pagination} {...tableInstance} />
{editId ? (
<UpstreamEditModal
isOpen
editId={editId}
onClose={() => setEditId(0)}
/>
) : null}
{configId ? (
<UpstreamNginxConfigModal
isOpen
upstreamId={configId}
onClose={() => setConfigId(0)}
/>
) : null}
</>
);
}
export { UpstreamsTable };
export default Table;

View File

@ -0,0 +1,87 @@
import { useEffect, useReducer, useState } from "react";
import { Alert, AlertIcon } from "@chakra-ui/react";
import { EmptyList, SpinnerPage, tableEventReducer } from "components";
import { useUpstreams } 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 } = useUpstreams(
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-upstream-title" })}
summary={intl.formatMessage({ id: "create-hint" })}
/>
);
}
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;

View File

@ -1,80 +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 { useUpstreams } from "hooks";
import { Heading, HStack } from "@chakra-ui/react";
import { HelpDrawer, PrettyButton } from "components";
import { intl } from "locale";
import { UpstreamCreateModal } from "modals";
import { UpstreamsTable } from "./UpstreamsTable";
const initialState = {
offset: 0,
limit: 10,
sortBy: [
{
id: "name",
desc: false,
},
],
filters: [],
};
import TableWrapper from "./TableWrapper";
function Upstreams() {
const [{ offset, limit, sortBy, filters }, dispatch] = useReducer(
tableEventReducer,
initialState,
);
const [tableData, setTableData] = useState(null);
const { isFetching, isLoading, error, data } = useUpstreams(
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 />;
}
// 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-upstream-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,
};
const [createShown, setCreateShown] = useState(false);
return (
<>
@ -82,16 +16,17 @@ function Upstreams() {
<Heading mb={2}>
{intl.formatMessage({ id: "upstreams.title" })}
</Heading>
<PrettyButton size="sm">
{intl.formatMessage({ id: "create-upstream" })}
</PrettyButton>
<HStack>
<HelpDrawer section="Upstreams" />
<PrettyButton size="sm" onClick={() => setCreateShown(true)}>
{intl.formatMessage({ id: "upstream.create" })}
</PrettyButton>
</HStack>
</HStack>
<UpstreamsTable
data={data?.items || []}
pagination={pagination}
sortBy={sortBy}
filters={filters}
onTableEvent={dispatch}
<TableWrapper onCreateClick={() => setCreateShown(true)} />
<UpstreamCreateModal
isOpen={createShown}
onClose={() => setCreateShown(false)}
/>
</>
);

View File

@ -1034,6 +1034,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.3.1":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd"
integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==
dependencies:
regenerator-runtime "^0.13.11"
"@babel/template@^7.18.6", "@babel/template@^7.3.3":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31"
@ -3108,6 +3115,13 @@
"@types/history" "^4.7.11"
"@types/react" "*"
"@types/react-syntax-highlighter@^15.5.6":
version "15.5.6"
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.6.tgz#77c95e6b74d2be23208fcdcf187b93b47025f1b1"
integrity sha512-i7wFuLbIAFlabTeD2I1cLjEOrG/xdMa/rpx2zwzAoGHuXJDhSqp9BSfDlMHSh9JSuNfxHk9eEmMX6D55GiyjGg==
dependencies:
"@types/react" "*"
"@types/react-table@^7.7.12":
version "7.7.12"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.12.tgz#628011d3cb695b07c678704a61f2f1d5b8e567fd"
@ -4289,11 +4303,26 @@ char-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e"
integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
character-entities@^1.0.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
character-entities@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
character-reference-invalid@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
check-types@^11.1.1:
version "11.1.2"
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
@ -4430,6 +4459,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
comma-separated-tokens@^1.0.0:
version "1.0.8"
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
comma-separated-tokens@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98"
@ -5792,6 +5826,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
fault@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
dependencies:
format "^0.2.0"
faye-websocket@^0.11.3:
version "0.11.4"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
@ -5969,6 +6010,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
formik@^2.2.9:
version "2.2.9"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0"
@ -6363,11 +6409,27 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hast-util-parse-selector@^2.0.0:
version "2.2.5"
resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a"
integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
hast-util-whitespace@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c"
integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==
hastscript@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640"
integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==
dependencies:
"@types/hast" "^2.0.0"
comma-separated-tokens "^1.0.0"
hast-util-parse-selector "^2.0.0"
property-information "^5.0.0"
space-separated-tokens "^1.0.0"
he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@ -6378,6 +6440,11 @@ hey-listen@^1.0.8:
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
highlight.js@^10.4.1, highlight.js@~10.7.0:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
history@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"
@ -6710,6 +6777,19 @@ ipaddr.js@^2.0.1:
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
is-alphabetical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
is-alphanumerical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
dependencies:
is-alphabetical "^1.0.0"
is-decimal "^1.0.0"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -6761,6 +6841,11 @@ is-date-object@^1.0.1:
dependencies:
has-tostringtag "^1.0.0"
is-decimal@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
@ -6788,6 +6873,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-hexadecimal@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
is-lambda@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
@ -7853,6 +7943,14 @@ lower-case@^2.0.2:
dependencies:
tslib "^2.0.3"
lowlight@^1.17.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
dependencies:
fault "^1.0.0"
highlight.js "~10.7.0"
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
@ -8858,6 +8956,18 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-entities@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
dependencies:
character-entities "^1.0.0"
character-entities-legacy "^1.0.0"
character-reference-invalid "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
@ -9569,6 +9679,16 @@ pretty-format@^28.0.0, pretty-format@^28.1.3:
ansi-styles "^5.0.0"
react-is "^18.0.0"
prismjs@^1.27.0:
version "1.29.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==
prismjs@~1.27.0:
version "1.27.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@ -9616,6 +9736,13 @@ prop-types@^15.0.0, prop-types@^15.6.2, prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
property-information@^5.0.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69"
integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==
dependencies:
xtend "^4.0.0"
property-information@^6.0.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22"
@ -9968,6 +10095,17 @@ react-style-singleton@^2.2.1:
invariant "^2.2.4"
tslib "^2.0.0"
react-syntax-highlighter@^15.5.0:
version "15.5.0"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20"
integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==
dependencies:
"@babel/runtime" "^7.3.1"
highlight.js "^10.4.1"
lowlight "^1.17.0"
prismjs "^1.27.0"
refractor "^3.6.0"
react-table@7.8.0:
version "7.8.0"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.8.0.tgz#07858c01c1718c09f7f1aed7034fcfd7bda907d2"
@ -10050,6 +10188,15 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
refractor@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a"
integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==
dependencies:
hastscript "^6.0.0"
parse-entities "^2.0.0"
prismjs "~1.27.0"
regenerate-unicode-properties@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
@ -10062,6 +10209,11 @@ regenerate@^1.4.2:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
regenerator-runtime@^0.13.11:
version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.9:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
@ -10656,6 +10808,11 @@ sourcemap-codec@^1.4.8:
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
space-separated-tokens@^1.0.0:
version "1.1.5"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899"
integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
space-separated-tokens@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz#43193cec4fb858a2ce934b7f98b7f2c18107098b"
@ -12106,7 +12263,7 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xtend@^4.0.2:
xtend@^4.0.0, xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==