update: webhooks

This commit is contained in:
anik-ghosh-au7 2022-07-14 23:41:44 +05:30
parent f2886e6da8
commit 8e655daa71
7 changed files with 460 additions and 28 deletions

View File

@ -2529,8 +2529,7 @@
"@chakra-ui/css-reset": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz",
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==",
"requires": {}
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg=="
},
"@chakra-ui/descendant": {
"version": "2.1.1",
@ -3134,8 +3133,7 @@
"@graphql-typed-document-node/core": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz",
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==",
"requires": {}
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg=="
},
"@popperjs/core": {
"version": "2.11.0",
@ -3845,8 +3843,7 @@
"react-icons": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
"requires": {}
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ=="
},
"react-is": {
"version": "16.13.1",
@ -4032,8 +4029,7 @@
"use-callback-ref": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==",
"requires": {}
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
},
"use-sidecar": {
"version": "1.0.5",

View File

@ -0,0 +1,401 @@
import React, { useState } from 'react';
import {
Button,
Center,
Flex,
Input,
InputGroup,
InputRightElement,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Switch,
Text,
useDisclosure,
useToast,
} from '@chakra-ui/react';
import { FaMinusCircle, FaPlus } from 'react-icons/fa';
import { useClient } from 'urql';
import { ArrayInputOperations } from '../constants';
import { validateEventName, validateURI } from '../utils';
enum INPUT_FIELDS {
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
HEADERS = 'headers',
}
enum HEADER_FIELDS {
KEY = 'key',
VALUE = 'value',
}
interface headersDataType {
[HEADER_FIELDS.KEY]: string;
[HEADER_FIELDS.VALUE]: string;
}
interface headersValidatorDataType {
[HEADER_FIELDS.KEY]: boolean;
[HEADER_FIELDS.VALUE]: boolean;
}
const initHeadersData: headersDataType = {
[HEADER_FIELDS.KEY]: '',
[HEADER_FIELDS.VALUE]: '',
};
const initHeadersValidatorData: headersValidatorDataType = {
[HEADER_FIELDS.KEY]: true,
[HEADER_FIELDS.VALUE]: true,
};
interface webhookDataType {
[INPUT_FIELDS.EVENT_NAME]: string;
[INPUT_FIELDS.ENDPOINT]: string;
[INPUT_FIELDS.ENABLED]: boolean;
[INPUT_FIELDS.HEADERS]: headersDataType[];
}
interface validatorDataType {
[INPUT_FIELDS.EVENT_NAME]: boolean;
[INPUT_FIELDS.ENDPOINT]: boolean;
[INPUT_FIELDS.HEADERS]: headersValidatorDataType[];
}
const initWebhookData: webhookDataType = {
[INPUT_FIELDS.EVENT_NAME]: '',
[INPUT_FIELDS.ENDPOINT]: '',
[INPUT_FIELDS.ENABLED]: false,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersData }],
};
const initWebhookValidatorData: validatorDataType = {
[INPUT_FIELDS.EVENT_NAME]: true,
[INPUT_FIELDS.ENDPOINT]: true,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersValidatorData }],
};
const AddWebhookModal = () => {
const client = useClient();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const [loading, setLoading] = useState<boolean>(false);
const [webhook, setWebhook] = useState<webhookDataType>({
...initWebhookData,
});
const [validator, setValidator] = useState<validatorDataType>({
...initWebhookValidatorData,
});
const inputChangehandler = (
inputType: string,
value: any,
headerInputType: string = HEADER_FIELDS.KEY,
headerIndex: number = 0
) => {
switch (inputType) {
case INPUT_FIELDS.EVENT_NAME:
setWebhook({ ...webhook, [inputType]: value });
setValidator({
...validator,
[INPUT_FIELDS.EVENT_NAME]: validateEventName(value),
});
break;
case INPUT_FIELDS.ENDPOINT:
setWebhook({ ...webhook, [inputType]: value });
setValidator({
...validator,
[INPUT_FIELDS.ENDPOINT]: validateURI(value),
});
break;
case INPUT_FIELDS.ENABLED:
setWebhook({ ...webhook, [inputType]: value });
break;
case INPUT_FIELDS.HEADERS:
const updatedHeaders: any = [...webhook[INPUT_FIELDS.HEADERS]];
const updatedHeadersValidatorData: any = [
...validator[INPUT_FIELDS.HEADERS],
];
const otherHeaderInputType =
headerInputType === HEADER_FIELDS.KEY
? HEADER_FIELDS.VALUE
: HEADER_FIELDS.KEY;
updatedHeaders[headerIndex][headerInputType] = value;
updatedHeadersValidatorData[headerIndex][headerInputType] =
value.length > 0
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
updatedHeadersValidatorData[headerIndex][otherHeaderInputType] =
value.length > 0
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
setWebhook({ ...webhook, [inputType]: updatedHeaders });
setValidator({
...validator,
[inputType]: updatedHeadersValidatorData,
});
break;
default:
break;
}
};
const updateHeadersHandler = (operation: string, index: number = 0) => {
switch (operation) {
case ArrayInputOperations.APPEND:
setWebhook({
...webhook,
[INPUT_FIELDS.HEADERS]: [
...(webhook?.[INPUT_FIELDS.HEADERS] || []),
{ ...initHeadersData },
],
});
setValidator({
...validator,
[INPUT_FIELDS.HEADERS]: [
...(validator?.[INPUT_FIELDS.HEADERS] || []),
{ ...initHeadersValidatorData },
],
});
break;
case ArrayInputOperations.REMOVE:
if (webhook?.[INPUT_FIELDS.HEADERS]?.length) {
const updatedHeaders = [...webhook[INPUT_FIELDS.HEADERS]];
updatedHeaders.splice(index, 1);
setWebhook({
...webhook,
[INPUT_FIELDS.HEADERS]: updatedHeaders,
});
}
if (validator?.[INPUT_FIELDS.HEADERS]?.length) {
const updatedHeadersData = [...validator[INPUT_FIELDS.HEADERS]];
updatedHeadersData.splice(index, 1);
setValidator({
...validator,
[INPUT_FIELDS.HEADERS]: updatedHeadersData,
});
}
break;
default:
break;
}
};
return (
<>
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
variant="solid"
onClick={onOpen}
isDisabled={false}
size="sm"
>
<Center h="100%">Add Webhook</Center>{' '}
</Button>
<Modal isOpen={isOpen} onClose={onClose} size="3xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>Add New Webhook</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex
flexDirection="column"
border="1px"
borderRadius="md"
borderColor="gray.200"
p="5"
>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex flex="1">Event Name</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="user.login"
value={webhook[INPUT_FIELDS.EVENT_NAME]}
isInvalid={!validator[INPUT_FIELDS.EVENT_NAME]}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.EVENT_NAME,
e.currentTarget.value
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="start"
alignItems="center"
marginBottom="5%"
>
<Flex flex="1">Endpoint</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="https://domain.com/webhook"
value={webhook[INPUT_FIELDS.ENDPOINT]}
isInvalid={!validator[INPUT_FIELDS.ENDPOINT]}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.ENDPOINT,
e.currentTarget.value
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="5%"
>
<Flex flex="1">Enabled</Flex>
<Flex w="25%" justifyContent="space-between">
<Text h="75%" fontWeight="bold" marginRight="2">
Off
</Text>
<Switch
size="md"
isChecked={webhook[INPUT_FIELDS.ENABLED]}
onChange={() =>
inputChangehandler(
INPUT_FIELDS.ENABLED,
!webhook[INPUT_FIELDS.ENABLED]
)
}
/>
<Text h="75%" fontWeight="bold" marginLeft="2">
On
</Text>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex>Headers</Flex>
<Flex>
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
h="1.75rem"
size="sm"
variant="ghost"
paddingRight="0"
onClick={() =>
updateHeadersHandler(ArrayInputOperations.APPEND)
}
>
Add more Headers
</Button>
</Flex>
</Flex>
<Flex flexDirection="column" maxH={220} overflowY="scroll">
{webhook[INPUT_FIELDS.HEADERS]?.map((headerData, index) => (
<Flex
key={`header-data-${index}`}
justifyContent="center"
alignItems="center"
>
<InputGroup size="md" marginBottom="2.5%">
<Input
type="text"
placeholder="key"
value={headerData[HEADER_FIELDS.KEY]}
isInvalid={
!validator[INPUT_FIELDS.HEADERS][index][
HEADER_FIELDS.KEY
]
}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.HEADERS,
e.target.value,
HEADER_FIELDS.KEY,
index
)
}
width="30%"
marginRight="2%"
/>
<Center marginRight="2%">
<Text fontWeight="bold">:</Text>
</Center>
<Input
type="text"
placeholder="value"
value={headerData[HEADER_FIELDS.VALUE]}
isInvalid={
!validator[INPUT_FIELDS.HEADERS][index][
HEADER_FIELDS.VALUE
]
}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.HEADERS,
e.target.value,
HEADER_FIELDS.VALUE,
index
)
}
width="65%"
/>
<InputRightElement width="3rem">
<Button
width="6rem"
colorScheme="blackAlpha"
variant="ghost"
padding="0"
onClick={() =>
updateHeadersHandler(
ArrayInputOperations.REMOVE,
index
)
}
>
<FaMinusCircle />
</Button>
</InputRightElement>
</InputGroup>
</Flex>
))}
</Flex>
</Flex>
</ModalBody>
<ModalFooter>
<Button
colorScheme="blue"
variant="solid"
onClick={() => {}}
isDisabled={loading}
>
<Center h="100%" pt="5%">
Save
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default AddWebhookModal;

