gitscope
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/pages/nodes/modals/core-settings-modal.tsx · v0.1.0
Source
1 a47eb4c Dawsh 2y ago import {
2 a47eb4c Dawsh 2y ago Badge,
3 a47eb4c Dawsh 2y ago Box,
4 a47eb4c Dawsh 2y ago Button,
5 a47eb4c Dawsh 2y ago chakra,
6 a47eb4c Dawsh 2y ago CircularProgress,
7 a47eb4c Dawsh 2y ago FormControl,
8 a47eb4c Dawsh 2y ago FormLabel,
9 a47eb4c Dawsh 2y ago HStack,
10 a47eb4c Dawsh 2y ago IconButton,
11 a47eb4c Dawsh 2y ago Modal,
12 a47eb4c Dawsh 2y ago ModalBody,
13 a47eb4c Dawsh 2y ago ModalCloseButton,
14 a47eb4c Dawsh 2y ago ModalContent,
15 a47eb4c Dawsh 2y ago ModalFooter,
16 a47eb4c Dawsh 2y ago ModalHeader,
17 a47eb4c Dawsh 2y ago ModalOverlay,
18 a47eb4c Dawsh 2y ago Text,
19 a47eb4c Dawsh 2y ago Tooltip,
20 a47eb4c Dawsh 2y ago useToast,
21 bf7c131 Mardin 2y ago } from '@chakra-ui/react';
22 a47eb4c Dawsh 2y ago import {
23 a47eb4c Dawsh 2y ago ArrowPathIcon,
24 a47eb4c Dawsh 2y ago ArrowsPointingInIcon,
25 a47eb4c Dawsh 2y ago ArrowsPointingOutIcon,
26 a47eb4c Dawsh 2y ago Cog6ToothIcon,
27 bf7c131 Mardin 2y ago } from '@heroicons/react/24/outline';
28 bf7c131 Mardin 2y ago import { joinPaths } from '@remix-run/router';
29 bf7c131 Mardin 2y ago import classNames from 'classnames';
30 89f8f93 Mardin 2y ago import { useNodes, useCoreSettings } from 'stores';
31 bf7c131 Mardin 2y ago import debounce from 'lodash.debounce';
32 bf7c131 Mardin 2y ago import { FC, useCallback, useEffect, useRef, useState } from 'react';
33 bf7c131 Mardin 2y ago import { Controller, useForm } from 'react-hook-form';
34 bf7c131 Mardin 2y ago import { useTranslation } from 'react-i18next';
35 bf7c131 Mardin 2y ago import { useMutation } from 'react-query';
36 bf7c131 Mardin 2y ago import { ReadyState } from 'react-use-websocket';
37 bf7c131 Mardin 2y ago import { useWebSocket } from 'react-use-websocket/dist/lib/use-websocket';
38 bedeeff Mardin 2y ago import { getAuthToken } from 'service/auth-storage';
39 bedeeff Mardin 2y ago import { Icon } from 'components/icon';
40 bedeeff Mardin 2y ago import { JsonEditor } from 'components/json-editor';
41 bedeeff Mardin 2y ago import 'components/json-editor/themes.js';
42 a47eb4c Dawsh 2y ago
43 a47eb4c Dawsh 2y ago export const MAX_NUMBER_OF_LOGS = 500;
44 a47eb4c Dawsh 2y ago
45 a47eb4c Dawsh 2y ago const UsageIcon = chakra(Cog6ToothIcon, {
46 a47eb4c Dawsh 2y ago baseStyle: {
47 a47eb4c Dawsh 2y ago w: 5,
48 a47eb4c Dawsh 2y ago h: 5,
49 a47eb4c Dawsh 2y ago },
50 a47eb4c Dawsh 2y ago });
51 a47eb4c Dawsh 2y ago export const ReloadIcon = chakra(ArrowPathIcon, {
52 a47eb4c Dawsh 2y ago baseStyle: {
53 a47eb4c Dawsh 2y ago w: 4,
54 a47eb4c Dawsh 2y ago h: 4,
55 a47eb4c Dawsh 2y ago },
56 a47eb4c Dawsh 2y ago });
57 a47eb4c Dawsh 2y ago
58 a47eb4c Dawsh 2y ago export const FullScreenIcon = chakra(ArrowsPointingOutIcon, {
59 a47eb4c Dawsh 2y ago baseStyle: {
60 a47eb4c Dawsh 2y ago w: 4,
61 a47eb4c Dawsh 2y ago h: 4,
62 a47eb4c Dawsh 2y ago },
63 a47eb4c Dawsh 2y ago });
64 a47eb4c Dawsh 2y ago export const ExitFullScreenIcon = chakra(ArrowsPointingInIcon, {
65 a47eb4c Dawsh 2y ago baseStyle: {
66 a47eb4c Dawsh 2y ago w: 3,
67 a47eb4c Dawsh 2y ago h: 3,
68 a47eb4c Dawsh 2y ago },
69 a47eb4c Dawsh 2y ago });
70 a47eb4c Dawsh 2y ago
71 a47eb4c Dawsh 2y ago const getStatus = (status: string) => {
72 a47eb4c Dawsh 2y ago return {
73 bf7c131 Mardin 2y ago [ReadyState.CONNECTING]: 'connecting',
74 bf7c131 Mardin 2y ago [ReadyState.OPEN]: 'connected',
75 bf7c131 Mardin 2y ago [ReadyState.CLOSING]: 'closed',
76 bf7c131 Mardin 2y ago [ReadyState.CLOSED]: 'closed',
77 bf7c131 Mardin 2y ago [ReadyState.UNINSTANTIATED]: 'closed',
78 a47eb4c Dawsh 2y ago }[status];
79 a47eb4c Dawsh 2y ago };
80 a47eb4c Dawsh 2y ago
81 a47eb4c Dawsh 2y ago const getWebsocketUrl = () => {
82 a47eb4c Dawsh 2y ago try {
83 a47eb4c Dawsh 2y ago let baseURL = new URL(
84 bf7c131 Mardin 2y ago import.meta.env.VITE_BASE_API.startsWith('/')
85 a47eb4c Dawsh 2y ago ? window.location.origin + import.meta.env.VITE_BASE_API
86 a47eb4c Dawsh 2y ago : import.meta.env.VITE_BASE_API
87 a47eb4c Dawsh 2y ago );
88 a47eb4c Dawsh 2y ago
89 a47eb4c Dawsh 2y ago return (
90 bf7c131 Mardin 2y ago (baseURL.protocol === 'https:' ? 'wss://' : 'ws://') +
91 bf7c131 Mardin 2y ago joinPaths([baseURL.host + baseURL.pathname, '/core/logs']) +
92 bf7c131 Mardin 2y ago '?interval=1&token=' +
93 a47eb4c Dawsh 2y ago getAuthToken()
94 a47eb4c Dawsh 2y ago );
95 a47eb4c Dawsh 2y ago } catch (e) {
96 bf7c131 Mardin 2y ago console.error('Unable to generate websocket url');
97 a47eb4c Dawsh 2y ago console.error(e);
98 a47eb4c Dawsh 2y ago return null;
99 a47eb4c Dawsh 2y ago }
100 a47eb4c Dawsh 2y ago };
101 a47eb4c Dawsh 2y ago
102 a47eb4c Dawsh 2y ago let logsTmp: string[] = [];
103 a47eb4c Dawsh 2y ago const CoreSettingModalContent: FC = () => {
104 4205669 Mardin 2y ago const { isEditingCore } = useNodes();
105 a47eb4c Dawsh 2y ago const {
106 a47eb4c Dawsh 2y ago fetchCoreSettings,
107 a47eb4c Dawsh 2y ago updateConfig,
108 a47eb4c Dawsh 2y ago isLoading,
109 a47eb4c Dawsh 2y ago config,
110 a47eb4c Dawsh 2y ago isPostLoading,
111 a47eb4c Dawsh 2y ago version,
112 a47eb4c Dawsh 2y ago restartCore,
113 a47eb4c Dawsh 2y ago } = useCoreSettings();
114 a47eb4c Dawsh 2y ago const logsDiv = useRef<HTMLDivElement | null>(null);
115 a47eb4c Dawsh 2y ago const [logs, setLogs] = useState<string[]>([]);
116 a47eb4c Dawsh 2y ago const { t } = useTranslation();
117 a47eb4c Dawsh 2y ago const toast = useToast();
118 a47eb4c Dawsh 2y ago const form = useForm({
119 a47eb4c Dawsh 2y ago defaultValues: { config: config || {} },
120 a47eb4c Dawsh 2y ago });
121 a47eb4c Dawsh 2y ago
122 a47eb4c Dawsh 2y ago useEffect(() => {
123 bf7c131 Mardin 2y ago if (config) form.setValue('config', config);
124 a47eb4c Dawsh 2y ago }, [config]);
125 a47eb4c Dawsh 2y ago
126 a47eb4c Dawsh 2y ago useEffect(() => {
127 a47eb4c Dawsh 2y ago if (isEditingCore) fetchCoreSettings();
128 a47eb4c Dawsh 2y ago }, [isEditingCore]);
129 bf7c131 Mardin 2y ago ''.startsWith;
130 a47eb4c Dawsh 2y ago const scrollShouldStayOnEnd = useRef(true);
131 a47eb4c Dawsh 2y ago const updateLogs = useCallback(
132 a47eb4c Dawsh 2y ago debounce((logs: string[]) => {
133 a47eb4c Dawsh 2y ago const isScrollOnEnd =
134 a47eb4c Dawsh 2y ago Math.abs(
135 a47eb4c Dawsh 2y ago (logsDiv.current?.scrollTop || 0) -
136 bf7c131 Mardin 2y ago (logsDiv.current?.scrollHeight || 0) +
137 bf7c131 Mardin 2y ago (logsDiv.current?.offsetHeight || 0)
138 a47eb4c Dawsh 2y ago ) < 10;
139 a47eb4c Dawsh 2y ago if (logsDiv.current && isScrollOnEnd)
140 a47eb4c Dawsh 2y ago scrollShouldStayOnEnd.current = true;
141 a47eb4c Dawsh 2y ago else scrollShouldStayOnEnd.current = false;
142 a47eb4c Dawsh 2y ago if (logs.length < 40) setLogs(logs);
143 a47eb4c Dawsh 2y ago }, 300),
144 a47eb4c Dawsh 2y ago []
145 a47eb4c Dawsh 2y ago );
146 a47eb4c Dawsh 2y ago
147 a47eb4c Dawsh 2y ago const { readyState } = useWebSocket(getWebsocketUrl(), {
148 a47eb4c Dawsh 2y ago onMessage: (e: any) => {
149 a47eb4c Dawsh 2y ago logsTmp.push(e.data);
150 a47eb4c Dawsh 2y ago if (logsTmp.length > MAX_NUMBER_OF_LOGS)
151 a47eb4c Dawsh 2y ago logsTmp = logsTmp.splice(0, logsTmp.length - MAX_NUMBER_OF_LOGS);
152 a47eb4c Dawsh 2y ago updateLogs([...logsTmp]);
153 a47eb4c Dawsh 2y ago },
154 a47eb4c Dawsh 2y ago shouldReconnect: () => true,
155 a47eb4c Dawsh 2y ago reconnectAttempts: 10,
156 a47eb4c Dawsh 2y ago reconnectInterval: 1000,
157 a47eb4c Dawsh 2y ago });
158 a47eb4c Dawsh 2y ago
159 a47eb4c Dawsh 2y ago useEffect(() => {
160 a47eb4c Dawsh 2y ago if (logsDiv.current && scrollShouldStayOnEnd.current)
161 a47eb4c Dawsh 2y ago logsDiv.current.scrollTop = logsDiv.current?.scrollHeight;
162 a47eb4c Dawsh 2y ago }, [logs]);
163 a47eb4c Dawsh 2y ago
164 a47eb4c Dawsh 2y ago useEffect(() => {
165 a47eb4c Dawsh 2y ago return () => {
166 a47eb4c Dawsh 2y ago logsTmp = [];
167 a47eb4c Dawsh 2y ago };
168 a47eb4c Dawsh 2y ago }, []);
169 a47eb4c Dawsh 2y ago
170 a47eb4c Dawsh 2y ago const status = getStatus(readyState.toString());
171 a47eb4c Dawsh 2y ago
172 a47eb4c Dawsh 2y ago const { mutate: handleRestartCore, isLoading: isRestarting } =
173 a47eb4c Dawsh 2y ago useMutation(restartCore);
174 a47eb4c Dawsh 2y ago
175 a47eb4c Dawsh 2y ago const handleOnSave = ({ config }: any) => {
176 a47eb4c Dawsh 2y ago updateConfig(config)
177 a47eb4c Dawsh 2y ago .then(() => {
178 a47eb4c Dawsh 2y ago toast({
179 bf7c131 Mardin 2y ago title: t('core.successMessage'),
180 bf7c131 Mardin 2y ago status: 'success',
181 a47eb4c Dawsh 2y ago isClosable: true,
182 bf7c131 Mardin 2y ago position: 'top',
183 a47eb4c Dawsh 2y ago duration: 3000,
184 a47eb4c Dawsh 2y ago });
185 a47eb4c Dawsh 2y ago })
186 a47eb4c Dawsh 2y ago .catch((e) => {
187 bf7c131 Mardin 2y ago let message = t('core.generalErrorMessage');
188 bf7c131 Mardin 2y ago if (typeof e.response._data.detail === 'object')
189 a47eb4c Dawsh 2y ago message =
190 a47eb4c Dawsh 2y ago e.response._data.detail[Object.keys(e.response._data.detail)[0]];
191 bf7c131 Mardin 2y ago if (typeof e.response._data.detail === 'string')
192 a47eb4c Dawsh 2y ago message = e.response._data.detail;
193 a47eb4c Dawsh 2y ago
194 a47eb4c Dawsh 2y ago toast({
195 a47eb4c Dawsh 2y ago title: message,
196 bf7c131 Mardin 2y ago status: 'error',
197 a47eb4c Dawsh 2y ago isClosable: true,
198 bf7c131 Mardin 2y ago position: 'top',
199 a47eb4c Dawsh 2y ago duration: 3000,
200 a47eb4c Dawsh 2y ago });
201 a47eb4c Dawsh 2y ago });
202 a47eb4c Dawsh 2y ago };
203 a47eb4c Dawsh 2y ago const editorRef = useRef<HTMLDivElement>(null);
204 a47eb4c Dawsh 2y ago const [isFullScreen, setFullScreen] = useState(false);
205 a47eb4c Dawsh 2y ago const handleFullScreen = () => {
206 a47eb4c Dawsh 2y ago if (document.fullscreenElement) {
207 a47eb4c Dawsh 2y ago document.exitFullscreen();
208 a47eb4c Dawsh 2y ago setFullScreen(false);
209 a47eb4c Dawsh 2y ago } else {
210 a47eb4c Dawsh 2y ago editorRef.current?.requestFullscreen();
211 a47eb4c Dawsh 2y ago setFullScreen(true);
212 a47eb4c Dawsh 2y ago }
213 a47eb4c Dawsh 2y ago };
214 a47eb4c Dawsh 2y ago return (
215 a47eb4c Dawsh 2y ago <form onSubmit={form.handleSubmit(handleOnSave)}>
216 a47eb4c Dawsh 2y ago <ModalBody>
217 a47eb4c Dawsh 2y ago <FormControl>
218 a47eb4c Dawsh 2y ago <HStack justifyContent="space-between" alignItems="flex-start">
219 a47eb4c Dawsh 2y ago <FormLabel>
220 bf7c131 Mardin 2y ago {t('core.configuration')}{' '}
221 a47eb4c Dawsh 2y ago {isLoading && <CircularProgress isIndeterminate size="15px" />}
222 a47eb4c Dawsh 2y ago </FormLabel>
223 a47eb4c Dawsh 2y ago <HStack gap={0}>
224 a47eb4c Dawsh 2y ago <Tooltip label="Xray Version" placement="top">
225 a47eb4c Dawsh 2y ago <Badge height="100%" textTransform="lowercase">
226 a47eb4c Dawsh 2y ago {version && `v${version}`}
227 a47eb4c Dawsh 2y ago </Badge>
228 a47eb4c Dawsh 2y ago </Tooltip>
229 a47eb4c Dawsh 2y ago </HStack>
230 a47eb4c Dawsh 2y ago </HStack>
231 a47eb4c Dawsh 2y ago <Box position="relative" ref={editorRef} minHeight="300px">
232 a47eb4c Dawsh 2y ago <Controller
233 a47eb4c Dawsh 2y ago control={form.control}
234 a47eb4c Dawsh 2y ago name="config"
235 a47eb4c Dawsh 2y ago render={({ field }) => (
236 a47eb4c Dawsh 2y ago <JsonEditor json={config} onChange={field.onChange} />
237 a47eb4c Dawsh 2y ago )}
238 a47eb4c Dawsh 2y ago />
239 a47eb4c Dawsh 2y ago <IconButton
240 a47eb4c Dawsh 2y ago size="xs"
241 a47eb4c Dawsh 2y ago aria-label="full screen"
242 a47eb4c Dawsh 2y ago variant="ghost"
243 a47eb4c Dawsh 2y ago position="absolute"
244 a47eb4c Dawsh 2y ago top="2"
245 a47eb4c Dawsh 2y ago right="4"
246 a47eb4c Dawsh 2y ago onClick={handleFullScreen}
247 a47eb4c Dawsh 2y ago >
248 a47eb4c Dawsh 2y ago {!isFullScreen ? <FullScreenIcon /> : <ExitFullScreenIcon />}
249 a47eb4c Dawsh 2y ago </IconButton>
250 a47eb4c Dawsh 2y ago </Box>
251 a47eb4c Dawsh 2y ago </FormControl>
252 a47eb4c Dawsh 2y ago <FormControl mt="4">
253 a47eb4c Dawsh 2y ago <HStack justifyContent="space-between">
254 bf7c131 Mardin 2y ago <FormLabel>{t('core.logs')}</FormLabel>
255 a47eb4c Dawsh 2y ago <Text as={FormLabel}>{t(`core.socket.${status}`)}</Text>
256 a47eb4c Dawsh 2y ago </HStack>
257 a47eb4c Dawsh 2y ago <Box
258 a47eb4c Dawsh 2y ago border="1px solid"
259 a47eb4c Dawsh 2y ago borderColor="gray.300"
260 a47eb4c Dawsh 2y ago bg="#F9F9F9"
261 a47eb4c Dawsh 2y ago _dark={{
262 bf7c131 Mardin 2y ago borderColor: 'gray.500',
263 bf7c131 Mardin 2y ago bg: '#2e3440',
264 a47eb4c Dawsh 2y ago }}
265 a47eb4c Dawsh 2y ago borderRadius={5}
266 a47eb4c Dawsh 2y ago minHeight="200px"
267 bf7c131 Mardin 2y ago maxHeight={'250px'}
268 a47eb4c Dawsh 2y ago p={2}
269 a47eb4c Dawsh 2y ago overflowY="auto"
270 a47eb4c Dawsh 2y ago ref={logsDiv}
271 a47eb4c Dawsh 2y ago >
272 a47eb4c Dawsh 2y ago {logs.map((message, i) => (
273 a47eb4c Dawsh 2y ago <Text fontSize="xs" opacity={0.8} key={i} whiteSpace="pre-line">
274 a47eb4c Dawsh 2y ago {message}
275 a47eb4c Dawsh 2y ago </Text>
276 a47eb4c Dawsh 2y ago ))}
277 a47eb4c Dawsh 2y ago </Box>
278 a47eb4c Dawsh 2y ago </FormControl>
279 a47eb4c Dawsh 2y ago </ModalBody>
280 a47eb4c Dawsh 2y ago <ModalFooter>
281 a47eb4c Dawsh 2y ago <HStack w="full" justifyContent="space-between">
282 a47eb4c Dawsh 2y ago <Box>
283 a47eb4c Dawsh 2y ago <Button
284 a47eb4c Dawsh 2y ago size="sm"
285 a47eb4c Dawsh 2y ago leftIcon={
286 a47eb4c Dawsh 2y ago <ReloadIcon
287 a47eb4c Dawsh 2y ago className={classNames({
288 bf7c131 Mardin 2y ago 'animate-spin': isRestarting,
289 a47eb4c Dawsh 2y ago })}
290 a47eb4c Dawsh 2y ago />
291 a47eb4c Dawsh 2y ago }
292 a47eb4c Dawsh 2y ago onClick={() => handleRestartCore()}
293 a47eb4c Dawsh 2y ago >
294 bf7c131 Mardin 2y ago {t(isRestarting ? 'core.restarting' : 'core.restartCore')}
295 a47eb4c Dawsh 2y ago </Button>
296 a47eb4c Dawsh 2y ago </Box>
297 a47eb4c Dawsh 2y ago <HStack>
298 a47eb4c Dawsh 2y ago <Button
299 a47eb4c Dawsh 2y ago size="sm"
300 a47eb4c Dawsh 2y ago variant="solid"
301 a47eb4c Dawsh 2y ago colorScheme="primary"
302 a47eb4c Dawsh 2y ago px="5"
303 a47eb4c Dawsh 2y ago type="submit"
304 a47eb4c Dawsh 2y ago isDisabled={isLoading || isPostLoading}
305 a47eb4c Dawsh 2y ago isLoading={isPostLoading}
306 a47eb4c Dawsh 2y ago >
307 bf7c131 Mardin 2y ago {t('core.save')}
308 a47eb4c Dawsh 2y ago </Button>
309 a47eb4c Dawsh 2y ago </HStack>
310 a47eb4c Dawsh 2y ago </HStack>
311 a47eb4c Dawsh 2y ago </ModalFooter>
312 a47eb4c Dawsh 2y ago </form>
313 a47eb4c Dawsh 2y ago );
314 a47eb4c Dawsh 2y ago };
315 a47eb4c Dawsh 2y ago export const CoreSettingsModal: FC = () => {
316 4205669 Mardin 2y ago const { isEditingCore } = useNodes();
317 4205669 Mardin 2y ago const onClose = useNodes.setState.bind(null, { isEditingCore: false });
318 a47eb4c Dawsh 2y ago const { t } = useTranslation();
319 a47eb4c Dawsh 2y ago
320 a47eb4c Dawsh 2y ago return (
321 a47eb4c Dawsh 2y ago <Modal isOpen={isEditingCore} onClose={onClose} size="3xl">
322 a47eb4c Dawsh 2y ago <ModalOverlay bg="blackAlpha.300" backdropFilter="blur(10px)" />
323 a47eb4c Dawsh 2y ago <ModalContent mx="3" w="full">
324 a47eb4c Dawsh 2y ago <ModalHeader pt={6}>
325 a47eb4c Dawsh 2y ago <HStack gap={2}>
326 a47eb4c Dawsh 2y ago <Icon color="primary">
327 a47eb4c Dawsh 2y ago <UsageIcon color="white" />
328 a47eb4c Dawsh 2y ago </Icon>
329 a47eb4c Dawsh 2y ago <Text fontWeight="semibold" fontSize="lg">
330 bf7c131 Mardin 2y ago {t('core.title')}
331 a47eb4c Dawsh 2y ago </Text>
332 a47eb4c Dawsh 2y ago </HStack>
333 a47eb4c Dawsh 2y ago </ModalHeader>
334 a47eb4c Dawsh 2y ago <ModalCloseButton mt={3} />
335 a47eb4c Dawsh 2y ago <CoreSettingModalContent />
336 a47eb4c Dawsh 2y ago </ModalContent>
337 a47eb4c Dawsh 2y ago </Modal>
338 a47eb4c Dawsh 2y ago );
339 a47eb4c Dawsh 2y ago };