mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: projects using this application (#6355)

This commit is contained in:
		
							parent
							
								
									c049374a25
								
							
						
					
					
						commit
						9cd324bd7c
					
				| @ -1,5 +1,5 @@ | ||||
| /* eslint react/no-multi-comp:off */ | ||||
| import React, { lazy, useContext, useState } from 'react'; | ||||
| import React, { useContext, useState } from 'react'; | ||||
| import { | ||||
|     Box, | ||||
|     Avatar, | ||||
| @ -31,6 +31,7 @@ import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import { ApplicationEdit } from './ApplicationEdit/ApplicationEdit'; | ||||
| import ApplicationOverview from './ApplicationOverview'; | ||||
| 
 | ||||
| type Tab = { | ||||
|     title: string; | ||||
| @ -68,8 +69,6 @@ const StyledTab = styled(Tab)(({ theme }) => ({ | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const ApplicationOverview = lazy(() => import('./ApplicationOverview')); | ||||
| 
 | ||||
| export const Application = () => { | ||||
|     const useOldApplicationScreen = !useUiFlag('sdkReporting'); | ||||
|     const navigate = useNavigate(); | ||||
|  | ||||
							
								
								
									
										227
									
								
								frontend/src/component/application/ApplicationChart.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								frontend/src/component/application/ApplicationChart.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,227 @@ | ||||
| import { Box, Divider, styled, Typography, useTheme } from '@mui/material'; | ||||
| import { ArcherContainer, ArcherElement } from 'react-archer'; | ||||
| import { ConditionallyRender } from '../common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| import { FC, useLayoutEffect, useRef, useState } from 'react'; | ||||
| import { ApplicationOverviewSchema } from '../../openapi'; | ||||
| import { useRequiredPathParam } from '../../hooks/useRequiredPathParam'; | ||||
| import { WarningAmberRounded } from '@mui/icons-material'; | ||||
| 
 | ||||
| const StyledTable = styled('table')(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     marginTop: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| const StyledCell = styled('td')(({ theme }) => ({ | ||||
|     verticalAlign: 'top', | ||||
|     paddingLeft: 0, | ||||
|     paddingRight: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| const StyleApplicationContainer = styled(Box)(({ theme }) => ({ | ||||
|     marginBottom: theme.spacing(18), | ||||
|     display: 'flex', | ||||
|     justifyContent: 'center', | ||||
| })); | ||||
| 
 | ||||
| const StyledApplicationBox = styled(Box)<{ | ||||
|     mode: 'success' | 'warning'; | ||||
| }>(({ theme, mode }) => ({ | ||||
|     borderRadius: theme.shape.borderRadiusMedium, | ||||
|     border: '1px solid', | ||||
|     borderColor: theme.palette[mode].border, | ||||
|     backgroundColor: theme.palette[mode].light, | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     alignItems: 'center', | ||||
|     padding: theme.spacing(1.5, 3, 2, 3), | ||||
| })); | ||||
| 
 | ||||
| const StyledEnvironmentBox = styled(Box)<{ | ||||
|     mode: 'success' | 'warning'; | ||||
| }>(({ theme, mode }) => ({ | ||||
|     borderRadius: theme.shape.borderRadiusMedium, | ||||
|     border: '1px solid', | ||||
|     borderColor: | ||||
|         theme.palette[mode === 'success' ? 'secondary' : 'warning'].border, | ||||
|     backgroundColor: | ||||
|         theme.palette[mode === 'success' ? 'secondary' : 'warning'].light, | ||||
|     display: 'inline-block', | ||||
|     padding: theme.spacing(1.5, 1.5, 1.5, 1.5), | ||||
| })); | ||||
| 
 | ||||
| const StyledDivider = styled(Divider)(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(2), | ||||
|     marginBottom: theme.spacing(2), | ||||
|     width: '100%', | ||||
| })); | ||||
| 
 | ||||
| const StyledEnvironmentsContainer = styled(Box)({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'start', | ||||
|     gap: '20px', | ||||
| }); | ||||
| 
 | ||||
| const EnvironmentHeader = styled(Typography)(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     fontWeight: theme.fontWeight.bold, | ||||
| })); | ||||
| 
 | ||||
| const StyledStatus = styled(Typography)<{ | ||||
|     mode: 'success' | 'warning'; | ||||
| }>(({ theme, mode }) => ({ | ||||
|     gap: theme.spacing(1), | ||||
|     fontSize: theme.fontSizes.smallBody, | ||||
|     color: theme.palette[mode].dark, | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
| })); | ||||
| 
 | ||||
| const useElementWidth = () => { | ||||
|     const elementRef = useRef<HTMLDivElement>(null); | ||||
|     const [width, setWidth] = useState('100%'); | ||||
| 
 | ||||
|     useLayoutEffect(() => { | ||||
|         setWidth(`${elementRef.current?.scrollWidth}px`); | ||||
|     }, [elementRef, setWidth]); | ||||
| 
 | ||||
|     return { | ||||
|         elementRef, | ||||
|         width, | ||||
|     }; | ||||
| }; | ||||
| const SuccessStatus = () => ( | ||||
|     <StyledStatus mode='success'> | ||||
|         <WarningAmberRounded | ||||
|             sx={(theme) => ({ | ||||
|                 color: theme.palette.success.main, | ||||
|             })} | ||||
|         />{' '} | ||||
|         Everything looks good! | ||||
|     </StyledStatus> | ||||
| ); | ||||
| 
 | ||||
| const WarningStatus: FC = ({ children }) => ( | ||||
|     <StyledStatus mode='warning'> | ||||
|         <WarningAmberRounded | ||||
|             sx={(theme) => ({ | ||||
|                 color: theme.palette.warning.main, | ||||
|             })} | ||||
|         />{' '} | ||||
|         {children} | ||||
|     </StyledStatus> | ||||
| ); | ||||
| 
 | ||||
| interface IApplicationChartProps { | ||||
|     data: ApplicationOverviewSchema; | ||||
| } | ||||
| 
 | ||||
| export const ApplicationChart = ({ data }: IApplicationChartProps) => { | ||||
|     const applicationName = useRequiredPathParam('name'); | ||||
|     const { elementRef, width } = useElementWidth(); | ||||
|     const navigate = useNavigate(); | ||||
|     const theme = useTheme(); | ||||
| 
 | ||||
|     const mode: 'success' | 'warning' = | ||||
|         data.issues.length === 0 ? 'success' : 'warning'; | ||||
| 
 | ||||
|     return ( | ||||
|         <Box sx={{ width }}> | ||||
|             <ArcherContainer | ||||
|                 strokeColor={theme.palette.secondary.border} | ||||
|                 endMarker={false} | ||||
|             > | ||||
|                 <StyleApplicationContainer> | ||||
|                     <ArcherElement | ||||
|                         id='application' | ||||
|                         relations={data.environments.map((environment) => ({ | ||||
|                             targetId: environment.name, | ||||
|                             targetAnchor: 'top', | ||||
|                             sourceAnchor: 'bottom', | ||||
|                             style: { | ||||
|                                 strokeColor: | ||||
|                                     mode === 'success' | ||||
|                                         ? theme.palette.secondary.border | ||||
|                                         : theme.palette.warning.border, | ||||
|                             }, | ||||
|                         }))} | ||||
|                     > | ||||
|                         <StyledApplicationBox mode={mode}> | ||||
|                             <Typography | ||||
|                                 sx={(theme) => ({ | ||||
|                                     fontSize: theme.fontSizes.smallerBody, | ||||
|                                 })} | ||||
|                                 color='text.secondary' | ||||
|                             > | ||||
|                                 Application | ||||
|                             </Typography> | ||||
|                             <Typography | ||||
|                                 sx={(theme) => ({ | ||||
|                                     fontSize: theme.fontSizes.bodySize, | ||||
|                                     fontWeight: theme.fontWeight.bold, | ||||
|                                 })} | ||||
|                             > | ||||
|                                 {applicationName} | ||||
|                             </Typography> | ||||
| 
 | ||||
|                             <StyledDivider /> | ||||
| 
 | ||||
|                             <ConditionallyRender | ||||
|                                 condition={mode === 'success'} | ||||
|                                 show={<SuccessStatus />} | ||||
|                                 elseShow={ | ||||
|                                     <WarningStatus> | ||||
|                                         {data.issues.length} issues detected | ||||
|                                     </WarningStatus> | ||||
|                                 } | ||||
|                             /> | ||||
|                         </StyledApplicationBox> | ||||
|                     </ArcherElement> | ||||
|                 </StyleApplicationContainer> | ||||
| 
 | ||||
|                 <StyledEnvironmentsContainer ref={elementRef}> | ||||
|                     {data.environments.map((environment) => ( | ||||
|                         <ArcherElement | ||||
|                             id={environment.name} | ||||
|                             key={environment.name} | ||||
|                         > | ||||
|                             <StyledEnvironmentBox | ||||
|                                 mode={mode} | ||||
|                                 key={environment.name} | ||||
|                             > | ||||
|                                 <EnvironmentHeader> | ||||
|                                     {environment.name} environment | ||||
|                                 </EnvironmentHeader> | ||||
| 
 | ||||
|                                 <StyledTable> | ||||
|                                     <tbody> | ||||
|                                         <tr> | ||||
|                                             <StyledCell>Instances:</StyledCell> | ||||
|                                             <StyledCell> | ||||
|                                                 {environment.instanceCount} | ||||
|                                             </StyledCell> | ||||
|                                         </tr> | ||||
|                                         <tr> | ||||
|                                             <StyledCell>SDK:</StyledCell> | ||||
|                                             <StyledCell> | ||||
|                                                 {environment.sdks.map((sdk) => ( | ||||
|                                                     <div key={sdk}>{sdk}</div> | ||||
|                                                 ))} | ||||
|                                             </StyledCell> | ||||
|                                         </tr> | ||||
|                                         <tr> | ||||
|                                             <StyledCell>Last seen:</StyledCell> | ||||
|                                             <StyledCell> | ||||
|                                                 {environment.lastSeen} | ||||
|                                             </StyledCell> | ||||
|                                         </tr> | ||||
|                                     </tbody> | ||||
|                                 </StyledTable> | ||||
|                             </StyledEnvironmentBox> | ||||
|                         </ArcherElement> | ||||
|                     ))} | ||||
|                 </StyledEnvironmentsContainer> | ||||
|             </ArcherContainer> | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| @ -6,7 +6,7 @@ import { ApplicationOverviewIssuesSchema } from 'openapi'; | ||||
| const WarningContainer = styled(Box)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     paddingBottom: theme.spacing(8), | ||||
|     alignSelf: 'stretch', | ||||
| })); | ||||
| 
 | ||||
| const WarningHeader = styled(Box)(({ theme }) => ({ | ||||
|  | ||||
| @ -1,73 +1,13 @@ | ||||
| import { usePageTitle } from 'hooks/usePageTitle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { | ||||
|     Alert, | ||||
|     Box, | ||||
|     Divider, | ||||
|     styled, | ||||
|     Typography, | ||||
|     useTheme, | ||||
| } from '@mui/material'; | ||||
| import { Alert, Box, Divider, styled } from '@mui/material'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| import { ArcherContainer, ArcherElement } from 'react-archer'; | ||||
| import { FC, useLayoutEffect, useRef, useState } from 'react'; | ||||
| import { useApplicationOverview } from 'hooks/api/getters/useApplicationOverview/useApplicationOverview'; | ||||
| import { WarningAmberRounded } from '@mui/icons-material'; | ||||
| import { ApplicationIssues } from './ApplicationIssues/ApplicationIssues'; | ||||
| 
 | ||||
| const StyledTable = styled('table')(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     marginTop: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| const StyledCell = styled('td')(({ theme }) => ({ | ||||
|     verticalAlign: 'top', | ||||
|     paddingLeft: 0, | ||||
|     paddingRight: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| const StyleApplicationContainer = styled(Box)(({ theme }) => ({ | ||||
|     marginBottom: theme.spacing(18), | ||||
|     display: 'flex', | ||||
|     justifyContent: 'center', | ||||
| })); | ||||
| 
 | ||||
| const StyledApplicationBox = styled(Box)<{ | ||||
|     mode: 'success' | 'warning'; | ||||
| }>(({ theme, mode }) => ({ | ||||
|     borderRadius: theme.shape.borderRadiusMedium, | ||||
|     border: '1px solid', | ||||
|     borderColor: theme.palette[mode].border, | ||||
|     backgroundColor: theme.palette[mode].light, | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     alignItems: 'center', | ||||
|     padding: theme.spacing(1.5, 3, 2, 3), | ||||
| })); | ||||
| 
 | ||||
| const StyledStatus = styled(Typography)<{ | ||||
|     mode: 'success' | 'warning'; | ||||
| }>(({ theme, mode }) => ({ | ||||
|     gap: theme.spacing(1), | ||||
|     fontSize: theme.fontSizes.smallBody, | ||||
|     color: theme.palette[mode].dark, | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
| })); | ||||
| 
 | ||||
| const StyledEnvironmentBox = styled(Box)<{ | ||||
|     mode: 'success' | 'warning'; | ||||
| }>(({ theme, mode }) => ({ | ||||
|     borderRadius: theme.shape.borderRadiusMedium, | ||||
|     border: '1px solid', | ||||
|     borderColor: | ||||
|         theme.palette[mode === 'success' ? 'secondary' : 'warning'].border, | ||||
|     backgroundColor: | ||||
|         theme.palette[mode === 'success' ? 'secondary' : 'warning'].light, | ||||
|     display: 'inline-block', | ||||
|     padding: theme.spacing(1.5, 1.5, 1.5, 1.5), | ||||
| })); | ||||
| import { ApplicationChart } from './ApplicationChart'; | ||||
| import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined'; | ||||
| import { Badge } from '../common/Badge/Badge'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| 
 | ||||
| const StyledDivider = styled(Divider)(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(2), | ||||
| @ -75,203 +15,54 @@ const StyledDivider = styled(Divider)(({ theme }) => ({ | ||||
|     width: '100%', | ||||
| })); | ||||
| 
 | ||||
| const StyledEnvironmentsContainer = styled(Box)({ | ||||
| const ApplicationContainer = styled(Box)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'start', | ||||
|     gap: '20px', | ||||
| }); | ||||
| 
 | ||||
| const EnvironmentHeader = styled(Typography)(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     fontWeight: theme.fontWeight.bold, | ||||
|     padding: theme.spacing(1), | ||||
|     flexDirection: 'column', | ||||
|     alignItems: 'center', | ||||
|     gap: theme.spacing(2), | ||||
|     alignSelf: 'stretch', | ||||
| })); | ||||
| 
 | ||||
| const SuccessStatus = () => ( | ||||
|     <StyledStatus mode='success'> | ||||
|         <WarningAmberRounded | ||||
|             sx={(theme) => ({ | ||||
|                 color: theme.palette.success.main, | ||||
|             })} | ||||
|         />{' '} | ||||
|         Everything looks good! | ||||
|     </StyledStatus> | ||||
| ); | ||||
| const ProjectContainer = styled(Box)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
|     gap: theme.spacing(2), | ||||
|     alignSelf: 'stretch', | ||||
| })); | ||||
| 
 | ||||
| const WarningStatus: FC = ({ children }) => ( | ||||
|     <StyledStatus mode='warning'> | ||||
|         <WarningAmberRounded | ||||
|             sx={(theme) => ({ | ||||
|                 color: theme.palette.warning.main, | ||||
|             })} | ||||
|         />{' '} | ||||
|         {children} | ||||
|     </StyledStatus> | ||||
| ); | ||||
| 
 | ||||
| const useElementWidth = () => { | ||||
|     const elementRef = useRef<HTMLDivElement>(null); | ||||
|     const [width, setWidth] = useState('100%'); | ||||
| 
 | ||||
|     useLayoutEffect(() => { | ||||
|         setWidth(`${elementRef.current?.scrollWidth}px`); | ||||
|     }, [elementRef, setWidth]); | ||||
| 
 | ||||
|     return { | ||||
|         elementRef, | ||||
|         width, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| export const ApplicationOverview = () => { | ||||
| const ApplicationOverview = () => { | ||||
|     usePageTitle('Applications - Overview'); | ||||
|     const applicationName = useRequiredPathParam('name'); | ||||
|     const navigate = useNavigate(); | ||||
|     const theme = useTheme(); | ||||
|     const { data, loading } = useApplicationOverview(applicationName); | ||||
| 
 | ||||
|     // @ts-ignore
 | ||||
|     window.navigateToInstances = (environment: string) => { | ||||
|         navigate( | ||||
|             `/applications/${applicationName}/instances?environment=${environment}`, | ||||
|         ); | ||||
|     }; | ||||
| 
 | ||||
|     const { elementRef, width } = useElementWidth(); | ||||
| 
 | ||||
|     const mode: 'success' | 'warning' = | ||||
|         data.issues.length === 0 ? 'success' : 'warning'; | ||||
| 
 | ||||
|     return ( | ||||
|         <ConditionallyRender | ||||
|             condition={!loading && data.environments.length === 0} | ||||
|             show={<Alert severity='warning'>No data available.</Alert>} | ||||
|             elseShow={ | ||||
|                 <> | ||||
|                 <ApplicationContainer> | ||||
|                     <ProjectContainer> | ||||
|                         Projects using this application | ||||
|                         {data.projects.map((project) => ( | ||||
|                             <Badge | ||||
|                                 sx={{ cursor: 'pointer' }} | ||||
|                                 onClick={(e) => { | ||||
|                                     e.preventDefault(); | ||||
|                                     navigate(`/projects/${project}`); | ||||
|                                 }} | ||||
|                                 color='secondary' | ||||
|                                 icon={<TopicOutlinedIcon />} | ||||
|                             > | ||||
|                                 {project} | ||||
|                             </Badge> | ||||
|                         ))} | ||||
|                     </ProjectContainer> | ||||
|                     <StyledDivider /> | ||||
|                     <ApplicationIssues issues={data.issues} /> | ||||
|                     <Box sx={{ width }}> | ||||
|                         <ArcherContainer | ||||
|                             strokeColor={theme.palette.secondary.border} | ||||
|                             endMarker={false} | ||||
|                         > | ||||
|                             <StyleApplicationContainer> | ||||
|                                 <ArcherElement | ||||
|                                     id='application' | ||||
|                                     relations={data.environments.map( | ||||
|                                         (environment) => ({ | ||||
|                                             targetId: environment.name, | ||||
|                                             targetAnchor: 'top', | ||||
|                                             sourceAnchor: 'bottom', | ||||
|                                             style: { | ||||
|                                                 strokeColor: | ||||
|                                                     mode === 'success' | ||||
|                                                         ? theme.palette | ||||
|                                                               .secondary.border | ||||
|                                                         : theme.palette.warning | ||||
|                                                               .border, | ||||
|                                             }, | ||||
|                                         }), | ||||
|                                     )} | ||||
|                                 > | ||||
|                                     <StyledApplicationBox mode={mode}> | ||||
|                                         <Typography | ||||
|                                             sx={(theme) => ({ | ||||
|                                                 fontSize: | ||||
|                                                     theme.fontSizes.smallerBody, | ||||
|                                             })} | ||||
|                                             color='text.secondary' | ||||
|                                         > | ||||
|                                             Application | ||||
|                                         </Typography> | ||||
|                                         <Typography | ||||
|                                             sx={(theme) => ({ | ||||
|                                                 fontSize: | ||||
|                                                     theme.fontSizes.bodySize, | ||||
|                                                 fontWeight: | ||||
|                                                     theme.fontWeight.bold, | ||||
|                                             })} | ||||
|                                         > | ||||
|                                             {applicationName} | ||||
|                                         </Typography> | ||||
| 
 | ||||
|                                         <StyledDivider /> | ||||
| 
 | ||||
|                                         <ConditionallyRender | ||||
|                                             condition={mode === 'success'} | ||||
|                                             show={<SuccessStatus />} | ||||
|                                             elseShow={ | ||||
|                                                 <WarningStatus> | ||||
|                                                     {data.issues.length} issues | ||||
|                                                     detected | ||||
|                                                 </WarningStatus> | ||||
|                                             } | ||||
|                                         /> | ||||
|                                     </StyledApplicationBox> | ||||
|                                 </ArcherElement> | ||||
|                             </StyleApplicationContainer> | ||||
| 
 | ||||
|                             <StyledEnvironmentsContainer ref={elementRef}> | ||||
|                                 {data.environments.map((environment) => ( | ||||
|                                     <ArcherElement | ||||
|                                         id={environment.name} | ||||
|                                         key={environment.name} | ||||
|                                     > | ||||
|                                         <StyledEnvironmentBox | ||||
|                                             mode={mode} | ||||
|                                             key={environment.name} | ||||
|                                         > | ||||
|                                             <EnvironmentHeader> | ||||
|                                                 {environment.name} environment | ||||
|                                             </EnvironmentHeader> | ||||
| 
 | ||||
|                                             <StyledTable> | ||||
|                                                 <tbody> | ||||
|                                                     <tr> | ||||
|                                                         <StyledCell> | ||||
|                                                             Instances: | ||||
|                                                         </StyledCell> | ||||
|                                                         <StyledCell> | ||||
|                                                             { | ||||
|                                                                 environment.instanceCount | ||||
|                                                             } | ||||
|                                                         </StyledCell> | ||||
|                                                     </tr> | ||||
|                                                     <tr> | ||||
|                                                         <StyledCell> | ||||
|                                                             SDK: | ||||
|                                                         </StyledCell> | ||||
|                                                         <StyledCell> | ||||
|                                                             {environment.sdks.map( | ||||
|                                                                 (sdk) => ( | ||||
|                                                                     <div | ||||
|                                                                         key={ | ||||
|                                                                             sdk | ||||
|                                                                         } | ||||
|                                                                     > | ||||
|                                                                         {sdk} | ||||
|                                                                     </div> | ||||
|                                                                 ), | ||||
|                                                             )} | ||||
|                                                         </StyledCell> | ||||
|                                                     </tr> | ||||
|                                                     <tr> | ||||
|                                                         <StyledCell> | ||||
|                                                             Last seen: | ||||
|                                                         </StyledCell> | ||||
|                                                         <StyledCell> | ||||
|                                                             { | ||||
|                                                                 environment.lastSeen | ||||
|                                                             } | ||||
|                                                         </StyledCell> | ||||
|                                                     </tr> | ||||
|                                                 </tbody> | ||||
|                                             </StyledTable> | ||||
|                                         </StyledEnvironmentBox> | ||||
|                                     </ArcherElement> | ||||
|                                 ))} | ||||
|                             </StyledEnvironmentsContainer> | ||||
|                         </ArcherContainer> | ||||
|                     </Box> | ||||
|                 </> | ||||
|                     <ApplicationChart data={data} /> | ||||
|                 </ApplicationContainer> | ||||
|             } | ||||
|         /> | ||||
|     ); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user