gitscope
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/pages/nodes/modals/core-settings-modal.tsx · v0.1.4
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 16e5ea1 Mrdn 2y ago import { useMutation } from '@tanstack/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 16e5ea1 Mrdn 2y ago const {
173 16e5ea1 Mrdn 2y ago mutate: handleRestartCore,
174 16e5ea1 Mrdn 2y ago isPending: isRestarting
175 16e5ea1 Mrdn 2y ago } = useMutation({ mutationFn: restartCore });
176 a47eb4c Dawsh 2y ago
177 a47eb4c Dawsh 2y ago const handleOnSave = ({ config }: any) => {
178 a47eb4c Dawsh 2y ago updateConfig(config)
179 a47eb4c Dawsh 2y ago .then(() => {
180 a47eb4c Dawsh 2y ago toast({
181 bf7c131 Mardin 2y ago title: t('core.successMessage'),
182 bf7c131 Mardin 2y ago status: 'success',
183 a47eb4c Dawsh 2y ago isClosable: true,
184 bf7c131 Mardin 2y ago position: 'top',
185 a47eb4c Dawsh 2y ago duration: 3000,
186 a47eb4c Dawsh 2y ago });
187 a47eb4c Dawsh 2y ago })
188 a47eb4c Dawsh 2y ago .catch((e) => {
189 bf7c131 Mardin 2y ago let message = t('core.generalErrorMessage');
190 bf7c131 Mardin 2y ago if (typeof e.response._data.detail === 'object')
191 a47eb4c Dawsh 2y ago message =
192 a47eb4c Dawsh 2y ago e.response._data.detail[Object.keys(e.response._data.detail)[0]];
193 bf7c131 Mardin 2y ago if (typeof e.response._data.detail === 'string')
194 a47eb4c Dawsh 2y ago message = e.response._data.detail;
195 a47eb4c Dawsh 2y ago
196 a47eb4c Dawsh 2y ago toast({
197 a47eb4c Dawsh 2y ago title: message,
198 bf7c131 Mardin 2y ago status: 'error',
199 a47eb4c Dawsh 2y ago isClosable: true,
200 bf7c131 Mardin 2y ago position: 'top',
201 a47eb4c Dawsh 2y ago duration: 3000,
202 a47eb4c Dawsh 2y ago });
203 a47eb4c Dawsh 2y ago });
204 a47eb4c Dawsh 2y ago };
205 a47eb4c Dawsh 2y ago const editorRef = useRef<HTMLDivElement>(null);
206 a47eb4c Dawsh 2y ago const [isFullScreen, setFullScreen] = useState(false);
207 a47eb4c Dawsh 2y ago const handleFullScreen = () => {
208 a47eb4c Dawsh 2y ago if (document.fullscreenElement) {
209 a47eb4c Dawsh 2y ago document.exitFullscreen();
210 a47eb4c Dawsh 2y ago setFullScreen(false);
211 a47eb4c Dawsh 2y ago } else {
212 a47eb4c Dawsh 2y ago editorRef.current?.requestFullscreen();
213 a47eb4c Dawsh 2y ago setFullScreen(true);
214 a47eb4c Dawsh 2y ago }
215 a47eb4c Dawsh 2y ago };
216 a47eb4c Dawsh 2y ago return (
217 a47eb4c Dawsh 2y ago <form onSubmit={form.handleSubmit(handleOnSave)}>
218 a47eb4c Dawsh 2y ago <ModalBody>
219 a47eb4c Dawsh 2y ago <FormControl>
220 a47eb4c Dawsh 2y ago <HStack justifyContent="space-between" alignItems="flex-start">
221 a47eb4c Dawsh 2y ago <FormLabel>
222 bf7c131 Mardin 2y ago {t('core.configuration')}{' '}
223 a47eb4c Dawsh 2y ago {isLoading && <CircularProgress isIndeterminate size="15px" />}
224 a47eb4c Dawsh 2y ago </FormLabel>
225 a47eb4c Dawsh 2y ago <HStack gap={0}>
226 a47eb4c Dawsh 2y ago <Tooltip label="Xray Version" placement="top">
227 a47eb4c Dawsh 2y ago <Badge height="100%" textTransform="lowercase">
228 a47eb4c Dawsh 2y ago {version && `v${version}`}
229 a47eb4c Dawsh 2y ago </Badge>
230 a47eb4c Dawsh 2y ago </Tooltip>
231 a47eb4c Dawsh 2y ago </HStack>
232 a47eb4c Dawsh 2y ago </HStack>
233 a47eb4c Dawsh 2y ago <Box position="relative" ref={editorRef} minHeight="300px">
234 a47eb4c Dawsh 2y ago <Controller
235 a47eb4c Dawsh 2y ago control={form.control}
236 a47eb4c Dawsh 2y ago name="config"
237 a47eb4c Dawsh 2y ago render={({ field }) => (
238 a47eb4c Dawsh 2y ago <JsonEditor json={config} onChange={field.onChange} />
239 a47eb4c Dawsh 2y ago )}
240 a47eb4c Dawsh 2y ago />
241 a47eb4c Dawsh 2y ago <IconButton
242 a47eb4c Dawsh 2y ago size="xs"
243 a47eb4c Dawsh 2y ago aria-label="full screen"
244 a47eb4c Dawsh 2y ago variant="ghost"
245 a47eb4c Dawsh 2y ago position="absolute"
246 a47eb4c Dawsh 2y ago top="2"
247 a47eb4c Dawsh 2y ago right="4"
248 a47eb4c Dawsh 2y ago onClick={handleFullScreen}
249 a47eb4c Dawsh 2y ago >
250 a47eb4c Dawsh 2y ago {!isFullScreen ? <FullScreenIcon /> : <ExitFullScreenIcon />}
251 a47eb4c Dawsh 2y ago </IconButton>
252 a47eb4c Dawsh 2y ago </Box>
253 a47eb4c Dawsh 2y ago </FormControl>
254 a47eb4c Dawsh 2y ago <FormControl mt="4">
255 a47eb4c Dawsh 2y ago <HStack justifyContent="space-between">
256 bf7c131 Mardin 2y ago <FormLabel>{t('core.logs')}</FormLabel>
257 a47eb4c Dawsh 2y ago <Text as={FormLabel}>{t(`core.socket.${status}`)}</Text>
258 a47eb4c Dawsh 2y ago </HStack>
259 a47eb4c Dawsh 2y ago <Box
260 a47eb4c Dawsh 2y ago border="1px solid"
261 a47eb4c Dawsh 2y ago borderColor="gray.300"
262 a47eb4c Dawsh 2y ago bg="#F9F9F9"
263 a47eb4c Dawsh 2y ago _dark={{
264 bf7c131 Mardin 2y ago borderColor: 'gray.500',
265 bf7c131 Mardin 2y ago bg: '#2e3440',
266 a47eb4c Dawsh 2y ago }}
267 a47eb4c Dawsh 2y ago borderRadius={5}
268 a47eb4c Dawsh 2y ago minHeight="200px"
269 bf7c131 Mardin 2y ago maxHeight={'250px'}
270 a47eb4c Dawsh 2y ago p={2}
271 a47eb4c Dawsh 2y ago overflowY="auto"
272 a47eb4c Dawsh 2y ago ref={logsDiv}
273 a47eb4c Dawsh 2y ago >
274 a47eb4c Dawsh 2y ago {logs.map((message, i) => (
275 a47eb4c Dawsh 2y ago <Text fontSize="xs" opacity={0.8} key={i} whiteSpace="pre-line">
276 a47eb4c Dawsh 2y ago {message}
277 a47eb4c Dawsh 2y ago </Text>
278 a47eb4c Dawsh 2y ago ))}
279 a47eb4c Dawsh 2y ago </Box>
280 a47eb4c Dawsh 2y ago </FormControl>
281 a47eb4c Dawsh 2y ago </ModalBody>
282 a47eb4c Dawsh 2y ago <ModalFooter>
283 a47eb4c Dawsh 2y ago <HStack w="full" justifyContent="space-between">
284 a47eb4c Dawsh 2y ago <Box>
285 a47eb4c Dawsh 2y ago <Button
286 a47eb4c Dawsh 2y ago size="sm"
287 a47eb4c Dawsh 2y ago leftIcon={
288 a47eb4c Dawsh 2y ago <ReloadIcon
289 a47eb4c Dawsh 2y ago className={classNames({
290 bf7c131 Mardin 2y ago 'animate-spin': isRestarting,
291 a47eb4c Dawsh 2y ago })}
292 a47eb4c Dawsh 2y ago />
293 a47eb4c Dawsh 2y ago }
294 a47eb4c Dawsh 2y ago onClick={() => handleRestartCore()}
295 a47eb4c Dawsh 2y ago >
296 bf7c131 Mardin 2y ago {t(isRestarting ? 'core.restarting' : 'core.restartCore')}
297 a47eb4c Dawsh 2y ago </Button>
298 a47eb4c Dawsh 2y ago </Box>
299 a47eb4c Dawsh 2y ago <HStack>
300 a47eb4c Dawsh 2y ago <Button
301 a47eb4c Dawsh 2y ago size="sm"
302 a47eb4c Dawsh 2y ago variant="solid"
303 a47eb4c Dawsh 2y ago colorScheme="primary"
304 a47eb4c Dawsh 2y ago px="5"
305 a47eb4c Dawsh 2y ago type="submit"
306 a47eb4c Dawsh 2y ago isDisabled={isLoading || isPostLoading}
307 a47eb4c Dawsh 2y ago isLoading={isPostLoading}
308 a47eb4c Dawsh 2y ago >
309 bf7c131 Mardin 2y ago {t('core.save')}
310 a47eb4c Dawsh 2y ago </Button>
311 a47eb4c Dawsh 2y ago </HStack>
312 a47eb4c Dawsh 2y ago </HStack>
313 a47eb4c Dawsh 2y ago </ModalFooter>
314 a47eb4c Dawsh 2y ago </form>
315 a47eb4c Dawsh 2y ago );
316 a47eb4c Dawsh 2y ago };
317 a47eb4c Dawsh 2y ago export const CoreSettingsModal: FC = () => {
318 4205669 Mardin 2y ago const { isEditingCore } = useNodes();
319 4205669 Mardin 2y ago const onClose = useNodes.setState.bind(null, { isEditingCore: false });
320 a47eb4c Dawsh 2y ago const { t } = useTranslation();
321 a47eb4c Dawsh 2y ago
322 a47eb4c Dawsh 2y ago return (
323 a47eb4c Dawsh 2y ago <Modal isOpen={isEditingCore} onClose={onClose} size="3xl">
324 a47eb4c Dawsh 2y ago <ModalOverlay bg="blackAlpha.300" backdropFilter="blur(10px)" />
325 a47eb4c Dawsh 2y ago <ModalContent mx="3" w="full">
326 a47eb4c Dawsh 2y ago <ModalHeader pt={6}>
327 a47eb4c Dawsh 2y ago <HStack gap={2}>
328 a47eb4c Dawsh 2y ago <Icon color="primary">
329 a47eb4c Dawsh 2y ago <UsageIcon color="white" />
330 a47eb4c Dawsh 2y ago </Icon>
331 a47eb4c Dawsh 2y ago <Text fontWeight="semibold" fontSize="lg">
332 bf7c131 Mardin 2y ago {t('core.title')}
333 a47eb4c Dawsh 2y ago </Text>
334 a47eb4c Dawsh 2y ago </HStack>
335 a47eb4c Dawsh 2y ago </ModalHeader>
336 a47eb4c Dawsh 2y ago <ModalCloseButton mt={3} />
337 a47eb4c Dawsh 2y ago <CoreSettingModalContent />
338 a47eb4c Dawsh 2y ago </ModalContent>
339 a47eb4c Dawsh 2y ago </Modal>
340 a47eb4c Dawsh 2y ago );
341 a47eb4c Dawsh 2y ago };