· 4 years ago · Apr 15, 2021, 09:20 PM
1import * as React from 'react';
2import { useState, useEffect } from 'react';
3import { FunctionComponent } from 'react';
4import {
5 Table,
6 TableHeader,
7 TableHeaderRow,
8 TableHeaderCell,
9 TableBody,
10 TableRow,
11 TableCell
12} from '@trendmicro/react-styled-ui/Table';
13
14import {
15 ThemeProvider,
16 theme,
17 CSSBaseline,
18 ColorModeProvider,
19 Slide,
20 Drawer,
21 DrawerOverlay,
22 DrawerContent,
23 DrawerHeader,
24 DrawerBody,
25 DrawerFooter,
26 Button,
27 useDisclosure,
28 Input,
29 TextLabel,
30 Text,
31 Icon
32} from '@trendmicro/react-styled-ui';
33import { AutoSizer } from 'react-virtualized';
34import { useTable, useBlockLayout, useRowSelect, useSortBy } from 'react-table';
35
36import * as Styles from './react-table.styles';
37
38interface IReactTableProps {
39 options: any
40}
41
42function getC1Endpoint():string {
43 const c1Services = JSON.parse(sessionStorage.getItem('C1C_Shell_State')).c1Services.c1ServicesList,
44 applicationService = c1Services.find(service => service.id === "application")
45
46 const c1Endpoint = applicationService.links.find(link => link.rel === "endpoint").href;
47
48 return c1Endpoint;
49}
50
51function getInternalEndpoint():string {
52 return sessionStorage.getItem('_tmco_api');
53}
54
55const columns = [
56 {
57 Header: 'Function Name',
58 accessor: 'functionName',
59 width: 'auto'
60 },
61 {
62 Header: 'Function ID',
63 accessor: 'functionID',
64 width: 150
65 },
66 {
67 Header: 'Group',
68 accessor: 'group',
69 width: 200
70 },
71 {
72 Header: 'Last Seen',
73 accessor: 'lastSeen',
74 width: 300
75 },
76];
77
78const emptyData = []
79const data1 = [
80 { id: 1, functionName: 'Virus/Malware', functionID: 20, group: 634, lastSeen: 634 },
81 { id: 2, functionName: 'Spyware/Grayware', functionID: 20, group: 634, lastSeen: 634 },
82 { id: 3, functionName: 'URL Filtering', functionID: 15, group: 598, lastSeen: 598 },
83 { id: 4, functionName: 'Web Reputation', functionID: 15, group: 598, lastSeen: 598 },
84 { id: 5, functionName: 'Network Virus', functionID: 15, group: 497, lastSeen: 497 },
85 { id: 6, functionName: 'Application Control', functionID: 0, group: 1, lastSeen: 0 },
86 { id: 7, functionName: 'Another One', functionID: 5, group: 0, lastSeen: 8 },
87 { id: 8, functionName: 'Flash McQueen', functionID: 0, group: 6, lastSeen: 8 },
88 { id: 9, functionName: 'PEST control', functionID: 0, group: 0, lastSeen: 0 },
89 { id: 10, functionName: 'Iterative Randomized Controller', functionID: 4, group: 67, lastSeen: 0 }
90];
91const data2 = [
92 { id: 5, functionName: 'Network Virus', functionID: 333, group: 488897, lastSeen: 11 },
93 { id: 1, functionName: 'Virus/Malware', functionID: 45, group: 1, lastSeen: 1 },
94 { id: 4, functionName: 'Web Reputation', functionID: 54, group: 777, lastSeen: 777 },
95 { id: 2, functionName: 'Spyware/Grayware', functionID: 66, group: 2, lastSeen: 2 },
96 { id: 5, functionName: 'Network Virus', functionID: 333, group: 488897, lastSeen: 11 },
97 { id: 1, functionName: 'Virus/Malware', functionID: 45, group: 1, lastSeen: 1 },
98 { id: 4, functionName: 'Web Reputation', functionID: 54, group: 777, lastSeen: 777 },
99 { id: 2, functionName: 'Spyware/Grayware', functionID: 66, group: 2, lastSeen: 2 },
100 { id: 5, functionName: 'Network Virus', functionID: 333, group: 488897, lastSeen: 11 },
101 { id: 1, functionName: 'Virus/Malware', functionID: 45, group: 1, lastSeen: 1 },
102 { id: 4, functionName: 'Web Reputation', functionID: 54, group: 777, lastSeen: 777 },
103 { id: 2, functionName: 'Spyware/Grayware', functionID: 66, group: 2, lastSeen: 2 },
104 { id: 5, functionName: 'Network Virus', functionID: 333, group: 488897, lastSeen: 11 },
105 { id: 1, functionName: 'Virus/Malware', functionID: 45, group: 1, lastSeen: 1 },
106 { id: 4, functionName: 'Web Reputation', functionID: 54, group: 777, lastSeen: 777 },
107 { id: 2, functionName: 'Spyware/Grayware', functionID: 66, group: 2, lastSeen: 2 },
108 { id: 5, functionName: 'Network Virus', functionID: 333, group: 488897, lastSeen: 11 },
109 { id: 1, functionName: 'Virus/Malware', functionID: 45, group: 1, lastSeen: 1 },
110 { id: 4, functionName: 'Web Reputation', functionID: 54, group: 777, lastSeen: 777 },
111 { id: 2, functionName: 'Spyware/Grayware', functionID: 66, group: 2, lastSeen: 2 },
112];
113
114
115// const {
116// getTableProps,
117// getTableBodyProps,
118// headerGroups,
119// rows,
120// prepareRow,
121// } = useTable(
122// {
123// columns,
124// data,
125// },
126// useBlockLayout,
127// );
128
129const getCalculatedColumns = ({ initColumns, tableWidth }) => {
130 const columns = initColumns.map(column => {
131 let columnWidth = column.width;
132 if (typeof columnWidth === 'string') {
133 const lastChar = columnWidth.substr(columnWidth.length - 1);
134 if (lastChar === '%') {
135 columnWidth = tableWidth * (parseFloat(columnWidth) / 100);
136 return {
137 ...column,
138 width: columnWidth
139 };
140 }
141 if (columnWidth === 'auto') {
142 return {
143 ...column,
144 width: 0
145 };
146 }
147 }
148 return column;
149 });
150 const customWidthColumns = columns.filter(column => !!column.width);
151 const totalCustomWidth = customWidthColumns.reduce((accumulator, column) => accumulator + column.width, 0);
152 let defaultCellWidth = (tableWidth - totalCustomWidth) / (columns.length - customWidthColumns.length);
153 defaultCellWidth = defaultCellWidth <= 0 ? 150 : defaultCellWidth;
154 return columns.map(column => {
155 if (!!column.width) {
156 return column;
157 }
158 return {
159 ...column,
160 width: defaultCellWidth
161 };
162 });
163};
164
165export const ReactTable: FunctionComponent<IReactTableProps> = (props: IReactTableProps) => {
166 const tabs:string[] = ['unprotected', 'protected'];
167
168 const [data, setdata] = useState(emptyData);
169 const [page, setPage] = useState(1);
170 const [dataType, setDataType] = useState(tabs[0]);
171 const [functionDetailsData, setFunctionDetailsData] = useState({});
172 const [functionsSearchValue, setFunctionsSearchValue] = useState('');
173 const [isLoading, setIsLoading] = useState(true);
174 const [isDrawerLoading, setIsDrawerLoading] = useState(true);
175 const [isDrawerError, setIsDrawerError] = useState(false);
176 const api = window['INTERNAL_SHELL'] ? getInternalEndpoint() : getC1Endpoint();
177
178 const { isOpen, onOpen, onClose } = useDisclosure();
179
180 function fetchData(dataType:string, pageParam:number = 1) {
181 setIsLoading(true);
182
183 let endpoint:string;
184 if(dataType === 'protected') {
185 endpoint = 'accounts/groups';
186 } else if(dataType === 'unprotected') {
187 endpoint = 'agent/groups/status';
188 }
189
190 if(pageParam) endpoint + '?page=' + pageParam;
191
192 fetch(api + endpoint, {
193 headers: new Headers({
194 'Authorization': 'AppSecBearer ' + atob(localStorage.getItem('_tmcotok'))
195 })
196 })
197 .then(res => res.json())
198 .then((result) => {
199 if(dataType === 'protected') {
200 setdata(data1);
201 setIsLoading(false);
202 } else if(dataType === 'unprotected') {
203 setdata(data2);
204 setIsLoading(false);
205 }
206 }, (error) => {
207 setIsLoading(false);
208 debugger
209 });
210 }
211
212 function fetchFunctionData(functionID:string) {
213 console.log('calling data for function ID:',functionID);
214 setIsDrawerLoading(true);
215 fetch(api + 'accounts/groups/9c0d8fc8-5a68-427e-a457-7252caa99657', {
216 headers: new Headers({
217 'Authorization': 'AppSecBearer ' + atob(localStorage.getItem('_tmcotok'))
218 })
219 })
220 .then(res => res.json())
221 .then((result) => {
222 setFunctionDetailsData(result);
223 setIsDrawerError(false);
224 setIsDrawerLoading(false);
225 }, (error) => {
226 setFunctionDetailsData({});
227 setIsDrawerLoading(false);
228 setIsDrawerError(true);
229 })
230 }
231
232 function switchData(type:string) {
233 setDataType(type);
234 fetchData(dataType, null);
235 }
236
237 function prevPage() {
238 if(page > 1) setPage(page - 1);
239 fetchData(dataType, page);
240 }
241
242 function nextPage() {
243 setPage(page + 1);
244 fetchData(dataType, page);
245 }
246
247 let currentFunctionID:string;
248
249 function setCurrentFunction(functionIndex:number) {
250 currentFunctionID = data[functionIndex]['functionID'];
251 fetchFunctionData(currentFunctionID);
252
253 onOpen();
254 }
255
256 // function searchFunctions(searchString:string) {
257 // fetchData('unprotected', searchString);
258 // }
259
260 let timer;
261 function handleSearchChange(event) {
262 if(timer) clearInterval(timer);
263
264 timer = window.setTimeout(() => {
265 fetchData('unprotected', event.target.value);
266 // searchFunctions(event.target.value);
267 }, 1500);
268 }
269
270 useEffect(() => {
271 fetchData(tabs[0]);
272 }, []);
273
274 return (
275 <>
276 <div style={Styles.FunctionListWrapper}>
277 <div style={Styles.VerticalTabs}>
278 {
279 tabs.map((tab, index) => {
280 const tabStyle = dataType === tab ? Styles.VerticalTabActive : Styles.VerticalTab;
281 return (
282 <>
283 <button style={tabStyle} onClick={() => switchData(tab)}>{tab}</button>
284 </>
285 )
286 }, this)
287 }
288 {/*
289 <button style={Styles.VerticalTab} onClick={() => switchData('protected')}>Protected</button>
290 <button style={Styles.VerticalTab} onClick={() => switchData('unprotected')}>Unprotected</button>
291 */}
292 </div>
293
294 <div style={Styles.TableWrapper}>
295 <ThemeProvider theme={theme}>
296 <ColorModeProvider value="dark">
297 <CSSBaseline />
298 {/*
299 <div style={Styles.TableSearchWrapper}>
300 <div style={Styles.TableSearch}>
301 <TextLabel mb="1x">Search functions:</TextLabel>
302 <Input onChange={(e) => {handleSearchChange(e)}} style={Styles.TableSearchInput}/>
303 <Text size="xs" mt="1x">Search by ID, Name or Group</Text>
304 </div>
305 </div>
306 */}
307
308 <div style={Styles.TablePaginationWrapper}>
309 <span style={Styles.TablePaginationPage}>Page: {page}</span>
310 <Button variant="ghost" style={Styles.PaginationPrevButton} disabled={page == 1 || isLoading} onClick={() => prevPage()}>
311 <Icon icon="angle-left" /> Previous
312 </Button>
313 <Button variant="ghost" style={Styles.PaginationNextButton} disabled={isLoading} onClick={() => nextPage()}>
314 Next <Icon icon="angle-right" />
315 </Button>
316 </div>
317 </ColorModeProvider>
318 </ThemeProvider>
319
320 {isLoading &&
321 <div style={Styles.LoadingOverlay}>
322 <span>Loading...</span>
323 </div>
324 }
325
326 {!isLoading &&
327 <AutoSizer>
328 {({ height, width }) => {
329 if (height === 0 || width === 0) {
330 return null;
331 }
332 const newColumns = getCalculatedColumns({ initColumns: columns, tableWidth: width });
333 return (
334 <ThemeProvider theme={theme}>
335 <ColorModeProvider value="dark">
336 <CSSBaseline />
337
338 <Table isHoverable>
339 <TableHeader>
340 <TableHeaderRow>
341 {
342 columns.map((col, i) => {
343 const columnId = col.accessor;
344 const _column = newColumns.filter(column => column.accessor === columnId);
345 const _columnWidth = _column[0].width;
346
347 return (
348 <TableHeaderCell key={col['accessor']} width={_columnWidth}>{col['Header']}</TableHeaderCell>
349 );
350 })
351 }
352 </TableHeaderRow>
353 </TableHeader>
354 <TableBody>
355 {data.map((row, i) => {
356 return (
357 <TableRow onClick={() => {
358 setCurrentFunction(i);
359 }}>
360 {
361 columns.map(col => {
362 const columnId = col.accessor;
363 const _column = newColumns.filter(column => column.accessor === columnId);
364 const _columnWidth = _column[0].width;
365
366 return (<TableCell key={col['accessor']} width={_columnWidth}>{row[col['accessor']]}</TableCell>);
367 })
368 }
369 </TableRow>
370 );
371 })}
372 </TableBody>
373 </Table>
374 </ColorModeProvider>
375 </ThemeProvider>
376 );
377 }}
378 </AutoSizer>
379 }
380 </div>
381 </div>
382
383 <ThemeProvider theme={theme}>
384 <ColorModeProvider value="dark">
385 <CSSBaseline />
386 <Slide
387 in={isOpen}
388 duration={250}
389 from={'right'}
390 finalHeight="100vh"
391 >
392 {styles => (
393 <Drawer
394 ensureFocus={true}
395 autoFocus={true}
396 backdrop={true}
397 isCloseButtonVisible={true}
398 isOpen={true} // Set to `true` if a transition is active
399 closeOnEsc={true}
400 closeOnOutsideClick={true}
401 onClose={onClose}
402 placement={'right'}
403 size={'100vh'}>
404 <DrawerOverlay opacity={styles.opacity} />
405 <DrawerContent style={Styles.FunctionsDrawer} {...styles}>
406 <DrawerHeader>
407 HEADER
408 </DrawerHeader>
409
410 <DrawerBody>
411 {isDrawerLoading && !isDrawerError &&
412 <div>
413 DRAWER IS LOADING DATA
414 </div>
415 }
416
417 {isDrawerError && !isDrawerLoading &&
418 <div>
419 ERROR LOADING DATA.
420
421 <Button onClick={() => {
422 fetchFunctionData(currentFunctionID);
423 }} minWidth="20x">
424 RETRY
425 </Button>
426 </div>
427 }
428
429 {!isDrawerLoading && !isDrawerError && functionDetailsData['account_id'] &&
430 <div>
431 <ul>
432 <li>
433 Account ID
434 {functionDetailsData && functionDetailsData['account_id']}
435 </li>
436 <li>
437 activated_on
438 {functionDetailsData && functionDetailsData['activated_on']}
439 </li>
440 <li>
441 created_on
442 {functionDetailsData && functionDetailsData['created_on']}
443 </li>
444 <li>
445 credentials key
446 {functionDetailsData['credentials']['key']}
447 </li>
448 <li>
449 credentials secret
450 {functionDetailsData['credentials']['secret']}
451 </li>
452 <li>
453 group_id
454 {functionDetailsData && functionDetailsData['group_id']}
455 </li>
456 <li>
457 name
458 {functionDetailsData && functionDetailsData['name']}
459 </li>
460 </ul>
461 </div>
462 }
463 </DrawerBody>
464
465 <DrawerFooter>
466 <Button variant="primary" onClick={onClose} minWidth="20x">
467 OK
468 </Button>
469 <Button onClick={onClose} minWidth="20x">
470 Cancel
471 </Button>
472 </DrawerFooter>
473 </DrawerContent>
474 </Drawer>
475 )}
476 </Slide>
477 </ColorModeProvider>
478 </ThemeProvider>
479 </>
480 );
481};
482