Table improvements, add modals
This commit is contained in:
parent
306ac20457
commit
b877bea86c
@ -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",
|
||||
|
12
frontend/src/api/npm/getUpstreamNginxConfig.ts
Normal file
12
frontend/src/api/npm/getUpstreamNginxConfig.ts
Normal 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;
|
||||
}
|
@ -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";
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -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";
|
||||
|
19
frontend/src/hooks/useUpstreamNginxConfig.ts
Normal file
19
frontend/src/hooks/useUpstreamNginxConfig.ts
Normal 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 };
|
@ -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"
|
||||
},
|
||||
|
222
frontend/src/modals/AccessListCreateModal.tsx
Normal file
222
frontend/src/modals/AccessListCreateModal.tsx
Normal 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 };
|
219
frontend/src/modals/UpstreamCreateModal.tsx
Normal file
219
frontend/src/modals/UpstreamCreateModal.tsx
Normal 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 };
|
241
frontend/src/modals/UpstreamEditModal.tsx
Normal file
241
frontend/src/modals/UpstreamEditModal.tsx
Normal 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 };
|
62
frontend/src/modals/UpstreamNginxConfigModal.tsx
Normal file
62
frontend/src/modals/UpstreamNginxConfigModal.tsx
Normal 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 };
|
@ -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";
|
||||
|
@ -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)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
87
frontend/src/pages/Upstreams/TableWrapper.tsx
Normal file
87
frontend/src/pages/Upstreams/TableWrapper.tsx
Normal 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;
|
@ -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)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -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==
|
||||
|
Loading…
Reference in New Issue
Block a user