1import {
2 Accordion,
3 AccordionButton,
4 AccordionItem,
5 AccordionPanel,
6 Box,
7 HStack,
8 IconButton,
9 Table,
10 TableProps,
11 Tbody,
12 Td,
13 Text,
14 Th,
15 Thead,
16 Tooltip,
17 Tr,
18 useBreakpointValue,
19 VStack,
20} from '@chakra-ui/react';
21import classNames from 'classnames';
22import { FC, Fragment, useEffect, useState } from 'react';
23import { useTranslation } from 'react-i18next';
24import {
25 EmptySection,
26 AccordionArrowIcon,
27 EditIcon,
28 Sort,
29 handleSort,
30} from 'components/table';
31import { useInbounds, fetchInboundHosts } from 'stores';
32import { useQuery } from 'react-query';
33
34type ExpandedIndex = number | number[];
35
36type NodesTableProps = {} & TableProps;
37
38export const HostsTable: FC<NodesTableProps> = (props) => {
39 const {
40 hostsFilters: filters,
41 selectedInbound,
42 onEditingHost,
43 onCreatingHost,
44 onHostsFilterChange,
45 refetchHosts,
46 } = useInbounds();
47 const { data: hosts } = useQuery('hosts', () => {
48 if (selectedInbound !== null) {
49 return fetchInboundHosts(selectedInbound.id);
50 }
51 });
52
53 useEffect(() => { refetchHosts() }, [selectedInbound]);
54
55 const { t } = useTranslation();
56 const [selectedRow, setSelectedRow] = useState<ExpandedIndex | undefined>(
57 undefined
58 );
59
60 const useTable = useBreakpointValue({ base: false, md: true });
61
62 // TODO: Find a different mechancism to to detect ftiler
63 // const isFiltered = users.length !== total;
64 const toggleAccordion = (index: number) => {
65 setSelectedRow(index === selectedRow ? undefined : index);
66 };
67
68 return (
69 <Box id="hosts-table" borderTopStartRadius="0" overflowX={{ base: 'unset', md: 'unset' }} >
70 <Accordion
71 allowMultiple
72 display={{ base: 'block', md: 'none' }}
73 index={selectedRow}
74 >
75 <Table orientation="vertical" zIndex="docked" {...props}>
76 <Thead zIndex="docked" position="relative">
77 <Tr>
78 <Th
79 minW="220px"
80 pl={4}
81 pr={4}
82 cursor={'pointer'}
83 onClick={handleSort.bind(null, filters, 'remark', onHostsFilterChange)}
84 >
85 <HStack>
86 <span>{t('remark')}</span>
87 <Sort sort={filters.sort} column="remark" />
88 </HStack>
89 </Th>
90 <Th
91 minW="100px"
92 cursor={'pointer'}
93 pr={0}
94 onClick={handleSort.bind(null, filters, 'address', onHostsFilterChange)}
95 >
96 <HStack>
97 <span>{t('hostsTable.address')}</span>
98 <Sort sort={filters.sort} column="address" />
99 </HStack>
100 </Th>
101 <Th
102 minW="100px"
103 cursor={'pointer'}
104 pr={0}
105 onClick={handleSort.bind(null, filters, 'port', onHostsFilterChange)}
106 >
107 <HStack>
108 <span>{t('hostsTable.port')}</span>
109 <Sort sort={filters.sort} column="port" />
110 </HStack>
111 </Th>
112 {/* Action buttons (certificate, core settings)*/}
113 </Tr>
114 </Thead>
115 <Tbody>
116 {!useTable && hosts?.map((host, i) => {
117 return (
118 <Fragment key={i}>
119 <Tr
120 onClick={toggleAccordion.bind(null, i)}
121 cursor="pointer"
122 >
123 <Td borderBottom={0} minW="100px" pl={4} pr={4}>
124 {host.remark}
125 </Td>
126 <Td borderBottom={0} minW="100px" pr={0}>
127 {host.address}
128 </Td>
129 <Td p={0} borderBottom={0} w="32px" minW="32px">
130 <AccordionArrowIcon
131 color="gray.600"
132 _dark={{
133 color: 'gray.400',
134 }}
135 transition="transform .2s ease-out"
136 transform={
137 selectedRow === i ? 'rotate(180deg)' : '0deg'
138 }
139 />
140 </Td>
141 </Tr>
142 <Tr
143 className="collapsible"
144 onClick={toggleAccordion.bind(null, i)}
145 >
146 <Td p={0} colSpan={4}>
147 <AccordionItem border={0}>
148 <AccordionButton display="none"></AccordionButton>
149 <AccordionPanel
150 border={0}
151 cursor="pointer"
152 px={6}
153 py={3}
154 >
155 <VStack justifyContent="space-between" spacing="4">
156 <VStack
157 alignItems="flex-start"
158 w="full"
159 spacing={-1}
160 >
161 <Text
162 textTransform="capitalize"
163 fontSize="xs"
164 fontWeight="bold"
165 color="gray.600"
166 _dark={{
167 color: 'gray.400',
168 }}
169 >
170 {t('nodesTable.ipPort')}
171 </Text>
172 <Box width="full" minW="230px">
173 {host.port}
174 </Box>
175 </VStack>
176 <HStack w="full" justifyContent="space-between">
177 <HStack>
178 <Tooltip
179 label={t('nodesDialog.editNode')}
180 placement="top"
181 >
182 <IconButton
183 p="0 !important"
184 aria-label="Edit user"
185 bg="transparent"
186 _dark={{
187 _hover: {
188 bg: 'gray.700',
189 },
190 }}
191 size={{
192 base: 'sm',
193 md: 'md',
194 }}
195 onClick={(e) => {
196 e.stopPropagation();
197 onEditingHost(true, host);
198 }}
199 >
200 <EditIcon />
201 </IconButton>
202 </Tooltip>
203 </HStack>
204 </HStack>
205 </VStack>
206 </AccordionPanel>
207 </AccordionItem>
208 </Td>
209 </Tr>
210 </Fragment>
211 );
212 })}
213 </Tbody>
214 </Table>
215 </Accordion>
216 <Table
217 orientation="vertical"
218 display={{ base: 'none', md: 'table' }}
219 {...props}
220 >
221 <Thead zIndex="docked" position="relative">
222 <Tr>
223 <Th
224 minW="120px"
225 pl={4}
226 pr={4}
227 cursor={'pointer'}
228 onClick={handleSort.bind(null, filters, 'remark', onHostsFilterChange)}
229 >
230 <HStack>
231 <span>{t('remark')}</span>
232 <Sort sort={filters.sort} column="remark" />
233 </HStack>
234 </Th>
235 <Th
236 minW="100px"
237 cursor={'pointer'}
238 pr={0}
239 onClick={handleSort.bind(null, filters, 'address', onHostsFilterChange)}
240 >
241 <HStack>
242 <span>{t('hostsTable.address')}</span>
243 <Sort sort={filters.sort} column="address" />
244 </HStack>
245 </Th>
246 <Th
247 minW="100px"
248 cursor={'pointer'}
249 pr={0}
250 onClick={handleSort.bind(null, filters, 'port', onHostsFilterChange)}
251 >
252 <HStack>
253 <span>{t('hostsTable.port')}</span>
254 <Sort sort={filters.sort} column="port" />
255 </HStack>
256 </Th>
257 </Tr>
258 </Thead>
259 <Tbody overflowY="auto">
260 {useTable && hosts?.map((host, i) => {
261 return (
262 <Tr
263 key={i}
264 className={classNames('interactive', {
265 'last-row': i === hosts.length - 1,
266 })}
267 onClick={() => onEditingHost(true, host)}
268 >
269 <Td minW="140px">
270 <div className="flex-status">
271 {host.remark}
272 </div>
273 </Td>
274 <Td width="400px" minW="150px">
275 {host.address}
276 </Td>
277 <Td width="350px" minW="150px">
278 {host.port}
279 </Td>
280 </Tr>
281 );
282 })}
283 {(hosts?.length === 0 || hosts === undefined) && (
284 <Tr>
285 <Td colSpan={5}>
286 <EmptySection
287 isFiltered={/* isFiltered */ false}
288 noObjectT="hostsTable.noHost"
289 noObjectMatchedT='hostsTable.noHostMatched'
290 createObjectT="addNode"
291 onCreateObject={onCreatingHost}
292 />
293 </Td>
294 </Tr>
295 )}
296 </Tbody>
297 </Table>
298 {/* TODO:
299 * <Pagination filters={filters} total={hosts === undefined ? 0 : hosts.length} onHostsFilterChange={onFilterChange} pageSizeManager={pageSizeManagers.hosts} />
300 */}
301 </Box>
302 );
303};