View File

@ -30,6 +30,7 @@ import {
FiMenu,
FiUsers,
FiChevronDown,
FiAnchor,
} from 'react-icons/fi';
import { BiCustomize } from 'react-icons/bi';
import { AiOutlineKey } from 'react-icons/ai';
@ -111,6 +112,7 @@ const LinkItems: Array<LinkItemProps> = [
],
},
{ name: 'Users', icon: FiUsers, route: '/users' },
{ name: 'Webhooks', icon: FiAnchor, route: '/webhooks' },
];
interface SidebarProps extends BoxProps {

View File

@ -79,3 +79,11 @@ export const GenerateKeys = `
}
}
`;
export const AddWebhook = `
mutation addWebhook($params: AddWebhookRequest!) {
_add_webhook(params: $params) {
message
}
}
`;

View File

@ -0,0 +1,18 @@
import React from 'react';
import { Box, Flex, Text } from '@chakra-ui/react';
import AddWebhookModal from '../components/AddWebhookModal';
const Webhooks = () => {
return (
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
<Text fontSize="md" fontWeight="bold">
Webhooks
</Text>
<AddWebhookModal />
</Flex>
</Box>
);
};
export default Webhooks;

View File

@ -8,32 +8,34 @@ const Auth = lazy(() => import('../pages/Auth'));
const Environment = lazy(() => import('../pages/Environment'));
const Home = lazy(() => import('../pages/Home'));
const Users = lazy(() => import('../pages/Users'));
const Webhooks = lazy(() => import('../pages/Webhooks'));
export const AppRoutes = () => {
const { isLoggedIn } = useAuthContext();
if (isLoggedIn) {
return (
<div>
<Suspense fallback={<></>}>
<Routes>
<Route
element={
<DashboardLayout>
<Outlet />
</DashboardLayout>
}
>
<Route path="/" element={<Outlet />}>
<Route index element={<Environment />} />
<Route path="/:sec" element={<Environment />} />
</Route>
<Route path="users" element={<Users />} />
<Route path="*" element={<Home />} />
</Route>
</Routes>
</Suspense>
</div>
<div>
<Suspense fallback={<></>}>
<Routes>
<Route
element={
<DashboardLayout>
<Outlet />
</DashboardLayout>
}
>
<Route path="/" element={<Outlet />}>
<Route index element={<Environment />} />
<Route path="/:sec" element={<Environment />} />
</Route>
<Route path="users" element={<Users />} />
<Route path="webhooks" element={<Webhooks />} />
<Route path="*" element={<Home />} />
</Route>
</Routes>
</Suspense>
</div>
);
}
return (

View File

@ -86,3 +86,8 @@ export const validateURI = (uri: string) => {
? true
: false;
};
export const validateEventName = (name: string) => {
if (!name || name === '') return true;
return name.toLowerCase().match(/^.{4,}[.].{5,}$/) ? true : false;
};