gitscope
← marzneshin / v0.1.0 / app/dashboard/src/pages/nodes/table/index.tsx
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/pages/nodes/table/index.tsx · TypeScript
Blame Raw
  1
  2import {
  3  Accordion,
  4  AccordionButton,
  5  AccordionItem,
  6  AccordionPanel,
  7  Box,
  8  HStack,
  9  IconButton,
 10  Table,
 11  TableProps,
 12  Tbody,
 13  Td,
 14  Text,
 15  Th,
 16  Thead,
 17  Tooltip,
 18  Tr,
 19  useBreakpointValue,
 20  VStack,
 21} from '@chakra-ui/react';
 22import classNames from 'classnames';
 23import { ActionButtons } from './action-buttons';
 24import { pageSizeManagers } from 'utils/userPreferenceStorage';
 25import { FC, Fragment, useState } from 'react';
 26import { useTranslation } from 'react-i18next';
 27import { StatusBadge } from 'components/status-badge';
 28import {
 29  Pagination,
 30  EmptySection,
 31  AccordionArrowIcon,
 32  EditIcon,
 33  Sort,
 34  StatusSortSelect,
 35  handleSort,
 36} from 'components/table';
 37import { useNodes } from 'stores';
 38
 39type ExpandedIndex = number | number[];
 40
 41type NodesTableProps = {} & TableProps;
 42
 43export const NodesTable: FC<NodesTableProps> = (props) => {
 44  const {
 45    nodesFilters: filters,
 46    nodes,
 47    onEditingNode,
 48    onAddingNode,
 49    onFilterChange,
 50  } = useNodes();
 51
 52  const total = nodes.length;
 53
 54  const { t } = useTranslation();
 55  const [selectedRow, setSelectedRow] = useState<ExpandedIndex | undefined>(
 56    undefined
 57  );
 58
 59  const useTable = useBreakpointValue({ base: false, md: true });
 60
 61  // TODO: Find a different mechancism to to detect ftiler
 62  // const isFiltered = users.length !== total;
 63
 64  const handleStatusFilter = (e: any) => {
 65    onFilterChange({
 66      status: e.target.value.length > 0 ? e.target.value : undefined,
 67    });
 68  };
 69
 70  const toggleAccordion = (index: number) => {
 71    setSelectedRow(index === selectedRow ? undefined : index);
 72  };
 73
 74  const StatusSortSelectNodes = () => (
 75    <StatusSortSelect
 76      filters={filters}
 77      options={['healthy', 'unhealthy', 'disabled']}
 78      handleStatusFilter={handleStatusFilter}
 79    />
 80  );
 81
 82  return (
 83    <Box id="node-table" overflowX={{ base: 'unset', md: 'unset' }} >
 84      <Accordion
 85        allowMultiple
 86        display={{ base: 'block', md: 'none' }}
 87        index={selectedRow}
 88      >
 89        <Table orientation="vertical" zIndex="docked" {...props}>
 90          <Thead zIndex="docked" position="relative">
 91            <Tr>
 92              <Th
 93                position="fixed"
 94                minW="120px"
 95                pl={4}
 96                pr={4}
 97                cursor={'pointer'}
 98                onClick={handleSort.bind(null, filters, 'name', onFilterChange)}
 99              >
100                <HStack>
101                  <span>{t('name')}</span>
102                  <Sort sort={filters.sort} column="name" />
103                </HStack>
104              </Th>
105              <Th
106                position="fixed"
107                minW="50px"
108                pl={0}
109                pr={0}
110                w="140px"
111                cursor={'pointer'}
112              >
113                <HStack spacing={0} position="relative">
114                  <Text
115                    position="absolute"
116                    _dark={{
117                      bg: 'gray.750',
118                    }}
119                    _light={{
120                      bg: '#F9FAFB',
121                    }}
122                    userSelect="none"
123                    pointerEvents="none"
124                    zIndex={1}
125                    w="100%"
126                  >
127                    {t('nodesTable.status')}
128                    {filters.status ? ': ' + filters.status : ''}
129                  </Text>
130                  <StatusSortSelectNodes />
131                </HStack>
132              </Th>
133              <Th
134                position="fixed"
135                minW="100px"
136                cursor={'pointer'}
137                pr={0}
138                onClick={handleSort.bind(null, filters, 'xray_version', onFilterChange)}
139              >
140                <HStack>
141                  <span>{t('nodesTable.xrayVersion')}</span>
142                  <Sort sort={filters.sort} column="xray_version" />
143                </HStack>
144              </Th>
145              <Th
146                position="fixed"
147                minW="100px"
148                cursor={'pointer'}
149                pr={0}
150              >
151                <span>{t('nodesTable.ipPort')}</span>
152              </Th>
153              {/* Action buttons (certificate, core settings)*/}
154              <Th
155                position="fixed"
156                minW="32px"
157                w="32px"
158                p={0}
159                cursor={'pointer'}
160              ></Th>
161            </Tr>
162          </Thead>
163          <Tbody>
164            {!useTable &&
165              nodes?.map((node, i) => {
166                return (
167                  <Fragment key={node.name}>
168                    <Tr
169                      onClick={toggleAccordion.bind(null, i)}
170                      cursor="pointer"
171                    >
172                      <Td borderBottom={0} minW="100px" pl={4} pr={4}>
173                        {node.name}
174                      </Td>
175                      <Td borderBottom={0} minW="50px" pl={0} pr={0}>
176                        <StatusBadge
177                          compact
178                          showDetail={false}
179                          status={node.status}
180                        />
181                      </Td>
182                      <Td borderBottom={0} minW="100px" pr={0}>
183                        {node.xray_version}
184                      </Td>
185                      <Td p={0} borderBottom={0} w="32px" minW="32px">
186                        <AccordionArrowIcon
187                          color="gray.600"
188                          _dark={{
189                            color: 'gray.400',
190                          }}
191                          transition="transform .2s ease-out"
192                          transform={
193                            selectedRow === i ? 'rotate(180deg)' : '0deg'
194                          }
195                        />
196                      </Td>
197                    </Tr>
198                    <Tr
199                      className="collapsible"
200                      onClick={toggleAccordion.bind(null, i)}
201                    >
202                      <Td p={0} colSpan={4}>
203                        <AccordionItem border={0}>
204                          <AccordionButton display="none"></AccordionButton>
205                          <AccordionPanel
206                            border={0}
207                            cursor="pointer"
208                            px={6}
209                            py={3}
210                          >
211                            <VStack justifyContent="space-between" spacing="4">
212                              <VStack
213                                alignItems="flex-start"
214                                w="full"
215                                spacing={-1}
216                              >
217                                <Text
218                                  textTransform="capitalize"
219                                  fontSize="xs"
220                                  fontWeight="bold"
221                                  color="gray.600"
222                                  _dark={{
223                                    color: 'gray.400',
224                                  }}
225                                >
226                                  {t('nodesTable.ipPort')}
227                                </Text>
228                                <Box width="full" minW="230px">
229                                  {node.address}:{node.port}
230                                </Box>
231                              </VStack>
232                              <HStack w="full" justifyContent="space-between">
233                                <HStack>
234                                  <ActionButtons />
235                                  <Tooltip
236                                    label={t('nodesDialog.editNode')}
237                                    placement="top"
238                                  >
239                                    <IconButton
240                                      p="0 !important"
241                                      aria-label="Edit user"
242                                      bg="transparent"
243                                      _dark={{
244                                        _hover: {
245                                          bg: 'gray.700',
246                                        },
247                                      }}
248                                      size={{
249                                        base: 'sm',
250                                        md: 'md',
251                                      }}
252                                      onClick={(e) => {
253                                        e.stopPropagation();
254                                        onEditingNode(node);
255                                      }}
256                                    >
257                                      <EditIcon />
258                                    </IconButton>
259                                  </Tooltip>
260                                </HStack>
261                              </HStack>
262                            </VStack>
263                          </AccordionPanel>
264                        </AccordionItem>
265                      </Td>
266                    </Tr>
267                  </Fragment>
268                );
269              })}
270          </Tbody>
271        </Table>
272      </Accordion>
273      <Table
274        orientation="vertical"
275        display={{ base: 'none', md: 'table' }}
276        {...props}
277      >
278        <Thead zIndex="docked" position="relative">
279          <Tr>
280            <Th
281              minW="200px"
282              cursor={'pointer'}
283              onClick={handleSort.bind(null, filters, 'name', onFilterChange)}
284            >
285              <HStack>
286                <span>{t('name')}</span>
287                <Sort sort={filters.sort} column="name" />
288              </HStack>
289            </Th>
290            <Th
291              width="100px"
292              minW="70px"
293              cursor={'pointer'}
294            >
295              <HStack spacing={0} position="relative">
296                <Text
297                  position="absolute"
298                  _dark={{
299                    bg: 'gray.750',
300                  }}
301                  _light={{
302                    bg: '#F9FAFB',
303                  }}
304                  userSelect="none"
305                  pointerEvents="none"
306                  zIndex={1}
307                  w="100%"
308                >
309                  {t('nodesTable.status')}
310                  {filters.status ? ': ' + filters.status : ''}
311                </Text>
312                <StatusSortSelectNodes />
313              </HStack>
314            </Th>
315            <Th
316              width="100px"
317              minW="70px"
318              cursor={'pointer'}
319              onClick={handleSort.bind(null, filters, 'xray_version', onFilterChange)}
320            >
321              <HStack>
322                <span>{t('nodesTable.xrayVersion')}</span>
323                <Sort sort={filters.sort} column="xray_version" />
324              </HStack>
325            </Th>
326            <Th
327              width="100px"
328              minW="70px"
329              cursor={'pointer'}
330              onClick={handleSort.bind(null, filters, 'address', onFilterChange)}
331            >
332              <HStack>
333                <span>{t('nodesTable.ipPort')}</span>
334                <Sort sort={filters.sort} column="address" />
335              </HStack>
336            </Th>
337            <Th
338              width="60px"
339              minW="50px"
340            />
341          </Tr>
342        </Thead>
343        <Tbody overflowY="auto">
344          {useTable &&
345            nodes?.map((node, i) => {
346              return (
347                <Tr
348                  key={node.name}
349                  className={classNames('interactive', {
350                    'last-row': i === nodes.length - 1,
351                  })}
352                  onClick={() => onEditingNode(node)}
353                >
354                  <Td minW="140px">
355                    <div className="flex-status">
356                      {node.name}
357                    </div>
358                  </Td>
359                  <Td width="400px" minW="150px">
360                    <StatusBadge
361                      status={node.status}
362                    />
363                  </Td>
364                  <Td width="350px" minW="150px">
365                    {node.xray_version}
366                  </Td>
367                  <Td width="350px" minW="150px">
368                    {node.address}:{node.port}
369                  </Td>
370                  <Td width="200px" minW="180px">
371                    <ActionButtons />
372                  </Td>
373                </Tr>
374              );
375            })}
376          {nodes.length == 0 && (
377            <Tr>
378              <Td colSpan={5}>
379                <EmptySection
380                  isFiltered={/* isFiltered */ false}
381                  noObjectT="nodesTable.noNode"
382                  noObjectMatchedT='nodesTable.noNodeMatched'
383                  createObjectT="addNode"
384                  onCreateObject={onAddingNode}
385                />
386              </Td>
387            </Tr>
388          )}
389        </Tbody>
390      </Table>
391      <Pagination filters={filters} total={total} onFilterChange={onFilterChange} pageSizeManager={pageSizeManagers.nodes} />
392    </Box>
393  );
394};