Просмотр исходного кода

[web] Update socket diagnostics: 2 columns, copy to clipboard (#23581)

* Use 2 columns for socket diagnostics

* Fixup time so it's never negative

* Add "Copy to clipboard" feature

GitOrigin-RevId: ba7bcc672287c2697afadd904ade315b4a39f954
Antoine Clausse 1 год назад
Родитель
Сommit
ed3442ada6

+ 147 - 140
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 <div>Network Information API not supported</div>
+    return ['Network Information API not supported']
   }
 
   const connection = navigator.connection as NetworkInformation
-  return (
-    <>
-      <div>Downlink: {connection.downlink} Mbps</div>
-      <div>Effective Type: {connection.effectiveType}</div>
-      <div>Round Trip Time: {connection.rtt} ms</div>
-      <div>Save Data: {connection.saveData ? 'Enabled' : 'Disabled'}</div>
-      <div>Platform: {navigator.platform}</div>
-      {/* @ts-ignore */}
-      <div>Device Memory: {navigator.deviceMemory}</div>
-      <div>Hardware Concurrency: {navigator.hardwareConcurrency}</div>
-    </>
-  )
+
+  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 => (
+    <DiagnosticItem
+      key={item.label}
+      icon={item.icon}
+      label={item.label}
+      value={item.text.map((t, i) => (
+        <div key={i}>{t}</div>
+      ))}
+      type={item.type}
+    />
+  ))
+  const cutAtIndex = 7
+  const leftItems = diagnosticItems.slice(0, cutAtIndex)
+  const rightItems = diagnosticItems.slice(cutAtIndex)
+
   return (
     <Container>
       <h1>Socket Diagnostics</h1>
       <ConnectionBadge state={getConnectionState()} />
-
-      <div className="d-flex gap-2 mt-3">
+      <div className="d-flex flex-wrap gap-4 mt-3 align-items-center">
         <ActionButton
           label="Reconnect"
           icon="refresh"
@@ -91,132 +195,35 @@ export const SocketDiagnostics = () => {
           onClick={disconnectSocket}
           disabled={!socketState.connected}
         />
+        <OLFormCheckbox
+          label="Auto ping"
+          id="autoping"
+          checked={autoping}
+          onChange={e => setAutoping(e.target.checked)}
+        />
       </div>
 
       {socketState.lastError && <ErrorAlert message={socketState.lastError} />}
 
       <div className="card p-4 mt-3">
-        <h3 className="text-lg">
-          <MaterialIcon type="speed" /> Connection Stats
-        </h3>
-        <div className="space-y-2">
-          <OLFormCheckbox
-            label="Auto ping"
-            id="autoping"
-            checked={autoping}
-            onChange={e => setAutoping(e.target.checked)}
-          />
-          <DiagnosticItem
-            icon="network_ping"
-            label="Ping Count"
-            value={
-              <>
-                {debugInfo.received} / {debugInfo.sent}
-                {lastReceivedS !== null && (
-                  <>
-                    <br />
-                    Last received {lastReceivedS}s ago
-                  </>
-                )}
-              </>
-            }
-            type={isLate === null ? undefined : isLate ? 'danger' : 'success'}
-          />
-
-          <DiagnosticItem
-            icon="schedule"
-            label="Latency"
-            value={
-              debugInfo.latency ? (
-                <>
-                  {debugInfo.latency} ms
-                  <br />
-                  Max: {debugInfo.maxLatency} ms
-                </>
-              ) : (
-                '-'
-              )
-            }
-            type={
-              debugInfo.latency
-                ? debugInfo.latency < 450
-                  ? 'success'
-                  : 'danger'
-                : undefined
-            }
-          />
-          <DiagnosticItem
-            icon="difference"
-            label="Clock Delta"
-            value={
-              debugInfo.clockDelta === null
-                ? '-'
-                : `${Math.round(debugInfo.clockDelta / 1000)}s`
-            }
-            type={
-              debugInfo.clockDelta !== null
-                ? Math.abs(debugInfo.clockDelta) < 1500
-                  ? 'success'
-                  : 'danger'
-                : undefined
-            }
-          />
-          <DiagnosticItem
-            icon="signal_cellular_alt"
-            label="Online"
-            value={debugInfo.onLine?.toString() ?? '-'}
-            type={debugInfo.onLine ? 'success' : 'danger'}
-          />
-
-          <DiagnosticItem
-            icon="schedule"
-            label="Current time"
-            value={now.toUTCString()}
-          />
-          <DiagnosticItem
-            icon="hourglass"
-            label="Connection time"
-            value={
-              debugInfo.client?.connectedAt ? (
-                <>
-                  {new Date(debugInfo.client.connectedAt).toUTCString()} (
-                  {Math.round(
-                    (Date.now() - debugInfo.client.connectedAt) / 1000
-                  )}
-                  s)
-                </>
-              ) : (
-                '-'
-              )
-            }
-          />
-          <DiagnosticItem
-            icon="local_shipping"
-            label="Transport"
-            value={socket?.socket.transport?.name ?? '-'}
-          />
-          <DiagnosticItem
-            icon="badge"
-            label="Client Public ID"
-            value={debugInfo.client?.publicId ?? '-'}
-          />
-          <DiagnosticItem
-            icon="pin"
-            label="IP Address"
-            value={debugInfo.client?.remoteIp ?? '-'}
-          />
-          <DiagnosticItem
-            icon="web"
-            label="User agent"
-            value={debugInfo.client?.userAgent ?? '-'}
-          />
-
-          <DiagnosticItem
-            icon="directions_boat"
-            label="Navigator info"
-            value={<NavigatorInfo />}
-          />
+        <div className="d-flex flex-wrap gap-4 row-gap-1 justify-content-between align-items-center">
+          <h3 className="text-lg">
+            <MaterialIcon type="speed" /> Connection Stats
+          </h3>
+          <div className="ms-auto">
+            <CopyToClipboard
+              content={diagnosticProps
+                .map(item => [`${item.label}:`, ...item.text].join('\n'))
+                .join('\n\n')}
+              tooltipId="copy-debug-info"
+              kind="text"
+            />
+          </div>
         </div>
+        <Row>
+          <Col md={6}>{leftItems}</Col>
+          <Col md={6}>{rightItems}</Col>
+        </Row>
       </div>
     </Container>
   )