|
| 1 | +import { useLocationSettings } from 'hooks/useLocationSettings'; |
| 2 | +import type { ConnectedEdge } from 'interfaces/connectedEdge'; |
| 3 | +import CircleIcon from '@mui/icons-material/Circle'; |
| 4 | +import ExpandMore from '@mui/icons-material/ExpandMore'; |
| 5 | +import { formatDateYMDHMS } from 'utils/formatDate'; |
| 6 | +import { |
| 7 | + Accordion, |
| 8 | + AccordionDetails, |
| 9 | + AccordionSummary, |
| 10 | + styled, |
| 11 | + Tooltip, |
| 12 | +} from '@mui/material'; |
| 13 | +import { Badge } from 'component/common/Badge/Badge'; |
| 14 | +import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; |
| 15 | +import { NetworkConnectedEdgeInstanceLatency } from './NetworkConnectedEdgeInstanceLatency'; |
| 16 | + |
| 17 | +const StyledInstance = styled('div')(({ theme }) => ({ |
| 18 | + borderRadius: theme.shape.borderRadiusMedium, |
| 19 | + border: '1px solid', |
| 20 | + borderColor: theme.palette.secondary.border, |
| 21 | + backgroundColor: theme.palette.secondary.light, |
| 22 | + display: 'flex', |
| 23 | + flexDirection: 'column', |
| 24 | + alignItems: 'center', |
| 25 | + padding: 0, |
| 26 | + zIndex: 1, |
| 27 | + marginTop: theme.spacing(1), |
| 28 | +})); |
| 29 | + |
| 30 | +const StyledAccordion = styled(Accordion)({ |
| 31 | + background: 'transparent', |
| 32 | + boxShadow: 'none', |
| 33 | +}); |
| 34 | + |
| 35 | +const StyledAccordionSummary = styled(AccordionSummary, { |
| 36 | + shouldForwardProp: (prop) => prop !== 'connectionStatus', |
| 37 | +})<{ connectionStatus: InstanceConnectionStatus }>( |
| 38 | + ({ theme, connectionStatus }) => ({ |
| 39 | + fontSize: theme.fontSizes.smallBody, |
| 40 | + padding: theme.spacing(1), |
| 41 | + minHeight: theme.spacing(3), |
| 42 | + '& .MuiAccordionSummary-content': { |
| 43 | + alignItems: 'center', |
| 44 | + gap: theme.spacing(1), |
| 45 | + margin: 0, |
| 46 | + '&.Mui-expanded': { |
| 47 | + margin: 0, |
| 48 | + }, |
| 49 | + '& svg': { |
| 50 | + fontSize: theme.fontSizes.mainHeader, |
| 51 | + color: |
| 52 | + connectionStatus === 'Stale' |
| 53 | + ? theme.palette.warning.main |
| 54 | + : connectionStatus === 'Disconnected' |
| 55 | + ? theme.palette.error.main |
| 56 | + : theme.palette.success.main, |
| 57 | + }, |
| 58 | + }, |
| 59 | + }), |
| 60 | +); |
| 61 | + |
| 62 | +const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ |
| 63 | + display: 'flex', |
| 64 | + flexDirection: 'column', |
| 65 | + fontSize: theme.fontSizes.smallerBody, |
| 66 | + gap: theme.spacing(2), |
| 67 | +})); |
| 68 | + |
| 69 | +const StyledDetailRow = styled('div')(({ theme }) => ({ |
| 70 | + display: 'flex', |
| 71 | + justifyContent: 'space-between', |
| 72 | + gap: theme.spacing(2), |
| 73 | + '& > span': { |
| 74 | + display: 'flex', |
| 75 | + alignItems: 'center', |
| 76 | + }, |
| 77 | +})); |
| 78 | + |
| 79 | +const StyledBadge = styled(Badge)(({ theme }) => ({ |
| 80 | + padding: theme.spacing(0, 1), |
| 81 | +})); |
| 82 | + |
| 83 | +const getConnectionStatus = ({ |
| 84 | + reportedAt, |
| 85 | +}: ConnectedEdge): InstanceConnectionStatus => { |
| 86 | + const reportedTime = new Date(reportedAt).getTime(); |
| 87 | + const reportedSecondsAgo = (Date.now() - reportedTime) / 1000; |
| 88 | + |
| 89 | + if (reportedSecondsAgo > 360) return 'Disconnected'; |
| 90 | + if (reportedSecondsAgo > 180) return 'Stale'; |
| 91 | + |
| 92 | + return 'Connected'; |
| 93 | +}; |
| 94 | + |
| 95 | +const getCPUPercentage = ({ |
| 96 | + started, |
| 97 | + reportedAt, |
| 98 | + cpuUsage, |
| 99 | +}: ConnectedEdge): string => { |
| 100 | + const cpuUsageSeconds = Number(cpuUsage); |
| 101 | + if (!cpuUsageSeconds) return 'No usage'; |
| 102 | + |
| 103 | + const startedTimestamp = new Date(started).getTime(); |
| 104 | + const reportedTimestamp = new Date(reportedAt).getTime(); |
| 105 | + |
| 106 | + const totalRuntimeSeconds = (reportedTimestamp - startedTimestamp) / 1000; |
| 107 | + if (totalRuntimeSeconds === 0) return 'No usage'; |
| 108 | + |
| 109 | + return `${((cpuUsageSeconds / totalRuntimeSeconds) * 100).toFixed(2)} %`; |
| 110 | +}; |
| 111 | + |
| 112 | +const getMemory = ({ memoryUsage }: ConnectedEdge): string => { |
| 113 | + if (!memoryUsage) return 'No usage'; |
| 114 | + |
| 115 | + const units = ['B', 'KB', 'MB', 'GB']; |
| 116 | + let size = memoryUsage; |
| 117 | + let unitIndex = 0; |
| 118 | + |
| 119 | + while (size >= 1024 && unitIndex < units.length - 1) { |
| 120 | + size /= 1024; |
| 121 | + unitIndex++; |
| 122 | + } |
| 123 | + |
| 124 | + return `${size.toFixed(2)} ${units[unitIndex]}`; |
| 125 | +}; |
| 126 | + |
| 127 | +type InstanceConnectionStatus = 'Connected' | 'Stale' | 'Disconnected'; |
| 128 | + |
| 129 | +interface INetworkConnectedEdgeInstanceProps { |
| 130 | + instance: ConnectedEdge; |
| 131 | +} |
| 132 | + |
| 133 | +export const NetworkConnectedEdgeInstance = ({ |
| 134 | + instance, |
| 135 | +}: INetworkConnectedEdgeInstanceProps) => { |
| 136 | + const { locationSettings } = useLocationSettings(); |
| 137 | + |
| 138 | + const connectionStatus = getConnectionStatus(instance); |
| 139 | + const start = formatDateYMDHMS(instance.started, locationSettings?.locale); |
| 140 | + const lastReport = formatDateYMDHMS( |
| 141 | + instance.reportedAt, |
| 142 | + locationSettings?.locale, |
| 143 | + ); |
| 144 | + const cpuPercentage = getCPUPercentage(instance); |
| 145 | + const memory = getMemory(instance); |
| 146 | + const archWarning = cpuPercentage === 'No usage' && |
| 147 | + memory === 'No usage' && ( |
| 148 | + <p>Resource metrics are only available when running on Linux</p> |
| 149 | + ); |
| 150 | + |
| 151 | + return ( |
| 152 | + <StyledInstance> |
| 153 | + <StyledAccordion> |
| 154 | + <StyledAccordionSummary |
| 155 | + expandIcon={<ExpandMore />} |
| 156 | + connectionStatus={connectionStatus} |
| 157 | + > |
| 158 | + <Tooltip |
| 159 | + arrow |
| 160 | + title={`${connectionStatus}. Last reported: ${lastReport}`} |
| 161 | + > |
| 162 | + <CircleIcon /> |
| 163 | + </Tooltip> |
| 164 | + {instance.id || instance.instanceId} |
| 165 | + </StyledAccordionSummary> |
| 166 | + <StyledAccordionDetails> |
| 167 | + <StyledDetailRow> |
| 168 | + <strong>ID</strong> |
| 169 | + <span>{instance.instanceId}</span> |
| 170 | + </StyledDetailRow> |
| 171 | + <StyledDetailRow> |
| 172 | + <strong>Upstream</strong> |
| 173 | + <span>{instance.connectedVia || 'Unleash'}</span> |
| 174 | + </StyledDetailRow> |
| 175 | + <StyledDetailRow> |
| 176 | + <strong>Status</strong> |
| 177 | + <StyledBadge |
| 178 | + color={ |
| 179 | + connectionStatus === 'Disconnected' |
| 180 | + ? 'error' |
| 181 | + : connectionStatus === 'Stale' |
| 182 | + ? 'warning' |
| 183 | + : 'success' |
| 184 | + } |
| 185 | + > |
| 186 | + {connectionStatus} |
| 187 | + </StyledBadge> |
| 188 | + </StyledDetailRow> |
| 189 | + <StyledDetailRow> |
| 190 | + <strong>Start</strong> |
| 191 | + <span>{start}</span> |
| 192 | + </StyledDetailRow> |
| 193 | + <StyledDetailRow> |
| 194 | + <strong>Last report</strong> |
| 195 | + <span>{lastReport}</span> |
| 196 | + </StyledDetailRow> |
| 197 | + <StyledDetailRow> |
| 198 | + <strong>App name</strong> |
| 199 | + <span>{instance.appName}</span> |
| 200 | + </StyledDetailRow> |
| 201 | + <StyledDetailRow> |
| 202 | + <strong>Region</strong> |
| 203 | + <span>{instance.region || 'Unknown'}</span> |
| 204 | + </StyledDetailRow> |
| 205 | + <StyledDetailRow> |
| 206 | + <strong>Version</strong> |
| 207 | + <span>{instance.edgeVersion}</span> |
| 208 | + </StyledDetailRow> |
| 209 | + <StyledDetailRow> |
| 210 | + <strong>CPU</strong> |
| 211 | + <span> |
| 212 | + {cpuPercentage}{' '} |
| 213 | + <HelpIcon |
| 214 | + tooltip={ |
| 215 | + <> |
| 216 | + <p> |
| 217 | + CPU average usage since instance |
| 218 | + started |
| 219 | + </p> |
| 220 | + {archWarning} |
| 221 | + </> |
| 222 | + } |
| 223 | + size='16px' |
| 224 | + /> |
| 225 | + </span> |
| 226 | + </StyledDetailRow> |
| 227 | + <StyledDetailRow> |
| 228 | + <strong>Memory</strong> |
| 229 | + <span> |
| 230 | + {memory}{' '} |
| 231 | + <HelpIcon |
| 232 | + tooltip={ |
| 233 | + <> |
| 234 | + <p>Current memory usage</p> |
| 235 | + {archWarning} |
| 236 | + </> |
| 237 | + } |
| 238 | + size='16px' |
| 239 | + /> |
| 240 | + </span> |
| 241 | + </StyledDetailRow> |
| 242 | + <StyledDetailRow> |
| 243 | + <strong>Stream clients</strong> |
| 244 | + <span>{instance.connectedStreamingClients}</span> |
| 245 | + </StyledDetailRow> |
| 246 | + <StyledDetailRow> |
| 247 | + <NetworkConnectedEdgeInstanceLatency |
| 248 | + instance={instance} |
| 249 | + /> |
| 250 | + </StyledDetailRow> |
| 251 | + </StyledAccordionDetails> |
| 252 | + </StyledAccordion> |
| 253 | + </StyledInstance> |
| 254 | + ); |
| 255 | +}; |
0 commit comments