diff --git a/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx b/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx index cbf16bcb4e..498a9ecc23 100644 --- a/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx +++ b/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx @@ -7,9 +7,10 @@ import { DiagnosticItem, ErrorAlert, } from './diagnostic-component' -import { Container } from 'react-bootstrap-5' +import { Col, Container, Row } from 'react-bootstrap-5' import MaterialIcon from '@/shared/components/material-icon' import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox' +import { CopyToClipboard } from '@/shared/components/copy-to-clipboard' type NetworkInformation = { downlink: number @@ -19,33 +20,38 @@ type NetworkInformation = { type: string } -const NavigatorInfo = () => { +const navigatorInfo = (): string[] => { if (!('connection' in navigator)) { - return
Network Information API not supported
+ return ['Network Information API not supported'] } const connection = navigator.connection as NetworkInformation - return ( - <> -
Downlink: {connection.downlink} Mbps
-
Effective Type: {connection.effectiveType}
-
Round Trip Time: {connection.rtt} ms
-
Save Data: {connection.saveData ? 'Enabled' : 'Disabled'}
-
Platform: {navigator.platform}
- {/* @ts-ignore */} -
Device Memory: {navigator.deviceMemory}
-
Hardware Concurrency: {navigator.hardwareConcurrency}
- - ) + + return [ + `Downlink: ${connection.downlink} Mbps`, + `Effective Type: ${connection.effectiveType}`, + `Round Trip Time: ${connection.rtt} ms`, + `Save Data: ${connection.saveData ? 'Enabled' : 'Disabled'}`, + `Platform: ${navigator.platform}`, + // @ts-ignore + `Device Memory: ${navigator.deviceMemory}`, + `Hardware Concurrency: ${navigator.hardwareConcurrency}`, + ] } const useCurrentTime = () => { - const [time, setTime] = React.useState(new Date()) + const [, setTime] = React.useState(new Date()) useEffect(() => { const interval = setInterval(() => setTime(new Date()), 1000) return () => clearInterval(interval) }, []) - return time +} + +type DiagnosticProps = { + icon: string + label: string + text: string[] + type?: 'success' | 'danger' } export const SocketDiagnostics = () => { @@ -58,7 +64,9 @@ export const SocketDiagnostics = () => { autoping, setAutoping, } = useSocketManager() - const now = useCurrentTime() + useCurrentTime() + + const now = new Date() const getConnectionState = (): ConnectionStatus => { if (socketState.connected) return 'connected' @@ -74,12 +82,108 @@ export const SocketDiagnostics = () => { !!debugInfo.unansweredSince && now.getTime() - debugInfo.unansweredSince >= 3000 + const diagnosticProps: DiagnosticProps[] = [ + { + icon: 'network_ping', + label: 'Ping Count', + text: [ + `${debugInfo.received} / ${debugInfo.sent}`, + lastReceivedS !== null ? `Last received ${lastReceivedS}s ago` : null, + ].filter(Boolean) as string[], + type: isLate === null ? undefined : isLate ? 'danger' : 'success', + }, + { + icon: 'schedule', + label: 'Latency', + text: [ + debugInfo.latency + ? `${debugInfo.latency} ms\nMax: ${debugInfo.maxLatency} ms` + : '-', + ], + type: debugInfo.latency && debugInfo.latency < 450 ? 'success' : 'danger', + }, + { + icon: 'difference', + label: 'Clock Delta', + text: [ + debugInfo.clockDelta === null + ? '-' + : `${Math.round(debugInfo.clockDelta / 1000)}s`, + ], + type: + debugInfo.clockDelta !== null && Math.abs(debugInfo.clockDelta) < 1500 + ? 'success' + : 'danger', + }, + { + icon: 'signal_cellular_alt', + label: 'Online', + text: [debugInfo.onLine?.toString() ?? '-'], + type: debugInfo.onLine ? 'success' : 'danger', + }, + { + icon: 'schedule', + label: 'Current time', + text: [now.toUTCString()], + }, + { + icon: 'hourglass', + label: 'Connection time', + text: [ + debugInfo.client?.connectedAt + ? `${new Date(debugInfo.client.connectedAt).toUTCString()} (${Math.round( + (Date.now() - debugInfo.client.connectedAt) / 1000 + )}s)` + : '-', + ], + }, + { + icon: 'local_shipping', + label: 'Transport', + text: [socket?.socket.transport?.name ?? '-'], + }, + { + icon: 'badge', + label: 'Client Public ID', + text: [debugInfo.client?.publicId ?? '-'], + }, + { + icon: 'pin', + label: 'IP Address', + text: [debugInfo.client?.remoteIp ?? '-'], + }, + { + icon: 'web', + label: 'User agent', + text: [debugInfo.client?.userAgent ?? '-'], + }, + { + icon: 'directions_boat', + label: 'Navigator info', + text: navigatorInfo(), + }, + ] + + const diagnosticItems = diagnosticProps.map(item => ( + ( +
{t}
+ ))} + type={item.type} + /> + )) + const cutAtIndex = 7 + const leftItems = diagnosticItems.slice(0, cutAtIndex) + const rightItems = diagnosticItems.slice(cutAtIndex) + return (

Socket Diagnostics

- -
+
{ onClick={disconnectSocket} disabled={!socketState.connected} /> + setAutoping(e.target.checked)} + />
{socketState.lastError && }
-

- Connection Stats -

-
- setAutoping(e.target.checked)} - /> - - {debugInfo.received} / {debugInfo.sent} - {lastReceivedS !== null && ( - <> -
- Last received {lastReceivedS}s ago - - )} - - } - type={isLate === null ? undefined : isLate ? 'danger' : 'success'} - /> - - - {debugInfo.latency} ms -
- Max: {debugInfo.maxLatency} ms - - ) : ( - '-' - ) - } - type={ - debugInfo.latency - ? debugInfo.latency < 450 - ? 'success' - : 'danger' - : undefined - } - /> - - - - - - {new Date(debugInfo.client.connectedAt).toUTCString()} ( - {Math.round( - (Date.now() - debugInfo.client.connectedAt) / 1000 - )} - s) - - ) : ( - '-' - ) - } - /> - - - - - - } - /> +
+

+ Connection Stats +

+
+ [`${item.label}:`, ...item.text].join('\n')) + .join('\n\n')} + tooltipId="copy-debug-info" + kind="text" + /> +
+ + {leftItems} + {rightItems} +
)