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