update: webhooks
This commit is contained in:
parent
f2886e6da8
commit
8e655daa71
12
dashboard/package-lock.json
generated
12
dashboard/package-lock.json
generated
|
@ -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",
|
||||
|
|
401
dashboard/src/components/AddWebhookModal.tsx
Normal file
401
dashboard/src/components/AddWebhookModal.tsx
Normal 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;
|
|
@ -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 {
|
||||
|
|
|
@ -79,3 +79,11 @@ export const GenerateKeys = `
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const AddWebhook = `
|
||||
mutation addWebhook($params: AddWebhookRequest!) {
|
||||
_add_webhook(params: $params) {
|
||||
message
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
18
dashboard/src/pages/Webhooks.tsx
Normal file
18
dashboard/src/pages/Webhooks.tsx
Normal 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;
|
|
@ -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 (
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user