gitscope
← marzneshin / v0.2.1 / app/dashboard/src/pages/nodes/dialog.tsx
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/pages/nodes/dialog.tsx · TypeScript
Blame Raw
  1import {
  2  Box,
  3  Button,
  4  chakra,
  5  Checkbox,
  6  FormControl,
  7  FormLabel,
  8  HStack,
  9  Modal,
 10  ModalBody,
 11  ModalCloseButton,
 12  ModalContent,
 13  ModalOverlay,
 14  Switch,
 15  Tooltip,
 16  useToast,
 17  VStack, IconButton, Spacer
 18} from '@chakra-ui/react';
 19import { zodResolver } from '@hookform/resolvers/zod';
 20import {
 21  getNodeCreateDefaultValues,
 22  NodeCreate,
 23  NodeCreateSchema,
 24  useNodes,
 25} from 'stores';
 26import { FC, useEffect, useState } from 'react';
 27import { Controller, useForm } from 'react-hook-form';
 28import { useTranslation } from 'react-i18next';
 29import 'slick-carousel/slick/slick-theme.css';
 30import 'slick-carousel/slick/slick.css';
 31import { DialogModalHeader, DeleteIcon, AddIcon, EditIcon } from 'components/dialog';
 32import { Input } from 'components/input';
 33
 34const CustomInput = chakra(Input, {
 35  baseStyle: {
 36    bg: 'white',
 37    _dark: {
 38      bg: 'gray.700',
 39    },
 40  },
 41});
 42
 43export const NodesDialog: FC = () => {
 44  const { editingNode, isEditingNode, onEditingNode, isAddingNode, refetchNodes, updateNode, addNode, onAddingNode, onDeletingNode } = useNodes();
 45  const [loading, setLoading] = useState(false);
 46  const [, setError] = useState<string | null>('');
 47
 48  const { t } = useTranslation();
 49  const toast = useToast();
 50  const defaultValueNode: NodeCreate = (isEditingNode === true && editingNode !== null && editingNode !== undefined) ? editingNode : getNodeCreateDefaultValues();
 51  const form = useForm<NodeCreate>({
 52    resolver: zodResolver(NodeCreateSchema),
 53    defaultValues: defaultValueNode,
 54  });
 55  const isOpen = isEditingNode || isAddingNode;
 56  const onClose = () => {
 57    onAddingNode(false);
 58    onEditingNode(undefined);
 59  };
 60  useEffect(() => {
 61    if (isOpen) {
 62      refetchNodes();
 63      form.reset(defaultValueNode);
 64    }
 65  }, [isOpen]);
 66
 67  const submit = async (values: NodeCreate) => {
 68    setLoading(true);
 69    const methods = { edited: updateNode, created: addNode };
 70    const method = isEditingNode ? 'edited' : 'created';
 71    const { name, port, id, address, usage_coefficient } = values;
 72    setError(null);
 73    let body: NodeCreate = {
 74      id,
 75      name,
 76      address,
 77      port,
 78      status: values.status,
 79      usage_coefficient,
 80      add_as_new_host: values.add_as_new_host,
 81    };
 82
 83    await methods[method](body)
 84      .then(() => {
 85        toast({
 86          title: t(
 87            isEditingNode ? 'nodeDialog.editNode' : 'nodeDialog.createNewNode',
 88            { name: values.name }
 89          ),
 90          status: 'success',
 91          isClosable: true,
 92          position: 'top',
 93          duration: 3000,
 94        });
 95        refetchNodes();
 96        onClose();
 97      })
 98      .catch((err) => {
 99        if (err?.response?.status === 409 || err?.response?.status === 400)
100          setError(err?.response?._data?.detail);
101        if (err?.response?.status === 422) {
102          Object.keys(err.response._data.detail).forEach((key) => {
103            setError(err?.response._data.detail[key] as string);
104            form.setError(
105              key as 'address' | 'port' | 'name' | 'status',
106              {
107                type: 'custom',
108                message: err.response._data.detail[key],
109              }
110            );
111          });
112        }
113      })
114      .finally(() => {
115        setLoading(false);
116      });
117  };
118
119  return (
120    <>
121      <Modal isOpen={isOpen} onClose={onClose}>
122        <ModalOverlay bg="blackAlpha.300" backdropFilter="blur(10px)" />
123        <ModalContent mx="3" w="fit-content" maxW="3xl">
124          <DialogModalHeader HeaderIcon={isEditingNode ? EditIcon : AddIcon} title={isEditingNode ? t('nodeDialog.editNode') : t('nodeDialog.addNode')} />
125          <ModalCloseButton mt={3} />
126          <ModalBody w="440px" pb={6} pt={3}>
127            <VStack>
128              {loading && 'loading...'}
129
130              <form onSubmit={form.handleSubmit((v) => submit(v))}>
131                <VStack>
132                  <HStack w="full">
133                    <FormControl>
134                      <CustomInput
135                        label={t('nodeDialog.nodeName')}
136                        size="sm"
137                        placeholder="Marzn-Node-2"
138                        {...form.register('name')}
139                        error={form.formState?.errors?.name?.message}
140                      />
141                    </FormControl>
142                    <HStack px={1}>
143                      <Controller
144                        name="status"
145                        control={form.control}
146                        render={({ field }) => {
147                          return (
148                            <Tooltip
149                              key={field.value}
150                              placement="top"
151                              label={
152                                `${t('usersTable.status')}: ` +
153                                (field.value !== 'disabled' ? t('active') : t('disabled'))
154                              }
155                              textTransform="capitalize"
156                            >
157                              <Box mt="6">
158                                <Switch
159                                  colorScheme="primary"
160                                  isChecked={field.value !== 'disabled'}
161                                  onChange={(e) => {
162                                    if (e.target.checked) {
163                                      field.onChange('none');
164                                    } else {
165                                      field.onChange('disabled');
166                                    }
167                                  }}
168                                />
169                              </Box>
170                            </Tooltip>
171                          );
172                        }}
173                      />
174                    </HStack>
175                  </HStack>
176                  <HStack alignItems="flex-start">
177                    <Box w="65%">
178                      <CustomInput
179                        label={t('nodeDialog.nodeAddress')}
180                        size="sm"
181                        placeholder="51.20.12.13"
182                        {...form.register('address')}
183                        error={form.formState?.errors?.address?.message}
184                      />
185                    </Box>
186                    <Box w="35%">
187                      <CustomInput
188                        label={t('nodeDialog.nodePort')}
189                        size="sm"
190                        placeholder="62050"
191                        {...form.register('port')}
192                        error={form.formState?.errors?.port?.message}
193                      />
194                    </Box>
195                  </HStack>
196                  {!editingNode && (
197                    <FormControl py={1}>
198                      <Checkbox {...form.register('add_as_new_host')}>
199                        <FormLabel m={0}>{t('nodeDialog.addHostForEveryInbound')}</FormLabel>
200                      </Checkbox>
201                    </FormControl>
202                  )}
203                  <Spacer m={2}></Spacer>
204                  <HStack w="full">
205                    <Tooltip label={t('delete')} placement="top">
206                      <IconButton
207                        colorScheme="red"
208                        variant="ghost"
209                        size="sm"
210                        aria-label="delete node"
211                        onClick={() => {
212                          if (editingNode !== null) {
213                            onDeletingNode(editingNode);
214                            onClose();
215                          }
216                        }}
217                      >
218                        <DeleteIcon />
219                      </IconButton>
220                    </Tooltip>
221                    <Button
222                      flexGrow={1}
223                      type="submit"
224                      colorScheme="primary"
225                      size="sm"
226                      px={5}
227                      w="full"
228                      isLoading={loading}
229                    >
230                      {isEditingNode ? t('nodeDialog.updateSubmit') : t('nodeDialog.addSubmit')}
231                    </Button>
232                  </HStack>
233                </VStack>
234              </form>
235            </VStack>
236          </ModalBody>
237        </ModalContent>
238      </Modal>
239    </>
240  );
241}