From 86774cf00dc424c220ad0b753de9a85a736bbdf4 Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Wed, 10 Apr 2024 08:47:33 -0700 Subject: [PATCH 01/35] wip --- lib/srv/desktop/tdp/proto.go | 14 +++ lib/web/desktop.go | 90 ++++++++++++++----- .../src/DesktopSession/DesktopSession.tsx | 20 +++++ .../teleport/src/DesktopSession/TopBar.tsx | 14 +++ web/packages/teleport/src/lib/tdp/client.ts | 9 ++ web/packages/teleport/src/lib/tdp/codec.ts | 21 +++++ 6 files changed, 148 insertions(+), 20 deletions(-) diff --git a/lib/srv/desktop/tdp/proto.go b/lib/srv/desktop/tdp/proto.go index e9258174071f2..71c72eca42803 100644 --- a/lib/srv/desktop/tdp/proto.go +++ b/lib/srv/desktop/tdp/proto.go @@ -82,6 +82,7 @@ const ( TypeSyncKeys = MessageType(32) TypeSharedDirectoryTruncateRequest = MessageType(33) TypeSharedDirectoryTruncateResponse = MessageType(34) + TypeLatencyStats = MessageType(35) ) // Message is a Go representation of a desktop protocol message. @@ -1623,6 +1624,19 @@ func decodeSharedDirectoryTruncateResponse(in io.Reader) (SharedDirectoryTruncat return res, err } +type LatencyStats struct { + BrowserLatency uint32 + DesktopLatency uint32 +} + +func (l LatencyStats) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteByte(byte(TypeLatencyStats)) + writeUint32(buf, l.BrowserLatency) + writeUint32(buf, l.DesktopLatency) + return buf.Bytes(), nil +} + // encodeString encodes strings for TDP. Strings are encoded as UTF-8 with // a 32-bit length prefix (in bytes): // https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#field-types diff --git a/lib/web/desktop.go b/lib/web/desktop.go index 8c471046c1a0a..a417d05237bf7 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -37,6 +37,7 @@ import ( "github.com/gorilla/websocket" "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" "github.com/julienschmidt/httprouter" "github.com/sirupsen/logrus" @@ -55,6 +56,7 @@ import ( "github.com/gravitational/teleport/lib/reversetunnelclient" "github.com/gravitational/teleport/lib/srv/desktop/tdp" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/diagnostics/latency" "github.com/gravitational/teleport/lib/web/scripts" ) @@ -202,8 +204,7 @@ func (h *Handler) createDesktopConnection( } // proxyWebsocketConn hangs here until connection is closed - handleProxyWebsocketConnErr( - proxyWebsocketConn(ws, serviceConnTLS), log) + handleProxyWebsocketConnErr(proxyWebsocketConn(ws, serviceConnTLS, log), log) return nil } @@ -431,18 +432,78 @@ func (c *connector) tryConnect(clusterName, desktopServiceID string) (conn net.C return conn, ver, trace.Wrap(err) } +// TODO(zmb3): combine with monitorSessionLatency or rename +func monitorDesktopLatency(ctx context.Context, ch chan<- tdp.Message, clock clockwork.Clock, ws *websocket.Conn) error { + wsPinger, err := latency.NewWebsocketPinger(clock, ws) + if err != nil { + return trace.Wrap(err, "creating websocket pinger") + } + + monitor, err := latency.NewMonitor(latency.MonitorConfig{ + Clock: clock, + ClientPinger: wsPinger, + ServerPinger: wsPinger, // TODO: don't forget to fix me + Reporter: latency.ReporterFunc(func(ctx context.Context, stats latency.Statistics) error { + println("!!! reporting latency") + ch <- tdp.LatencyStats{ + BrowserLatency: uint32(stats.Client), + DesktopLatency: uint32(stats.Server), + } + return nil + }), + }) + if err != nil { + return trace.Wrap(err, "creating latency monitor") + } + + monitor.Run(ctx) + return nil +} + // proxyWebsocketConn does a bidrectional copy between the websocket // connection to the browser (ws) and the mTLS connection to Windows // Desktop Serivce (wds) -func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn) error { +func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *logrus.Entry) error { + ctx, cancel := context.WithCancel(context.Background()) var closeOnce sync.Once close := func() { + cancel() ws.Close() wds.Close() } - errs := make(chan error, 2) + tdpMessagesToSend := make(chan tdp.Message) + errs := make(chan error, 3) + + go monitorDesktopLatency(ctx, tdpMessagesToSend, clockwork.NewRealClock(), ws) + + // run a goroutine to pick TDP messages up from a channel and send + // them to the browser + go func() { + for msg := range tdpMessagesToSend { + if ls, ok := msg.(tdp.LatencyStats); ok { + log.Infof("sending latency stats: %v / %v", ls.BrowserLatency, ls.DesktopLatency) + } + encoded, err := msg.Encode() + if err != nil { + errs <- err + return + } + + err = ws.WriteMessage(websocket.BinaryMessage, encoded) + if utils.IsOKNetworkError(err) { + errs <- nil + return + } + if err != nil { + errs <- err + return + } + } + }() + // run a second goroutine to read TDP messages from the Windows + // agent and write them to our send channel go func() { defer closeOnce.Do(close) @@ -467,7 +528,7 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn) error { if !isFatal { severity = tdp.SeverityWarning } - sendErr := sendTDPNotification(ws, err, severity) + sendErr := sendTDPNotification(ws, err, severity) // TODO // If the error wasn't fatal and we successfully // sent it back to the client, continue. @@ -484,23 +545,12 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn) error { errs <- err return } - encoded, err := msg.Encode() - if err != nil { - errs <- err - return - } - err = ws.WriteMessage(websocket.BinaryMessage, encoded) - if utils.IsOKNetworkError(err) { - errs <- nil - return - } - if err != nil { - errs <- err - return - } + tdpMessagesToSend <- msg } }() + // run a goroutine to read TDP messages coming from the browser + // and pass them on to the Windows agent go func() { defer closeOnce.Do(close) @@ -529,7 +579,7 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn) error { }() var retErrs []error - for i := 0; i < 2; i++ { + for i := 0; i < 3; i++ { retErrs = append(retErrs, <-errs) } return trace.NewAggregate(retErrs...) diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index a67cb1dfcddc2..eb7014686eef4 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -39,6 +39,7 @@ import TopBar from './TopBar'; import type { State, WebsocketAttempt } from './useDesktopSession'; import type { WebAuthnState } from 'teleport/lib/useWebAuthn'; +import { TdpClientEvent } from 'teleport/lib/tdp'; export function DesktopSessionContainer() { const state = useDesktopSession(); @@ -93,6 +94,24 @@ export function DesktopSession(props: State) { canvasState: { shouldConnect: false, shouldDisplay: false }, }); + const [latencyStats, setLatencyStats] = useState({ client: 0, server: 0 }); + useEffect(() => { + if (!tdpClient) { + return; + } + const setStats = stats => { + console.log('got latency', stats); + setLatencyStats({ + client: stats.browserLatency, + server: stats.desktopLatency, + }); + }; + tdpClient.on(TdpClientEvent.LATENCY_STATS, setStats); + return () => { + tdpClient.removeListener(TdpClientEvent.LATENCY_STATS, setStats); + }; + }, [tdpClient]); + // Calculate the next `ScreenState` whenever any of the constituent pieces of state change. useEffect(() => { setScreenState(prevState => @@ -116,6 +135,7 @@ export function DesktopSession(props: State) { return ( { setClipboardSharingState(prevState => ({ ...prevState, diff --git a/web/packages/teleport/src/DesktopSession/TopBar.tsx b/web/packages/teleport/src/DesktopSession/TopBar.tsx index b9cb9f8fa0701..7207690878a56 100644 --- a/web/packages/teleport/src/DesktopSession/TopBar.tsx +++ b/web/packages/teleport/src/DesktopSession/TopBar.tsx @@ -27,6 +27,7 @@ import ActionMenu from './ActionMenu'; import { WarningDropdown } from './WarningDropdown'; import type { NotificationItem } from 'shared/components/Notification'; +import { LatencyDiagnostic } from 'shared/components/LatencyDiagnostic'; export default function TopBar(props: Props) { const { @@ -38,6 +39,7 @@ export default function TopBar(props: Props) { onShareDirectory, warnings, onRemoveWarning, + latency, } = props; const theme = useTheme(); @@ -61,6 +63,14 @@ export default function TopBar(props: Props) { + + + + Date: Thu, 6 Mar 2025 00:13:27 +0100 Subject: [PATCH 02/35] add ping message and latency from desktop side --- lib/srv/desktop/rdp/rdpclient/client.go | 10 ++++++ lib/srv/desktop/tdp/proto.go | 24 +++++++++++++ lib/web/desktop.go | 48 ++++++++++++++++++++++--- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index 7b0e56f38fddb..59f6faceb7488 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -75,6 +75,7 @@ import ( "encoding/binary" "fmt" "log/slog" + "net" "os" "runtime/cgo" "sync" @@ -435,6 +436,15 @@ func (c *Client) startInputStreaming(stopCh chan struct{}) error { return err } + if m, ok := msg.(tdp.Ping); ok { + conn, err := net.Dial("tcp", c.cfg.Addr) + if err == nil { + conn.Close() + } + c.cfg.Conn.WriteMessage(m) + continue + } + if atomic.LoadUint32(&c.readyForInput) == 0 { switch m := msg.(type) { case tdp.ClientScreenSpec: diff --git a/lib/srv/desktop/tdp/proto.go b/lib/srv/desktop/tdp/proto.go index 8a0e10b9127f2..01a4e65f2bfb3 100644 --- a/lib/srv/desktop/tdp/proto.go +++ b/lib/srv/desktop/tdp/proto.go @@ -29,6 +29,7 @@ import ( "encoding/binary" "encoding/json" "errors" + "github.com/google/uuid" "image" "image/png" "io" @@ -83,6 +84,7 @@ const ( TypeSharedDirectoryTruncateRequest = MessageType(33) TypeSharedDirectoryTruncateResponse = MessageType(34) TypeLatencyStats = MessageType(35) + TypePing = MessageType(36) ) // Message is a Go representation of a desktop protocol message. @@ -183,6 +185,8 @@ func decodeMessage(firstByte byte, in byteReader) (Message, error) { return decodeSharedDirectoryTruncateRequest(in) case TypeSharedDirectoryTruncateResponse: return decodeSharedDirectoryTruncateResponse(in) + case TypePing: + return decodePing(in) default: return nil, trace.BadParameter("unsupported desktop protocol message type %d", firstByte) } @@ -1645,6 +1649,26 @@ func (l LatencyStats) Encode() ([]byte, error) { return buf.Bytes(), nil } +type Ping struct { + UUID uuid.UUID +} + +func (p Ping) Encode() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteByte(byte(TypePing)) + buf.Write(p.UUID[:]) + return buf.Bytes(), nil +} + +func decodePing(in io.Reader) (Ping, error) { + var ping Ping + _, err := io.ReadFull(in, ping.UUID[:]) + if err != nil { + return ping, trace.Wrap(err) + } + return ping, nil +} + // encodeString encodes strings for TDP. Strings are encoded as UTF-8 with // a 32-bit length prefix (in bytes): // https://github.com/gravitational/teleport/blob/master/rfd/0037-desktop-access-protocol.md#field-types diff --git a/lib/web/desktop.go b/lib/web/desktop.go index 7bd3f7454fa6c..cd2533698c13e 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -24,6 +24,7 @@ import ( "crypto" "crypto/tls" "errors" + "github.com/google/uuid" "io" "log/slog" "math/rand/v2" @@ -538,8 +539,37 @@ func (c *connector) tryConnect(ctx context.Context, clusterName, desktopServiceI return conn, ver, trace.Wrap(err) } +type desktopPinger struct { + wds net.Conn + ch <-chan tdp.Ping +} + +func (d desktopPinger) Ping(ctx context.Context) error { + ping := tdp.Ping{ + UUID: uuid.New(), + } + buf, err := ping.Encode() + if err != nil { + return trace.Wrap(err) + } + _, err = d.wds.Write(buf) + if err != nil { + return trace.Wrap(err) + } + for { + select { + case p := <-d.ch: + if p.UUID == ping.UUID { + return nil + } + case <-ctx.Done(): + return trace.Wrap(ctx.Err()) + } + } +} + // TODO(zmb3): combine with monitorSessionLatency or rename -func monitorDesktopLatency(ctx context.Context, ch chan<- tdp.Message, clock clockwork.Clock, ws *websocket.Conn) error { +func monitorDesktopLatency(ctx context.Context, ch chan<- tdp.Message, clock clockwork.Clock, ws *websocket.Conn, pinger desktopPinger) error { wsPinger, err := latency.NewWebsocketPinger(clock, ws) if err != nil { return trace.Wrap(err, "creating websocket pinger") @@ -548,9 +578,8 @@ func monitorDesktopLatency(ctx context.Context, ch chan<- tdp.Message, clock clo monitor, err := latency.NewMonitor(latency.MonitorConfig{ Clock: clock, ClientPinger: wsPinger, - ServerPinger: wsPinger, // TODO: don't forget to fix me + ServerPinger: pinger, Reporter: latency.ReporterFunc(func(ctx context.Context, stats latency.Statistics) error { - println("!!! reporting latency") ch <- tdp.LatencyStats{ BrowserLatency: uint32(stats.Client), DesktopLatency: uint32(stats.Server), @@ -581,12 +610,23 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger) erro tdpMessagesToSend := make(chan tdp.Message) errs := make(chan error, 3) - go monitorDesktopLatency(ctx, tdpMessagesToSend, clockwork.NewRealClock(), ws) + pings := make(chan tdp.Ping) + + pinger := desktopPinger{ + wds: wds, + ch: pings, + } + + go monitorDesktopLatency(ctx, tdpMessagesToSend, clockwork.NewRealClock(), ws, pinger) // run a goroutine to pick TDP messages up from a channel and send // them to the browser go func() { for msg := range tdpMessagesToSend { + if ping, ok := msg.(tdp.Ping); ok { + pings <- ping + continue + } if ls, ok := msg.(tdp.LatencyStats); ok { log.InfoContext(ctx, "sending latency stats: %v / %v", ls.BrowserLatency, ls.DesktopLatency) } From fa07c792ce86e9c5d507f875f743bbfc775bdd25 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Fri, 14 Mar 2025 21:04:27 +0100 Subject: [PATCH 03/35] version --- lib/web/desktop.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/web/desktop.go b/lib/web/desktop.go index cd2533698c13e..0cc9589af4a89 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -201,7 +201,7 @@ func (h *Handler) createDesktopConnection( clientSrcAddr: clientSrcAddr, clientDstAddr: clientDstAddr, } - serviceConn, _, err := c.connectToWindowsService(ctx, clusterName, validServiceIDs) + serviceConn, version, err := c.connectToWindowsService(ctx, clusterName, validServiceIDs) if err != nil { return sendTDPError(trace.Wrap(err, "cannot connect to Windows Desktop Service")) } @@ -240,7 +240,7 @@ func (h *Handler) createDesktopConnection( // proxyWebsocketConn hangs here until connection is closed handleProxyWebsocketConnErr( ctx, - proxyWebsocketConn(ws, serviceConnTLS, log), + proxyWebsocketConn(ws, serviceConnTLS, log, version), log, ) @@ -598,7 +598,7 @@ func monitorDesktopLatency(ctx context.Context, ch chan<- tdp.Message, clock clo // proxyWebsocketConn does a bidrectional copy between the websocket // connection to the browser (ws) and the mTLS connection to Windows // Desktop Serivce (wds) -func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger) error { +func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger, version string) error { ctx, cancel := context.WithCancel(context.Background()) var closeOnce sync.Once close := func() { From 236cbec53fd4fb2e997f415aa435685a737e90fa Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Sat, 15 Mar 2025 20:08:34 +0100 Subject: [PATCH 04/35] Fix backward compatibility --- lib/web/desktop.go | 17 +- .../LatencyDiagnostic/LatencyDiagnostic.tsx | 364 +++++++++--------- .../src/DesktopSession/DesktopSession.tsx | 2 +- .../teleport/src/DesktopSession/TopBar.tsx | 183 ++++----- 4 files changed, 287 insertions(+), 279 deletions(-) diff --git a/lib/web/desktop.go b/lib/web/desktop.go index 0cc9589af4a89..b6d27a9caa1ae 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -610,14 +610,21 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger, vers tdpMessagesToSend := make(chan tdp.Message) errs := make(chan error, 3) + latencySupported, err := utils.MinVerWithoutPreRelease(version, "18.0.0") + if err != nil { + return trace.Wrap(err) + } + pings := make(chan tdp.Ping) - pinger := desktopPinger{ - wds: wds, - ch: pings, - } + if latencySupported { + pinger := desktopPinger{ + wds: wds, + ch: pings, + } - go monitorDesktopLatency(ctx, tdpMessagesToSend, clockwork.NewRealClock(), ws, pinger) + go monitorDesktopLatency(ctx, tdpMessagesToSend, clockwork.NewRealClock(), ws, pinger) + } // run a goroutine to pick TDP messages up from a channel and send // them to the browser diff --git a/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx b/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx index 0d2256dd935ba..7702d47da0c28 100644 --- a/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx +++ b/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx @@ -19,227 +19,227 @@ import React from 'react'; import styled from 'styled-components'; -import { Flex, H2, Text } from 'design'; +import {Flex, H2, Text} from 'design'; import * as Icons from 'design/Icon'; -import { TeleportGearIcon } from 'design/SVGIcon'; -import { MenuIcon } from 'shared/components/MenuAction'; +import {TeleportGearIcon} from 'design/SVGIcon'; +import {MenuIcon} from 'shared/components/MenuAction'; -import { DocumentSsh } from 'teleport/Console/stores'; +import {DocumentSsh} from 'teleport/Console/stores'; export const WARN_THRESHOLD = 150; export const ERROR_THRESHOLD = 400; export enum LatencyColor { - Ok = 'dataVisualisation.tertiary.caribbean', - Warn = 'dataVisualisation.tertiary.abbey', - Error = 'dataVisualisation.tertiary.sunflower', - Unknown = 'text.muted', + Ok = 'dataVisualisation.tertiary.caribbean', + Warn = 'dataVisualisation.tertiary.abbey', + Error = 'dataVisualisation.tertiary.sunflower', + Unknown = 'text.muted', } function colorForLatency(l: number): LatencyColor { - if (l >= ERROR_THRESHOLD) { - return LatencyColor.Error; - } + if (l >= ERROR_THRESHOLD) { + return LatencyColor.Error; + } - if (l >= WARN_THRESHOLD) { - return LatencyColor.Warn; - } + if (l >= WARN_THRESHOLD) { + return LatencyColor.Warn; + } - return LatencyColor.Ok; + return LatencyColor.Ok; } // latencyColors determines the color to use for each leg of the connection // and the total measurement. export function latencyColors(latency: { client: number; server: number }): { - client: LatencyColor; - server: LatencyColor; - total: LatencyColor; + client: LatencyColor; + server: LatencyColor; + total: LatencyColor; } { - if (latency === undefined) { - return { - client: LatencyColor.Unknown, - server: LatencyColor.Unknown, - total: LatencyColor.Unknown, - }; - } - - const clientColor = colorForLatency(latency.client); - const serverColor = colorForLatency(latency.server); - - // any + red = red - if (latency.client >= ERROR_THRESHOLD || latency.server >= ERROR_THRESHOLD) { - return { - client: clientColor, - server: serverColor, - total: LatencyColor.Error, - }; - } - - // any + yellow = yellow - if (latency.client >= WARN_THRESHOLD || latency.server >= WARN_THRESHOLD) { - return { - client: clientColor, - server: serverColor, - total: LatencyColor.Warn, - }; - } - - // green + green = green - return { client: clientColor, server: serverColor, total: LatencyColor.Ok }; + if (latency === undefined) { + return { + client: LatencyColor.Unknown, + server: LatencyColor.Unknown, + total: LatencyColor.Unknown, + }; + } + + const clientColor = colorForLatency(latency.client); + const serverColor = colorForLatency(latency.server); + + // any + red = red + if (latency.client >= ERROR_THRESHOLD || latency.server >= ERROR_THRESHOLD) { + return { + client: clientColor, + server: serverColor, + total: LatencyColor.Error, + }; + } + + // any + yellow = yellow + if (latency.client >= WARN_THRESHOLD || latency.server >= WARN_THRESHOLD) { + return { + client: clientColor, + server: serverColor, + total: LatencyColor.Warn, + }; + } + + // green + green = green + return {client: clientColor, server: serverColor, total: LatencyColor.Ok}; } export function LatencyDiagnostic({ - latency, -}: { - latency: DocumentSsh['latency']; + latency, + }: { + latency: DocumentSsh['latency']; }) { - const colors = latencyColors(latency); - - return ( - - - -

Network Connection

- - - } - text="You" - alignItems="flex-start" - /> - - } - text="Teleport" - alignItems="center" - /> - - } - text="Server" - alignItems="flex-end" - /> - - - - - {latency === undefined && ( - - Connecting - - )} - - {latency !== undefined && colors.total === LatencyColor.Error && ( - - )} - - {latency !== undefined && colors.total === LatencyColor.Warn && ( - - )} - - {latency !== undefined && colors.total === LatencyColor.Ok && ( - - )} - - {latency !== undefined && ( - - Total Latency: {latency.client + latency.server}ms - - )} - - -
-
-
- ); + const colors = latencyColors(latency); + + return ( + + + +

Network Connection

+ + + } + text="You" + alignItems="flex-start" + /> + + } + text="Teleport" + alignItems="center" + /> + + } + text="Server" + alignItems="flex-end" + /> + + + + + {latency === undefined && ( + + Connecting + + )} + + {latency !== undefined && colors.total === LatencyColor.Error && ( + + )} + + {latency !== undefined && colors.total === LatencyColor.Warn && ( + + )} + + {latency !== undefined && colors.total === LatencyColor.Ok && ( + + )} + + {latency !== undefined && ( + + Total Latency: {latency.client + latency.server}ms + + )} + + +
+
+
+ ); } const IconContainer: React.FC<{ - icon: JSX.Element; - text: string; - alignItems: 'flex-start' | 'center' | 'flex-end'; -}> = ({ icon, text, alignItems }) => ( - - {icon} - {text} - + icon: JSX.Element; + text: string; + alignItems: 'flex-start' | 'center' | 'flex-end'; +}> = ({icon, text, alignItems}) => ( + + {icon} + {text} + ); const Leg: React.FC<{ - color: LatencyColor; - latency: number | undefined; -}> = ({ color, latency }) => ( - - - {latency !== undefined && {latency}ms} - {latency === undefined && } - -); - -// Looks like `<----->` -const DoubleSidedArrow = () => { - return ( + color: LatencyColor; + latency: number | undefined; +}> = ({color, latency}) => ( - - - + > + + {latency !== undefined && {latency}ms} + {latency === undefined && } - ); +); + +// Looks like `<----->` +const DoubleSidedArrow = () => { + return ( + + + + + + ); }; const Container = styled.div` - background: ${props => props.theme.colors.levels.elevated}; - padding: ${props => props.theme.space[4]}px; - width: 370px; + background: ${props => props.theme.colors.levels.elevated}; + padding: ${props => props.theme.space[4]}px; + width: 370px; `; const Line = styled.div` - color: ${props => props.theme.colors.text.muted}; - border: 0.5px dashed; - width: 100%; + color: ${props => props.theme.colors.text.muted}; + border: 0.5px dashed; + width: 100%; `; const Placeholder = styled.div` - height: 24px; + height: 24px; `; diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index 6448110e9bfbd..0ec986d1f4077 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -180,7 +180,7 @@ export function DesktopSession(props: State) { } }, [client, shouldConnect]); - const [latencyStats, setLatencyStats] = useState({client: 0, server: 0}); + const [latencyStats, setLatencyStats] = useState(undefined); useEffect(() => { if (!client) { return; diff --git a/web/packages/teleport/src/DesktopSession/TopBar.tsx b/web/packages/teleport/src/DesktopSession/TopBar.tsx index 40917648c0a1a..a7b491f457ca7 100644 --- a/web/packages/teleport/src/DesktopSession/TopBar.tsx +++ b/web/packages/teleport/src/DesktopSession/TopBar.tsx @@ -16,114 +16,115 @@ * along with this program. If not, see . */ -import { useTheme } from 'styled-components'; +import {useTheme} from 'styled-components'; -import { Flex, Text, TopNav } from 'design'; -import { Clipboard, FolderShared } from 'design/Icon'; -import { HoverTooltip } from 'design/Tooltip'; -import type { NotificationItem } from 'shared/components/Notification'; -import { LatencyDiagnostic } from 'shared/components/LatencyDiagnostic'; +import {Flex, Text, TopNav} from 'design'; +import {Clipboard, FolderShared} from 'design/Icon'; +import {HoverTooltip} from 'design/Tooltip'; +import type {NotificationItem} from 'shared/components/Notification'; +import {LatencyDiagnostic} from 'shared/components/LatencyDiagnostic'; import ActionMenu from './ActionMenu'; -import { AlertDropdown } from './AlertDropdown'; +import {AlertDropdown} from './AlertDropdown'; export default function TopBar(props: Props) { - const { - userHost, - isSharingClipboard, - clipboardSharingMessage, - onDisconnect, - canShareDirectory, - isSharingDirectory, - onShareDirectory, - onCtrlAltDel, - alerts, - onRemoveAlert, - latency - } = props; - const theme = useTheme(); + const { + userHost, + isSharingClipboard, + clipboardSharingMessage, + onDisconnect, + canShareDirectory, + isSharingDirectory, + onShareDirectory, + onCtrlAltDel, + alerts, + onRemoveAlert, + latency + } = props; + const theme = useTheme(); - const primaryOnTrue = (b: boolean): any => { - return { - color: b ? theme.colors.text.main : theme.colors.text.disabled, + const primaryOnTrue = (b: boolean): any => { + return { + color: b ? theme.colors.text.main : theme.colors.text.disabled, + }; }; - }; - return ( - - - {userHost} - + return ( + + + {userHost} + - - - - - + + + {latency && + + + } - - - - - - - - - - - - ); + + + + + + + +
+ +
+ + ); } function directorySharingToolTip( - canShare: boolean, - isSharing: boolean + canShare: boolean, + isSharing: boolean ): string { - if (!canShare) { - return 'Directory Sharing Disabled'; - } - if (!isSharing) { - return 'Directory Sharing Inactive'; - } - return 'Directory Sharing Enabled'; + if (!canShare) { + return 'Directory Sharing Disabled'; + } + if (!isSharing) { + return 'Directory Sharing Inactive'; + } + return 'Directory Sharing Enabled'; } export const TopBarHeight = 40; type Props = { - userHost: string; - isSharingClipboard: boolean; - clipboardSharingMessage: string; - canShareDirectory: boolean; - isSharingDirectory: boolean; - onDisconnect: VoidFunction; - onShareDirectory: VoidFunction; - onCtrlAltDel: VoidFunction; - alerts: NotificationItem[]; - onRemoveAlert(id: string): void; - latency: { - client: number; - server: number; - }; + userHost: string; + isSharingClipboard: boolean; + clipboardSharingMessage: string; + canShareDirectory: boolean; + isSharingDirectory: boolean; + onDisconnect: VoidFunction; + onShareDirectory: VoidFunction; + onCtrlAltDel: VoidFunction; + alerts: NotificationItem[]; + onRemoveAlert(id: string): void; + latency: { + client: number; + server: number; + }; }; From cd45be896ac1a02a8fbd410d4b7651a0450fc4c7 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 17 Mar 2025 21:46:09 +0100 Subject: [PATCH 05/35] godocs --- lib/srv/desktop/tdp/proto.go | 5 +++++ lib/web/desktop.go | 2 ++ 2 files changed, 7 insertions(+) diff --git a/lib/srv/desktop/tdp/proto.go b/lib/srv/desktop/tdp/proto.go index 01a4e65f2bfb3..1265b3231743f 100644 --- a/lib/srv/desktop/tdp/proto.go +++ b/lib/srv/desktop/tdp/proto.go @@ -1636,6 +1636,7 @@ func decodeSharedDirectoryTruncateResponse(in io.Reader) (SharedDirectoryTruncat return res, err } +// LatencyStats is used to report the latency of the connection(s) to the web UI. type LatencyStats struct { BrowserLatency uint32 DesktopLatency uint32 @@ -1649,7 +1650,11 @@ func (l LatencyStats) Encode() ([]byte, error) { return buf.Bytes(), nil } +// Ping is used to measure the latency of the connection(s) between proxy and desktop (includes +// latency between proxy and Windows Desktop Service and between WDS and desktop). type Ping struct { + + // UUID is used to correlate message send by proxy and received from the Windows Desktop Service UUID uuid.UUID } diff --git a/lib/web/desktop.go b/lib/web/desktop.go index b6d27a9caa1ae..b23ebbdfe689b 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -539,6 +539,8 @@ func (c *connector) tryConnect(ctx context.Context, clusterName, desktopServiceI return conn, ver, trace.Wrap(err) } +// desktopPinger measures latency between proxy and the desktop by sending tdp.Ping messages +// Windows Desktop Service and measuring the time it takes to receive message with the same UUID back. type desktopPinger struct { wds net.Conn ch <-chan tdp.Ping From c0f2dfb10879575db2b39e06f2405a82af8b1948 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 17 Mar 2025 21:54:21 +0100 Subject: [PATCH 06/35] formatting --- .../src/DesktopSession/DesktopSession.tsx | 953 +++++++++--------- 1 file changed, 476 insertions(+), 477 deletions(-) diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index 0ec986d1f4077..b9011dad6e86a 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -16,169 +16,169 @@ * along with this program. If not, see . */ -import {useCallback, useEffect, useRef, useState} from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; -import {Box, ButtonPrimary, ButtonSecondary, Flex, Indicator} from 'design'; -import {Info} from 'design/Alert'; +import { Box, ButtonPrimary, ButtonSecondary, Flex, Indicator } from 'design'; +import { Info } from 'design/Alert'; import Dialog, { - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, } from 'design/Dialog'; -import {Attempt} from 'shared/hooks/useAttemptNext'; +import { Attempt } from 'shared/hooks/useAttemptNext'; import AuthnDialog from 'teleport/components/AuthnDialog'; import TdpClientCanvas from 'teleport/components/TdpClientCanvas'; -import {TdpClientCanvasRef} from 'teleport/components/TdpClientCanvas/TdpClientCanvas'; -import {TdpClientEvent} from 'teleport/lib/tdp'; -import {BitmapFrame} from 'teleport/lib/tdp/client'; -import {ClientScreenSpec, PngFrame} from 'teleport/lib/tdp/codec'; -import type {MfaState} from 'teleport/lib/useMfa'; +import { TdpClientCanvasRef } from 'teleport/components/TdpClientCanvas/TdpClientCanvas'; +import { TdpClientEvent } from 'teleport/lib/tdp'; +import { BitmapFrame } from 'teleport/lib/tdp/client'; +import { ClientScreenSpec, PngFrame } from 'teleport/lib/tdp/codec'; +import type { MfaState } from 'teleport/lib/useMfa'; import TopBar from './TopBar'; import useDesktopSession, { - clipboardSharingMessage, - directorySharingPossible, - isSharingClipboard, - isSharingDirectory, - type State, - type WebsocketAttempt, + clipboardSharingMessage, + directorySharingPossible, + isSharingClipboard, + isSharingDirectory, + type State, + type WebsocketAttempt, } from './useDesktopSession'; export function DesktopSessionContainer() { - const state = useDesktopSession(); - return ; + const state = useDesktopSession(); + return ; } declare global { - interface Window { - showDirectoryPicker: () => Promise; - } + interface Window { + showDirectoryPicker: () => Promise; + } } export function DesktopSession(props: State) { - const { - mfa, - tdpClient: client, - username, - hostname, - directorySharingState, - setDirectorySharingState, - setInitialTdpConnectionSucceeded, - clientOnClipboardData, - clientOnTdpError, - clientOnTdpWarning, - clientOnTdpInfo, - clientOnWsClose, - clientOnWsOpen, - canvasOnKeyDown, - canvasOnKeyUp, - canvasOnFocusOut, - canvasOnMouseMove, - canvasOnMouseDown, - canvasOnMouseUp, - canvasOnMouseWheelScroll, - canvasOnContextMenu, - onResize, - clientScreenSpecToRequest, - clipboardSharingState, - setClipboardSharingState, - onShareDirectory, - onCtrlAltDel, - alerts, - onRemoveAlert, - fetchAttempt, - tdpConnection, - wsConnection, - showAnotherSessionActiveDialog, - } = props; - - const [screenState, setScreenState] = useState({ - screen: 'processing', - canvasState: {shouldConnect: false, shouldDisplay: false}, - }); - - useEffect(() => { - if (client && clientOnClipboardData) { - client.on(TdpClientEvent.TDP_CLIPBOARD_DATA, clientOnClipboardData); - - return () => { - client.removeListener( - TdpClientEvent.TDP_CLIPBOARD_DATA, - clientOnClipboardData - ); - }; - } - }, [client, clientOnClipboardData]); - - useEffect(() => { - if (client && clientOnTdpError) { - client.on(TdpClientEvent.TDP_ERROR, clientOnTdpError); - client.on(TdpClientEvent.CLIENT_ERROR, clientOnTdpError); - - return () => { - client.removeListener(TdpClientEvent.TDP_ERROR, clientOnTdpError); - client.removeListener(TdpClientEvent.CLIENT_ERROR, clientOnTdpError); - }; - } - }, [client, clientOnTdpError]); + const { + mfa, + tdpClient: client, + username, + hostname, + directorySharingState, + setDirectorySharingState, + setInitialTdpConnectionSucceeded, + clientOnClipboardData, + clientOnTdpError, + clientOnTdpWarning, + clientOnTdpInfo, + clientOnWsClose, + clientOnWsOpen, + canvasOnKeyDown, + canvasOnKeyUp, + canvasOnFocusOut, + canvasOnMouseMove, + canvasOnMouseDown, + canvasOnMouseUp, + canvasOnMouseWheelScroll, + canvasOnContextMenu, + onResize, + clientScreenSpecToRequest, + clipboardSharingState, + setClipboardSharingState, + onShareDirectory, + onCtrlAltDel, + alerts, + onRemoveAlert, + fetchAttempt, + tdpConnection, + wsConnection, + showAnotherSessionActiveDialog, + } = props; + + const [screenState, setScreenState] = useState({ + screen: 'processing', + canvasState: { shouldConnect: false, shouldDisplay: false }, + }); + + useEffect(() => { + if (client && clientOnClipboardData) { + client.on(TdpClientEvent.TDP_CLIPBOARD_DATA, clientOnClipboardData); + + return () => { + client.removeListener( + TdpClientEvent.TDP_CLIPBOARD_DATA, + clientOnClipboardData + ); + }; + } + }, [client, clientOnClipboardData]); - useEffect(() => { - if (client && clientOnTdpWarning) { - client.on(TdpClientEvent.TDP_WARNING, clientOnTdpWarning); - client.on(TdpClientEvent.CLIENT_WARNING, clientOnTdpWarning); - - return () => { - client.removeListener(TdpClientEvent.TDP_WARNING, clientOnTdpWarning); - client.removeListener( - TdpClientEvent.CLIENT_WARNING, - clientOnTdpWarning - ); - }; - } - }, [client, clientOnTdpWarning]); + useEffect(() => { + if (client && clientOnTdpError) { + client.on(TdpClientEvent.TDP_ERROR, clientOnTdpError); + client.on(TdpClientEvent.CLIENT_ERROR, clientOnTdpError); - useEffect(() => { - if (client && clientOnTdpInfo) { - client.on(TdpClientEvent.TDP_INFO, clientOnTdpInfo); + return () => { + client.removeListener(TdpClientEvent.TDP_ERROR, clientOnTdpError); + client.removeListener(TdpClientEvent.CLIENT_ERROR, clientOnTdpError); + }; + } + }, [client, clientOnTdpError]); + + useEffect(() => { + if (client && clientOnTdpWarning) { + client.on(TdpClientEvent.TDP_WARNING, clientOnTdpWarning); + client.on(TdpClientEvent.CLIENT_WARNING, clientOnTdpWarning); + + return () => { + client.removeListener(TdpClientEvent.TDP_WARNING, clientOnTdpWarning); + client.removeListener( + TdpClientEvent.CLIENT_WARNING, + clientOnTdpWarning + ); + }; + } + }, [client, clientOnTdpWarning]); - return () => { - client.removeListener(TdpClientEvent.TDP_INFO, clientOnTdpInfo); - }; - } - }, [client, clientOnTdpInfo]); + useEffect(() => { + if (client && clientOnTdpInfo) { + client.on(TdpClientEvent.TDP_INFO, clientOnTdpInfo); - useEffect(() => { - if (client && clientOnWsClose) { - client.on(TdpClientEvent.WS_CLOSE, clientOnWsClose); + return () => { + client.removeListener(TdpClientEvent.TDP_INFO, clientOnTdpInfo); + }; + } + }, [client, clientOnTdpInfo]); - return () => { - client.removeListener(TdpClientEvent.WS_CLOSE, clientOnWsClose); - }; - } - }, [client, clientOnWsClose]); + useEffect(() => { + if (client && clientOnWsClose) { + client.on(TdpClientEvent.WS_CLOSE, clientOnWsClose); - useEffect(() => { - if (client && clientOnWsOpen) { - client.on(TdpClientEvent.WS_OPEN, clientOnWsOpen); + return () => { + client.removeListener(TdpClientEvent.WS_CLOSE, clientOnWsClose); + }; + } + }, [client, clientOnWsClose]); - return () => { - client.removeListener(TdpClientEvent.WS_OPEN, clientOnWsOpen); - }; - } - }, [client, clientOnWsOpen]); + useEffect(() => { + if (client && clientOnWsOpen) { + client.on(TdpClientEvent.WS_OPEN, clientOnWsOpen); - const {shouldConnect} = screenState.canvasState; - // Call connect after all listeners have been registered - useEffect(() => { - if (client && shouldConnect) { - client.connect(clientScreenSpecToRequest); - return () => { - client.shutdown(); - }; - } - }, [client, shouldConnect]); + return () => { + client.removeListener(TdpClientEvent.WS_OPEN, clientOnWsOpen); + }; + } + }, [client, clientOnWsOpen]); + + const { shouldConnect } = screenState.canvasState; + // Call connect after all listeners have been registered + useEffect(() => { + if (client && shouldConnect) { + client.connect(clientScreenSpecToRequest); + return () => { + client.shutdown(); + }; + } + }, [client, shouldConnect]); const [latencyStats, setLatencyStats] = useState(undefined); useEffect(() => { @@ -198,252 +198,251 @@ export function DesktopSession(props: State) { }; }, [client]); - // Calculate the next `ScreenState` whenever any of the constituent pieces of state change. - useEffect(() => { - setScreenState(prevState => - nextScreenState( - prevState, - fetchAttempt, - tdpConnection, - wsConnection, - showAnotherSessionActiveDialog, - mfa - ) - ); - }, [ + // Calculate the next `ScreenState` whenever any of the constituent pieces of state change. + useEffect(() => { + setScreenState(prevState => + nextScreenState( + prevState, fetchAttempt, tdpConnection, wsConnection, showAnotherSessionActiveDialog, - mfa, - ]); - - const tdpClientCanvasRef = useRef(null); - - useEffect(() => { - if (!client) { - return; - } - const setPointer = tdpClientCanvasRef.current?.setPointer; - client.addListener(TdpClientEvent.POINTER, setPointer); - - return () => { - client.removeListener(TdpClientEvent.POINTER, setPointer); - }; - }, [client]); - - const onInitialTdpConnectionSucceeded = useCallback(() => { - setInitialTdpConnectionSucceeded(() => { - // TODO(gzdunek): This callback is a temporary fix for focusing the canvas. - // Focus the canvas once we start rendering frames. - // The timeout it a small hack, we should verify - // what is the earliest moment we can focus the canvas. - setTimeout(() => { - tdpClientCanvasRef.current?.focus(); - }, 100); - }); - }, [setInitialTdpConnectionSucceeded]); - - useEffect(() => { - if (!client) { - return; - } - const renderFrame = (frame: PngFrame) => { - onInitialTdpConnectionSucceeded(); - tdpClientCanvasRef.current?.renderPngFrame(frame); - }; - client.addListener(TdpClientEvent.TDP_PNG_FRAME, renderFrame); + mfa + ) + ); + }, [ + fetchAttempt, + tdpConnection, + wsConnection, + showAnotherSessionActiveDialog, + mfa, + ]); + + const tdpClientCanvasRef = useRef(null); + + useEffect(() => { + if (!client) { + return; + } + const setPointer = tdpClientCanvasRef.current?.setPointer; + client.addListener(TdpClientEvent.POINTER, setPointer); - return () => { - client.removeListener(TdpClientEvent.TDP_PNG_FRAME, renderFrame); - }; - }, [client, onInitialTdpConnectionSucceeded]); + return () => { + client.removeListener(TdpClientEvent.POINTER, setPointer); + }; + }, [client]); + + const onInitialTdpConnectionSucceeded = useCallback(() => { + setInitialTdpConnectionSucceeded(() => { + // TODO(gzdunek): This callback is a temporary fix for focusing the canvas. + // Focus the canvas once we start rendering frames. + // The timeout it a small hack, we should verify + // what is the earliest moment we can focus the canvas. + setTimeout(() => { + tdpClientCanvasRef.current?.focus(); + }, 100); + }); + }, [setInitialTdpConnectionSucceeded]); - useEffect(() => { - if (!client) { - return; - } - const renderFrame = (frame: BitmapFrame) => { - onInitialTdpConnectionSucceeded(); - tdpClientCanvasRef.current?.renderBitmapFrame(frame); - }; - client.addListener(TdpClientEvent.TDP_BMP_FRAME, renderFrame); + useEffect(() => { + if (!client) { + return; + } + const renderFrame = (frame: PngFrame) => { + onInitialTdpConnectionSucceeded(); + tdpClientCanvasRef.current?.renderPngFrame(frame); + }; + client.addListener(TdpClientEvent.TDP_PNG_FRAME, renderFrame); - return () => { - client.removeListener(TdpClientEvent.TDP_BMP_FRAME, renderFrame); - }; - }, [client, onInitialTdpConnectionSucceeded]); + return () => { + client.removeListener(TdpClientEvent.TDP_PNG_FRAME, renderFrame); + }; + }, [client, onInitialTdpConnectionSucceeded]); - useEffect(() => { - if (!client) { - return; - } - const clear = () => tdpClientCanvasRef.current?.clear(); - client.addListener(TdpClientEvent.RESET, clear); + useEffect(() => { + if (!client) { + return; + } + const renderFrame = (frame: BitmapFrame) => { + onInitialTdpConnectionSucceeded(); + tdpClientCanvasRef.current?.renderBitmapFrame(frame); + }; + client.addListener(TdpClientEvent.TDP_BMP_FRAME, renderFrame); - return () => { - client.removeListener(TdpClientEvent.RESET, clear); - }; - }, [client]); + return () => { + client.removeListener(TdpClientEvent.TDP_BMP_FRAME, renderFrame); + }; + }, [client, onInitialTdpConnectionSucceeded]); - useEffect(() => { - if (!client) { - return; - } - const setResolution = (spec: ClientScreenSpec) => - tdpClientCanvasRef.current?.setResolution(spec); - client.addListener(TdpClientEvent.TDP_CLIENT_SCREEN_SPEC, setResolution); + useEffect(() => { + if (!client) { + return; + } + const clear = () => tdpClientCanvasRef.current?.clear(); + client.addListener(TdpClientEvent.RESET, clear); - return () => { - client.removeListener( - TdpClientEvent.TDP_CLIENT_SCREEN_SPEC, - setResolution - ); - }; - }, [client]); + return () => { + client.removeListener(TdpClientEvent.RESET, clear); + }; + }, [client]); - return ( - - { - setClipboardSharingState(prevState => ({ - ...prevState, - isSharing: false, - })); - setDirectorySharingState(prevState => ({ - ...prevState, - isSharing: false, - })); - client.shutdown(); - }} - userHost={`${username}@${hostname}`} - canShareDirectory={directorySharingPossible(directorySharingState)} - isSharingDirectory={isSharingDirectory(directorySharingState)} - isSharingClipboard={isSharingClipboard(clipboardSharingState)} - clipboardSharingMessage={clipboardSharingMessage(clipboardSharingState)} - onShareDirectory={onShareDirectory} - onCtrlAltDel={onCtrlAltDel} - alerts={alerts} - onRemoveAlert={onRemoveAlert} - /> - - {screenState.screen === 'anotherSessionActive' && ( - - )} - {screenState.screen === 'mfa' && } - {screenState.screen === 'alert dialog' && ( - - )} - {screenState.screen === 'processing' && } - - - - ); + useEffect(() => { + if (!client) { + return; + } + const setResolution = (spec: ClientScreenSpec) => + tdpClientCanvasRef.current?.setResolution(spec); + client.addListener(TdpClientEvent.TDP_CLIENT_SCREEN_SPEC, setResolution); + + return () => { + client.removeListener( + TdpClientEvent.TDP_CLIENT_SCREEN_SPEC, + setResolution + ); + }; + }, [client]); + + return ( + + { + setClipboardSharingState(prevState => ({ + ...prevState, + isSharing: false, + })); + setDirectorySharingState(prevState => ({ + ...prevState, + isSharing: false, + })); + client.shutdown(); + }} + userHost={`${username}@${hostname}`} + canShareDirectory={directorySharingPossible(directorySharingState)} + isSharingDirectory={isSharingDirectory(directorySharingState)} + isSharingClipboard={isSharingClipboard(clipboardSharingState)} + clipboardSharingMessage={clipboardSharingMessage(clipboardSharingState)} + onShareDirectory={onShareDirectory} + onCtrlAltDel={onCtrlAltDel} + alerts={alerts} + onRemoveAlert={onRemoveAlert} + /> + + {screenState.screen === 'anotherSessionActive' && ( + + )} + {screenState.screen === 'mfa' && } + {screenState.screen === 'alert dialog' && ( + + )} + {screenState.screen === 'processing' && } + + + + ); } -const MfaDialog = ({mfa}: { mfa: MfaState }) => { - return ( - - ); +const MfaDialog = ({ mfa }: { mfa: MfaState }) => { + return ( + + ); }; -const AlertDialog = ({screenState}: { screenState: ScreenState }) => ( - ({width: '484px'})} open={true}> - - Disconnected - - - <> - {screenState.alertMessage || invalidStateMessage}} - /> - Refresh the page to reconnect. - - - - { - window.location.reload(); - }} - > - Refresh - - - +const AlertDialog = ({ screenState }: { screenState: ScreenState }) => ( + ({ width: '484px' })} open={true}> + + Disconnected + + + <> + {screenState.alertMessage || invalidStateMessage}} + /> + Refresh the page to reconnect. + + + + { + window.location.reload(); + }} + > + Refresh + + + ); const AnotherSessionActiveDialog = (props: State) => { - return ( - ({width: '484px'})} - onClose={() => { - }} - open={true} + return ( + ({ width: '484px' })} + onClose={() => {}} + open={true} + > + + Another Session Is Active + + + This desktop has an active session, connecting to it may close the other + session. Do you wish to continue? + + + { + window.close(); + }} > - - Another Session Is Active - - - This desktop has an active session, connecting to it may close the other - session. Do you wish to continue? - - - { - window.close(); - }} - > - Abort - - { - props.setShowAnotherSessionActiveDialog(false); - }} - > - Continue - - - - ); + Abort + + { + props.setShowAnotherSessionActiveDialog(false); + }} + > + Continue + + + + ); }; const Processing = () => { - return ( - - - - ); + return ( + + + + ); }; const invalidStateMessage = 'internal application error'; @@ -454,86 +453,86 @@ const invalidStateMessage = 'internal application error'; * to the websocket. */ const nextScreenState = ( - prevState: ScreenState, - fetchAttempt: Attempt, - tdpConnection: Attempt, - wsConnection: WebsocketAttempt, - showAnotherSessionActiveDialog: boolean, - webauthn: MfaState + prevState: ScreenState, + fetchAttempt: Attempt, + tdpConnection: Attempt, + wsConnection: WebsocketAttempt, + showAnotherSessionActiveDialog: boolean, + webauthn: MfaState ): ScreenState => { - // We always want to show the user the first alert that caused the session to fail/end, - // so if we're already showing an alert, don't change the screen. + // We always want to show the user the first alert that caused the session to fail/end, + // so if we're already showing an alert, don't change the screen. + // + // This allows us to track the various pieces of the state independently and always display + // the vital information to the user. For example, we can track the TDP connection status + // and the websocket connection status separately throughout the codebase. If the TDP connection + // fails, and then the websocket closes, we want to show the `tdpConnection.statusText` to the user, + // not the `wsConnection.statusText`. But if the websocket closes unexpectedly before a TDP message telling + // us why, we want to show the websocket closing message to the user. + if (prevState.screen === 'alert dialog') { + return prevState; + } + + // Otherwise, calculate a new screen state. + const showAnotherSessionActive = showAnotherSessionActiveDialog; + const showMfa = webauthn.challenge; + const showAlert = + fetchAttempt.status === 'failed' || // Fetch attempt failed + tdpConnection.status === 'failed' || // TDP connection failed + tdpConnection.status === '' || // TDP connection ended gracefully server-side + wsConnection.status === 'closed'; // Websocket closed (could mean client side graceful close or unexpected close, the message will tell us which) + + const atLeastOneAttemptProcessing = + fetchAttempt.status === 'processing' || + tdpConnection.status === 'processing'; + const noDialogs = !(showMfa || showAnotherSessionActive || showAlert); + const showProcessing = atLeastOneAttemptProcessing && noDialogs; + + if (showAnotherSessionActive) { + // Highest priority: we don't want to connect (`shouldConnect`) until + // the user has decided whether to continue with the active session. + return { + screen: 'anotherSessionActive', + canvasState: { shouldConnect: false, shouldDisplay: false }, + }; + } else if (showMfa) { + // Second highest priority. Secondary to `showAnotherSessionActive` because + // this won't happen until the user has decided whether to continue with the active session. // - // This allows us to track the various pieces of the state independently and always display - // the vital information to the user. For example, we can track the TDP connection status - // and the websocket connection status separately throughout the codebase. If the TDP connection - // fails, and then the websocket closes, we want to show the `tdpConnection.statusText` to the user, - // not the `wsConnection.statusText`. But if the websocket closes unexpectedly before a TDP message telling - // us why, we want to show the websocket closing message to the user. - if (prevState.screen === 'alert dialog') { - return prevState; - } - - // Otherwise, calculate a new screen state. - const showAnotherSessionActive = showAnotherSessionActiveDialog; - const showMfa = webauthn.challenge; - const showAlert = - fetchAttempt.status === 'failed' || // Fetch attempt failed - tdpConnection.status === 'failed' || // TDP connection failed - tdpConnection.status === '' || // TDP connection ended gracefully server-side - wsConnection.status === 'closed'; // Websocket closed (could mean client side graceful close or unexpected close, the message will tell us which) - - const atLeastOneAttemptProcessing = - fetchAttempt.status === 'processing' || - tdpConnection.status === 'processing'; - const noDialogs = !(showMfa || showAnotherSessionActive || showAlert); - const showProcessing = atLeastOneAttemptProcessing && noDialogs; - - if (showAnotherSessionActive) { - // Highest priority: we don't want to connect (`shouldConnect`) until - // the user has decided whether to continue with the active session. - return { - screen: 'anotherSessionActive', - canvasState: {shouldConnect: false, shouldDisplay: false}, - }; - } else if (showMfa) { - // Second highest priority. Secondary to `showAnotherSessionActive` because - // this won't happen until the user has decided whether to continue with the active session. - // - // `shouldConnect` is true because we want to maintain the websocket connection that the mfa - // request was made over. - return { - screen: 'mfa', - canvasState: {shouldConnect: true, shouldDisplay: false}, - }; - } else if (showAlert) { - // Third highest priority. If either attempt or the websocket has failed, show the alert. - return { - screen: 'alert dialog', - alertMessage: calculateAlertMessage( - fetchAttempt, - tdpConnection, - wsConnection, - showAnotherSessionActiveDialog, - prevState - ), - canvasState: {shouldConnect: false, shouldDisplay: false}, - }; - } else if (showProcessing) { - // Fourth highest priority. If at least one attempt is still processing, show the processing indicator - // while trying to connect to the TDP server via the websocket. - const shouldConnect = fetchAttempt.status !== 'processing'; - return { - screen: 'processing', - canvasState: {shouldConnect, shouldDisplay: false}, - }; - } else { - // Default state: everything is good, so show the canvas. - return { - screen: 'canvas', - canvasState: {shouldConnect: true, shouldDisplay: true}, - }; - } + // `shouldConnect` is true because we want to maintain the websocket connection that the mfa + // request was made over. + return { + screen: 'mfa', + canvasState: { shouldConnect: true, shouldDisplay: false }, + }; + } else if (showAlert) { + // Third highest priority. If either attempt or the websocket has failed, show the alert. + return { + screen: 'alert dialog', + alertMessage: calculateAlertMessage( + fetchAttempt, + tdpConnection, + wsConnection, + showAnotherSessionActiveDialog, + prevState + ), + canvasState: { shouldConnect: false, shouldDisplay: false }, + }; + } else if (showProcessing) { + // Fourth highest priority. If at least one attempt is still processing, show the processing indicator + // while trying to connect to the TDP server via the websocket. + const shouldConnect = fetchAttempt.status !== 'processing'; + return { + screen: 'processing', + canvasState: { shouldConnect, shouldDisplay: false }, + }; + } else { + // Default state: everything is good, so show the canvas. + return { + screen: 'canvas', + canvasState: { shouldConnect: true, shouldDisplay: true }, + }; + } }; /** @@ -541,48 +540,48 @@ const nextScreenState = ( */ /* eslint-disable no-console */ const calculateAlertMessage = ( - fetchAttempt: Attempt, - tdpConnection: Attempt, - wsConnection: WebsocketAttempt, - showAnotherSessionActiveDialog: boolean, - prevState: ScreenState + fetchAttempt: Attempt, + tdpConnection: Attempt, + wsConnection: WebsocketAttempt, + showAnotherSessionActiveDialog: boolean, + prevState: ScreenState ): string => { - let message = ''; - if (fetchAttempt.status === 'failed') { - message = fetchAttempt.statusText || 'fetch attempt failed'; - } else if (tdpConnection.status === 'failed') { - message = tdpConnection.statusText || 'TDP connection failed'; - } else if (tdpConnection.status === '') { - message = tdpConnection.statusText || 'TDP connection ended gracefully'; - } else if (wsConnection.status === 'closed') { - message = - wsConnection.statusText || 'websocket disconnected for an unknown reason'; - } else { - console.error('invalid state'); - console.error({ - fetchAttempt, - tdpConnection, - wsConnection, - showAnotherSessionActiveDialog, - prevState, - }); - message = invalidStateMessage; - } - return message; + let message = ''; + if (fetchAttempt.status === 'failed') { + message = fetchAttempt.statusText || 'fetch attempt failed'; + } else if (tdpConnection.status === 'failed') { + message = tdpConnection.statusText || 'TDP connection failed'; + } else if (tdpConnection.status === '') { + message = tdpConnection.statusText || 'TDP connection ended gracefully'; + } else if (wsConnection.status === 'closed') { + message = + wsConnection.statusText || 'websocket disconnected for an unknown reason'; + } else { + console.error('invalid state'); + console.error({ + fetchAttempt, + tdpConnection, + wsConnection, + showAnotherSessionActiveDialog, + prevState, + }); + message = invalidStateMessage; + } + return message; }; /* eslint-enable no-console */ type ScreenState = { - screen: - | 'mfa' - | 'anotherSessionActive' - | 'alert dialog' - | 'processing' - | 'canvas'; - - alertMessage?: string; - canvasState: { - shouldConnect: boolean; - shouldDisplay: boolean; - }; + screen: + | 'mfa' + | 'anotherSessionActive' + | 'alert dialog' + | 'processing' + | 'canvas'; + + alertMessage?: string; + canvasState: { + shouldConnect: boolean; + shouldDisplay: boolean; + }; }; From 4906593b74975f482e87e01a894abcdc6ded9eea Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 17 Mar 2025 21:55:27 +0100 Subject: [PATCH 07/35] fix imports --- .../src/DesktopSession/DesktopSession.tsx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index 204089f5f8580..ab53d8bb18cc4 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -32,7 +32,7 @@ import { Attempt } from 'shared/hooks/useAttemptNext'; import AuthnDialog from 'teleport/components/AuthnDialog'; import TdpClientCanvas from 'teleport/components/TdpClientCanvas'; import { TdpClientCanvasRef } from 'teleport/components/TdpClientCanvas/TdpClientCanvas'; -import { useListener } from 'teleport/lib/tdp/client'; +import {TdpClientEvent, useListener} from 'teleport/lib/tdp/client'; import { MfaState, shouldShowMfaPrompt } from 'teleport/lib/useMfa'; import TopBar from './TopBar'; @@ -108,23 +108,23 @@ export function DesktopSession(props: State) { } }, [client, shouldConnect]); - const [latencyStats, setLatencyStats] = useState(undefined); - useEffect(() => { - if (!client) { - return; - } - const setStats = stats => { - console.log('got latency', stats); - setLatencyStats({ - client: stats.browserLatency, - server: stats.desktopLatency, - }); - }; - client.on(TdpClientEvent.LATENCY_STATS, setStats); - return () => { - client.removeListener(TdpClientEvent.LATENCY_STATS, setStats); - }; - }, [client]); + const [latencyStats, setLatencyStats] = useState(undefined); + useEffect(() => { + if (!client) { + return; + } + const setStats = stats => { + console.log('got latency', stats); + setLatencyStats({ + client: stats.browserLatency, + server: stats.desktopLatency, + }); + }; + client.on(TdpClientEvent.LATENCY_STATS, setStats); + return () => { + client.removeListener(TdpClientEvent.LATENCY_STATS, setStats); + }; + }, [client]); // Calculate the next `ScreenState` whenever any of the constituent pieces of state change. useEffect(() => { From 8b7ae4bc440371329fb949f866a47257d102973a Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 17 Mar 2025 22:02:20 +0100 Subject: [PATCH 08/35] formatting --- .../LatencyDiagnostic/LatencyDiagnostic.tsx | 364 +++++++++--------- 1 file changed, 182 insertions(+), 182 deletions(-) diff --git a/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx b/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx index 7702d47da0c28..0d2256dd935ba 100644 --- a/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx +++ b/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx @@ -19,227 +19,227 @@ import React from 'react'; import styled from 'styled-components'; -import {Flex, H2, Text} from 'design'; +import { Flex, H2, Text } from 'design'; import * as Icons from 'design/Icon'; -import {TeleportGearIcon} from 'design/SVGIcon'; -import {MenuIcon} from 'shared/components/MenuAction'; +import { TeleportGearIcon } from 'design/SVGIcon'; +import { MenuIcon } from 'shared/components/MenuAction'; -import {DocumentSsh} from 'teleport/Console/stores'; +import { DocumentSsh } from 'teleport/Console/stores'; export const WARN_THRESHOLD = 150; export const ERROR_THRESHOLD = 400; export enum LatencyColor { - Ok = 'dataVisualisation.tertiary.caribbean', - Warn = 'dataVisualisation.tertiary.abbey', - Error = 'dataVisualisation.tertiary.sunflower', - Unknown = 'text.muted', + Ok = 'dataVisualisation.tertiary.caribbean', + Warn = 'dataVisualisation.tertiary.abbey', + Error = 'dataVisualisation.tertiary.sunflower', + Unknown = 'text.muted', } function colorForLatency(l: number): LatencyColor { - if (l >= ERROR_THRESHOLD) { - return LatencyColor.Error; - } + if (l >= ERROR_THRESHOLD) { + return LatencyColor.Error; + } - if (l >= WARN_THRESHOLD) { - return LatencyColor.Warn; - } + if (l >= WARN_THRESHOLD) { + return LatencyColor.Warn; + } - return LatencyColor.Ok; + return LatencyColor.Ok; } // latencyColors determines the color to use for each leg of the connection // and the total measurement. export function latencyColors(latency: { client: number; server: number }): { - client: LatencyColor; - server: LatencyColor; - total: LatencyColor; + client: LatencyColor; + server: LatencyColor; + total: LatencyColor; } { - if (latency === undefined) { - return { - client: LatencyColor.Unknown, - server: LatencyColor.Unknown, - total: LatencyColor.Unknown, - }; - } - - const clientColor = colorForLatency(latency.client); - const serverColor = colorForLatency(latency.server); - - // any + red = red - if (latency.client >= ERROR_THRESHOLD || latency.server >= ERROR_THRESHOLD) { - return { - client: clientColor, - server: serverColor, - total: LatencyColor.Error, - }; - } - - // any + yellow = yellow - if (latency.client >= WARN_THRESHOLD || latency.server >= WARN_THRESHOLD) { - return { - client: clientColor, - server: serverColor, - total: LatencyColor.Warn, - }; - } - - // green + green = green - return {client: clientColor, server: serverColor, total: LatencyColor.Ok}; + if (latency === undefined) { + return { + client: LatencyColor.Unknown, + server: LatencyColor.Unknown, + total: LatencyColor.Unknown, + }; + } + + const clientColor = colorForLatency(latency.client); + const serverColor = colorForLatency(latency.server); + + // any + red = red + if (latency.client >= ERROR_THRESHOLD || latency.server >= ERROR_THRESHOLD) { + return { + client: clientColor, + server: serverColor, + total: LatencyColor.Error, + }; + } + + // any + yellow = yellow + if (latency.client >= WARN_THRESHOLD || latency.server >= WARN_THRESHOLD) { + return { + client: clientColor, + server: serverColor, + total: LatencyColor.Warn, + }; + } + + // green + green = green + return { client: clientColor, server: serverColor, total: LatencyColor.Ok }; } export function LatencyDiagnostic({ - latency, - }: { - latency: DocumentSsh['latency']; + latency, +}: { + latency: DocumentSsh['latency']; }) { - const colors = latencyColors(latency); - - return ( - - - -

Network Connection

- - - } - text="You" - alignItems="flex-start" - /> - - } - text="Teleport" - alignItems="center" - /> - - } - text="Server" - alignItems="flex-end" - /> - - - - - {latency === undefined && ( - - Connecting - - )} - - {latency !== undefined && colors.total === LatencyColor.Error && ( - - )} - - {latency !== undefined && colors.total === LatencyColor.Warn && ( - - )} - - {latency !== undefined && colors.total === LatencyColor.Ok && ( - - )} - - {latency !== undefined && ( - - Total Latency: {latency.client + latency.server}ms - - )} - - -
-
-
- ); + const colors = latencyColors(latency); + + return ( + + + +

Network Connection

+ + + } + text="You" + alignItems="flex-start" + /> + + } + text="Teleport" + alignItems="center" + /> + + } + text="Server" + alignItems="flex-end" + /> + + + + + {latency === undefined && ( + + Connecting + + )} + + {latency !== undefined && colors.total === LatencyColor.Error && ( + + )} + + {latency !== undefined && colors.total === LatencyColor.Warn && ( + + )} + + {latency !== undefined && colors.total === LatencyColor.Ok && ( + + )} + + {latency !== undefined && ( + + Total Latency: {latency.client + latency.server}ms + + )} + + +
+
+
+ ); } const IconContainer: React.FC<{ - icon: JSX.Element; - text: string; - alignItems: 'flex-start' | 'center' | 'flex-end'; -}> = ({icon, text, alignItems}) => ( - - {icon} - {text} - + icon: JSX.Element; + text: string; + alignItems: 'flex-start' | 'center' | 'flex-end'; +}> = ({ icon, text, alignItems }) => ( + + {icon} + {text} + ); const Leg: React.FC<{ - color: LatencyColor; - latency: number | undefined; -}> = ({color, latency}) => ( - - - {latency !== undefined && {latency}ms} - {latency === undefined && } - + color: LatencyColor; + latency: number | undefined; +}> = ({ color, latency }) => ( + + + {latency !== undefined && {latency}ms} + {latency === undefined && } + ); // Looks like `<----->` const DoubleSidedArrow = () => { - return ( - - - - - - ); + return ( + + + + + + ); }; const Container = styled.div` - background: ${props => props.theme.colors.levels.elevated}; - padding: ${props => props.theme.space[4]}px; - width: 370px; + background: ${props => props.theme.colors.levels.elevated}; + padding: ${props => props.theme.space[4]}px; + width: 370px; `; const Line = styled.div` - color: ${props => props.theme.colors.text.muted}; - border: 0.5px dashed; - width: 100%; + color: ${props => props.theme.colors.text.muted}; + border: 0.5px dashed; + width: 100%; `; const Placeholder = styled.div` - height: 24px; + height: 24px; `; From c9b6a0155fc94e536cb5bbc864d0d4b432137df7 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 17 Mar 2025 22:09:31 +0100 Subject: [PATCH 09/35] formatting --- .../teleport/src/DesktopSession/TopBar.tsx | 184 +++++++++--------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/web/packages/teleport/src/DesktopSession/TopBar.tsx b/web/packages/teleport/src/DesktopSession/TopBar.tsx index a7b491f457ca7..38beb929516b9 100644 --- a/web/packages/teleport/src/DesktopSession/TopBar.tsx +++ b/web/packages/teleport/src/DesktopSession/TopBar.tsx @@ -16,115 +16,115 @@ * along with this program. If not, see . */ -import {useTheme} from 'styled-components'; +import { useTheme } from 'styled-components'; -import {Flex, Text, TopNav} from 'design'; -import {Clipboard, FolderShared} from 'design/Icon'; -import {HoverTooltip} from 'design/Tooltip'; -import type {NotificationItem} from 'shared/components/Notification'; +import { Flex, Text, TopNav } from 'design'; +import { Clipboard, FolderShared } from 'design/Icon'; +import { HoverTooltip } from 'design/Tooltip'; +import type { NotificationItem } from 'shared/components/Notification'; import {LatencyDiagnostic} from 'shared/components/LatencyDiagnostic'; import ActionMenu from './ActionMenu'; -import {AlertDropdown} from './AlertDropdown'; +import { AlertDropdown } from './AlertDropdown'; export default function TopBar(props: Props) { - const { - userHost, - isSharingClipboard, - clipboardSharingMessage, - onDisconnect, - canShareDirectory, - isSharingDirectory, - onShareDirectory, - onCtrlAltDel, - alerts, - onRemoveAlert, - latency - } = props; - const theme = useTheme(); + const { + userHost, + isSharingClipboard, + clipboardSharingMessage, + onDisconnect, + canShareDirectory, + isSharingDirectory, + onShareDirectory, + onCtrlAltDel, + alerts, + onRemoveAlert, + latency, + } = props; + const theme = useTheme(); - const primaryOnTrue = (b: boolean): any => { - return { - color: b ? theme.colors.text.main : theme.colors.text.disabled, - }; + const primaryOnTrue = (b: boolean): any => { + return { + color: b ? theme.colors.text.main : theme.colors.text.disabled, }; + }; - return ( - - - {userHost} - + return ( + + + {userHost} + - - - {latency && - - - } - - - - - - - - - - - - - ); + + + { + latency && + + + } + + + + + + + + + + + + ); } function directorySharingToolTip( - canShare: boolean, - isSharing: boolean + canShare: boolean, + isSharing: boolean ): string { - if (!canShare) { - return 'Directory Sharing Disabled'; - } - if (!isSharing) { - return 'Directory Sharing Inactive'; - } - return 'Directory Sharing Enabled'; + if (!canShare) { + return 'Directory Sharing Disabled'; + } + if (!isSharing) { + return 'Directory Sharing Inactive'; + } + return 'Directory Sharing Enabled'; } export const TopBarHeight = 40; type Props = { - userHost: string; - isSharingClipboard: boolean; - clipboardSharingMessage: string; - canShareDirectory: boolean; - isSharingDirectory: boolean; - onDisconnect: VoidFunction; - onShareDirectory: VoidFunction; - onCtrlAltDel: VoidFunction; - alerts: NotificationItem[]; - onRemoveAlert(id: string): void; - latency: { - client: number; - server: number; - }; + userHost: string; + isSharingClipboard: boolean; + clipboardSharingMessage: string; + canShareDirectory: boolean; + isSharingDirectory: boolean; + onDisconnect: VoidFunction; + onShareDirectory: VoidFunction; + onCtrlAltDel: VoidFunction; + alerts: NotificationItem[]; + onRemoveAlert(id: string): void; + latency: { + client: number; + server: number; + }; }; From d8b7aad67ebf2198aecd231ba6a7b2291d4cc756 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 17 Mar 2025 22:45:09 +0100 Subject: [PATCH 10/35] log and gci --- lib/srv/desktop/tdp/proto.go | 2 +- lib/web/desktop.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/srv/desktop/tdp/proto.go b/lib/srv/desktop/tdp/proto.go index 1265b3231743f..a93018da2be0c 100644 --- a/lib/srv/desktop/tdp/proto.go +++ b/lib/srv/desktop/tdp/proto.go @@ -29,11 +29,11 @@ import ( "encoding/binary" "encoding/json" "errors" - "github.com/google/uuid" "image" "image/png" "io" + "github.com/google/uuid" "github.com/gravitational/trace" authproto "github.com/gravitational/teleport/api/client/proto" diff --git a/lib/web/desktop.go b/lib/web/desktop.go index c0ee0dd7ad3c1..032cb0c353a9e 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -24,7 +24,6 @@ import ( "crypto" "crypto/tls" "errors" - "github.com/google/uuid" "io" "log/slog" "math/rand/v2" @@ -32,6 +31,7 @@ import ( "net/http" "sync" + "github.com/google/uuid" "github.com/gorilla/websocket" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -640,7 +640,7 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger, vers continue } if ls, ok := msg.(tdp.LatencyStats); ok { - log.InfoContext(ctx, "sending latency stats: %v / %v", ls.BrowserLatency, ls.DesktopLatency) + log.InfoContext(ctx, "sending latency stats", "browser", ls.BrowserLatency, "desktop", ls.DesktopLatency) } encoded, err := msg.Encode() if err != nil { From 5b081818ec926439ab357e4cf494d3c077ce2b58 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Mon, 17 Mar 2025 22:49:25 +0100 Subject: [PATCH 11/35] lint --- .../src/DesktopSession/DesktopSession.tsx | 30 +++++++++---------- .../teleport/src/DesktopSession/TopBar.tsx | 14 ++++----- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index ab53d8bb18cc4..7294dc8a1fbf0 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -32,7 +32,7 @@ import { Attempt } from 'shared/hooks/useAttemptNext'; import AuthnDialog from 'teleport/components/AuthnDialog'; import TdpClientCanvas from 'teleport/components/TdpClientCanvas'; import { TdpClientCanvasRef } from 'teleport/components/TdpClientCanvas/TdpClientCanvas'; -import {TdpClientEvent, useListener} from 'teleport/lib/tdp/client'; +import { TdpClientEvent, useListener } from 'teleport/lib/tdp/client'; import { MfaState, shouldShowMfaPrompt } from 'teleport/lib/useMfa'; import TopBar from './TopBar'; @@ -110,20 +110,20 @@ export function DesktopSession(props: State) { const [latencyStats, setLatencyStats] = useState(undefined); useEffect(() => { - if (!client) { - return; - } - const setStats = stats => { - console.log('got latency', stats); - setLatencyStats({ - client: stats.browserLatency, - server: stats.desktopLatency, - }); - }; - client.on(TdpClientEvent.LATENCY_STATS, setStats); - return () => { - client.removeListener(TdpClientEvent.LATENCY_STATS, setStats); - }; + if (!client) { + return; + } + const setStats = stats => { + console.log('got latency', stats); + setLatencyStats({ + client: stats.browserLatency, + server: stats.desktopLatency, + }); + }; + client.on(TdpClientEvent.LATENCY_STATS, setStats); + return () => { + client.removeListener(TdpClientEvent.LATENCY_STATS, setStats); + }; }, [client]); // Calculate the next `ScreenState` whenever any of the constituent pieces of state change. diff --git a/web/packages/teleport/src/DesktopSession/TopBar.tsx b/web/packages/teleport/src/DesktopSession/TopBar.tsx index 38beb929516b9..f8a5b1fc80665 100644 --- a/web/packages/teleport/src/DesktopSession/TopBar.tsx +++ b/web/packages/teleport/src/DesktopSession/TopBar.tsx @@ -21,8 +21,8 @@ import { useTheme } from 'styled-components'; import { Flex, Text, TopNav } from 'design'; import { Clipboard, FolderShared } from 'design/Icon'; import { HoverTooltip } from 'design/Tooltip'; +import { LatencyDiagnostic } from 'shared/components/LatencyDiagnostic'; import type { NotificationItem } from 'shared/components/Notification'; -import {LatencyDiagnostic} from 'shared/components/LatencyDiagnostic'; import ActionMenu from './ActionMenu'; import { AlertDropdown } from './AlertDropdown'; @@ -63,15 +63,15 @@ export default function TopBar(props: Props) { - { - latency && - + - } + )} Date: Wed, 19 Mar 2025 14:43:37 +0100 Subject: [PATCH 12/35] Apply tooltip on the icon directly, instead of on the Menu component This fixes a problem where onMouseLeave in HoverTooltip wasn't called and the tooltip didn't disappear. --- .../LatencyDiagnostic/LatencyDiagnostic.tsx | 6 +++++- .../components/MenuAction/MenuActionIcon.tsx | 20 +++++++++++-------- .../teleport/src/DesktopSession/TopBar.tsx | 10 +--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx b/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx index 0d2256dd935ba..12f238a127d23 100644 --- a/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx +++ b/web/packages/shared/components/LatencyDiagnostic/LatencyDiagnostic.tsx @@ -96,7 +96,11 @@ export function LatencyDiagnostic({ const colors = latencyColors(latency); return ( - +

Network Connection

diff --git a/web/packages/shared/components/MenuAction/MenuActionIcon.tsx b/web/packages/shared/components/MenuAction/MenuActionIcon.tsx index 679d09e05b287..6f201e169fb14 100644 --- a/web/packages/shared/components/MenuAction/MenuActionIcon.tsx +++ b/web/packages/shared/components/MenuAction/MenuActionIcon.tsx @@ -22,6 +22,7 @@ import { ButtonIcon } from 'design'; import { MoreHoriz } from 'design/Icon'; import { IconProps } from 'design/Icon/Icon'; import Menu from 'design/Menu'; +import { HoverTooltip } from 'design/Tooltip'; import { AnchorProps, MenuProps } from './types'; @@ -56,14 +57,16 @@ export default class MenuActionIcon extends React.Component< const { children, buttonIconProps, menuProps, Icon } = this.props; return ( <> - (this.anchorEl = e)} - onClick={this.onOpen} - data-testid="button" - > - - + + (this.anchorEl = e)} + onClick={this.onOpen} + data-testid="button" + > + + + ; + tooltip?: React.ReactNode; }; diff --git a/web/packages/teleport/src/DesktopSession/TopBar.tsx b/web/packages/teleport/src/DesktopSession/TopBar.tsx index f8a5b1fc80665..929400ff92310 100644 --- a/web/packages/teleport/src/DesktopSession/TopBar.tsx +++ b/web/packages/teleport/src/DesktopSession/TopBar.tsx @@ -63,15 +63,7 @@ export default function TopBar(props: Props) { - {latency && ( - - - - )} + {latency && } Date: Wed, 19 Mar 2025 14:44:29 +0100 Subject: [PATCH 13/35] Use consistent spacing between top bar elements --- web/packages/teleport/src/DesktopSession/TopBar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/packages/teleport/src/DesktopSession/TopBar.tsx b/web/packages/teleport/src/DesktopSession/TopBar.tsx index 929400ff92310..7ea33106b124e 100644 --- a/web/packages/teleport/src/DesktopSession/TopBar.tsx +++ b/web/packages/teleport/src/DesktopSession/TopBar.tsx @@ -62,7 +62,7 @@ export default function TopBar(props: Props) { - + {latency && } - + - + From 71e14d03526c6910334d4a9be3d3b850e31e0518 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 25 Mar 2025 21:34:54 +0100 Subject: [PATCH 14/35] updates from origin --- go.mod | 2 + go.sum | 4 + package.json | 2 +- pnpm-lock.yaml | 579 +++++++++++++++++++++++-------------------------- 4 files changed, 278 insertions(+), 309 deletions(-) diff --git a/go.mod b/go.mod index aa2e8593e64e9..8f23abfe9e633 100644 --- a/go.mod +++ b/go.mod @@ -192,6 +192,7 @@ require ( github.com/sijms/go-ora/v2 v2.8.24 github.com/snowflakedb/gosnowflake v1.13.0 github.com/spf13/cobra v1.9.1 + github.com/spiffe/aws-spiffe-workload-helper v0.0.1-rc.8 github.com/spiffe/go-spiffe/v2 v2.5.0 github.com/stretchr/testify v1.10.0 github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb @@ -296,6 +297,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.0 // indirect + github.com/aws/rolesanywhere-credential-helper v1.2.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index fd511acc0b072..94890808e4902 100644 --- a/go.sum +++ b/go.sum @@ -963,6 +963,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.16 h1:BHEK2Q/7CMRMCb3nySi/w8UbIcP github.com/aws/aws-sdk-go-v2/service/sts v1.33.16/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= github.com/aws/aws-sigv4-auth-cassandra-gocql-driver-plugin v1.1.0 h1:EJsHUYgFBV7/N1YtL73lsfZODAOU+CnNSZfEAlqqQaA= github.com/aws/aws-sigv4-auth-cassandra-gocql-driver-plugin v1.1.0/go.mod h1:AxKuXHc0zv2yYaeueUG7R3ONbcnQIuDj0bkdFmPVRzU= +github.com/aws/rolesanywhere-credential-helper v1.2.0 h1:eLqJvSznH8nJk48dwFc0raWOpbTGgBeNYH3Q8UQFVx4= +github.com/aws/rolesanywhere-credential-helper v1.2.0/go.mod h1:YRxmRrAaqbVVXPNH1gHT76nWaMGvpAziHAHw8UwKrpU= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k= github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= @@ -2184,6 +2186,8 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spiffe/aws-spiffe-workload-helper v0.0.1-rc.8 h1:KFHLQuNqUG4YZXo+QwMurqFxU5qWchFDCxYbl+uJz9w= +github.com/spiffe/aws-spiffe-workload-helper v0.0.1-rc.8/go.mod h1:xhhvkBenvvbuEEw9YR2HwGzUPv0sQiHd3wv8avhra+U= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/package.json b/package.json index d7774a54ece45..00d498303f9ac 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "react-select-event": "^5.5.1", "storybook": "^8.6.3", "typescript": "^5.8.2", - "vite": "^6.2.0" + "vite": "^6.2.3" }, "dependencies": { "@codemirror/autocomplete": "^6.18.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80215927aacda..20b50c9aefe43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,7 +131,7 @@ importers: version: 8.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.3(prettier@3.5.3))(typescript@5.8.2) '@storybook/react-vite': specifier: ^8.6.3 - version: 8.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.34.8)(storybook@8.6.3(prettier@3.5.3))(typescript@5.8.2)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) + version: 8.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.37.0)(storybook@8.6.3(prettier@3.5.3))(typescript@5.8.2)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) '@storybook/test-runner': specifier: ^0.22.0 version: 0.22.0(@types/node@20.17.22)(babel-plugin-macros@3.1.0)(storybook@8.6.3(prettier@3.5.3)) @@ -208,8 +208,8 @@ importers: specifier: ^5.8.2 version: 5.8.2 vite: - specifier: ^6.2.0 - version: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + specifier: ^6.2.3 + version: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) e/web/teleport: {} @@ -241,7 +241,7 @@ importers: version: 21.1.7 '@vitejs/plugin-react-swc': specifier: ^3.8.0 - version: 3.8.0(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) + version: 3.8.0(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) babel-plugin-styled-components: specifier: ^2.1.4 version: 2.1.4(@babel/core@7.26.10)(styled-components@6.1.15(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) @@ -277,16 +277,16 @@ importers: version: 26.0.0 rollup-plugin-visualizer: specifier: ^5.14.0 - version: 5.14.0(rollup@4.34.8) + version: 5.14.0(rollup@4.37.0) typescript-eslint: specifier: ^8.26.0 version: 8.26.0(eslint@9.21.0)(typescript@5.8.2) vite-plugin-wasm: specifier: ^3.4.1 - version: 3.4.1(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) + version: 3.4.1(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) + version: 5.1.4(typescript@5.8.2)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) web/packages/design: dependencies: @@ -496,7 +496,7 @@ importers: version: 25.1.8(electron-builder-squirrel-windows@25.1.8) electron-vite: specifier: ^3.0.0 - version: 3.0.0(@swc/core@1.11.5)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) + version: 3.0.0(@swc/core@1.11.5)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) events: specifier: 3.3.0 version: 3.3.0 @@ -550,10 +550,6 @@ packages: resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.9': - resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==} - engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} @@ -642,11 +638,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.9': - resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} @@ -1151,18 +1142,10 @@ packages: resolution: {integrity: sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.9': - resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==} - engines: {node: '>=6.9.0'} - '@babel/types@7.26.10': resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.9': - resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} - engines: {node: '>=6.9.0'} - '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1540,152 +1523,152 @@ packages: '@emotion/weak-memoize@0.4.0': resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - '@esbuild/aix-ppc64@0.25.0': - resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + '@esbuild/aix-ppc64@0.25.1': + resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.0': - resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + '@esbuild/android-arm64@0.25.1': + resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.0': - resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + '@esbuild/android-arm@0.25.1': + resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.0': - resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + '@esbuild/android-x64@0.25.1': + resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.0': - resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + '@esbuild/darwin-arm64@0.25.1': + resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.0': - resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + '@esbuild/darwin-x64@0.25.1': + resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.0': - resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + '@esbuild/freebsd-arm64@0.25.1': + resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.0': - resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + '@esbuild/freebsd-x64@0.25.1': + resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.0': - resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + '@esbuild/linux-arm64@0.25.1': + resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.0': - resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + '@esbuild/linux-arm@0.25.1': + resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.0': - resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + '@esbuild/linux-ia32@0.25.1': + resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.0': - resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + '@esbuild/linux-loong64@0.25.1': + resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.0': - resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + '@esbuild/linux-mips64el@0.25.1': + resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.0': - resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + '@esbuild/linux-ppc64@0.25.1': + resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.0': - resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + '@esbuild/linux-riscv64@0.25.1': + resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.0': - resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + '@esbuild/linux-s390x@0.25.1': + resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.0': - resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + '@esbuild/linux-x64@0.25.1': + resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.0': - resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + '@esbuild/netbsd-arm64@0.25.1': + resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.0': - resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + '@esbuild/netbsd-x64@0.25.1': + resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.0': - resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + '@esbuild/openbsd-arm64@0.25.1': + resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.0': - resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + '@esbuild/openbsd-x64@0.25.1': + resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.0': - resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + '@esbuild/sunos-x64@0.25.1': + resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.0': - resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + '@esbuild/win32-arm64@0.25.1': + resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.0': - resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + '@esbuild/win32-ia32@0.25.1': + resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.0': - resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + '@esbuild/win32-x64@0.25.1': + resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -2253,98 +2236,103 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.34.8': - resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} + '@rollup/rollup-android-arm-eabi@4.37.0': + resolution: {integrity: sha512-l7StVw6WAa8l3vA1ov80jyetOAEo1FtHvZDbzXDO/02Sq/QVvqlHkYoFwDJPIMj0GKiistsBudfx5tGFnwYWDQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.34.8': - resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} + '@rollup/rollup-android-arm64@4.37.0': + resolution: {integrity: sha512-6U3SlVyMxezt8Y+/iEBcbp945uZjJwjZimu76xoG7tO1av9VO691z8PkhzQ85ith2I8R2RddEPeSfcbyPfD4hA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.34.8': - resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} + '@rollup/rollup-darwin-arm64@4.37.0': + resolution: {integrity: sha512-+iTQ5YHuGmPt10NTzEyMPbayiNTcOZDWsbxZYR1ZnmLnZxG17ivrPSWFO9j6GalY0+gV3Jtwrrs12DBscxnlYA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.34.8': - resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} + '@rollup/rollup-darwin-x64@4.37.0': + resolution: {integrity: sha512-m8W2UbxLDcmRKVjgl5J/k4B8d7qX2EcJve3Sut7YGrQoPtCIQGPH5AMzuFvYRWZi0FVS0zEY4c8uttPfX6bwYQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.34.8': - resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} + '@rollup/rollup-freebsd-arm64@4.37.0': + resolution: {integrity: sha512-FOMXGmH15OmtQWEt174v9P1JqqhlgYge/bUjIbiVD1nI1NeJ30HYT9SJlZMqdo1uQFyt9cz748F1BHghWaDnVA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.34.8': - resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} + '@rollup/rollup-freebsd-x64@4.37.0': + resolution: {integrity: sha512-SZMxNttjPKvV14Hjck5t70xS3l63sbVwl98g3FlVVx2YIDmfUIy29jQrsw06ewEYQ8lQSuY9mpAPlmgRD2iSsA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.34.8': - resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} + '@rollup/rollup-linux-arm-gnueabihf@4.37.0': + resolution: {integrity: sha512-hhAALKJPidCwZcj+g+iN+38SIOkhK2a9bqtJR+EtyxrKKSt1ynCBeqrQy31z0oWU6thRZzdx53hVgEbRkuI19w==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.34.8': - resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} + '@rollup/rollup-linux-arm-musleabihf@4.37.0': + resolution: {integrity: sha512-jUb/kmn/Gd8epbHKEqkRAxq5c2EwRt0DqhSGWjPFxLeFvldFdHQs/n8lQ9x85oAeVb6bHcS8irhTJX2FCOd8Ag==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.34.8': - resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} + '@rollup/rollup-linux-arm64-gnu@4.37.0': + resolution: {integrity: sha512-oNrJxcQT9IcbcmKlkF+Yz2tmOxZgG9D9GRq+1OE6XCQwCVwxixYAa38Z8qqPzQvzt1FCfmrHX03E0pWoXm1DqA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.34.8': - resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} + '@rollup/rollup-linux-arm64-musl@4.37.0': + resolution: {integrity: sha512-pfxLBMls+28Ey2enpX3JvjEjaJMBX5XlPCZNGxj4kdJyHduPBXtxYeb8alo0a7bqOoWZW2uKynhHxF/MWoHaGQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.34.8': - resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} + '@rollup/rollup-linux-loongarch64-gnu@4.37.0': + resolution: {integrity: sha512-yCE0NnutTC/7IGUq/PUHmoeZbIwq3KRh02e9SfFh7Vmc1Z7atuJRYWhRME5fKgT8aS20mwi1RyChA23qSyRGpA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': - resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': + resolution: {integrity: sha512-NxcICptHk06E2Lh3a4Pu+2PEdZ6ahNHuK7o6Np9zcWkrBMuv21j10SQDJW3C9Yf/A/P7cutWoC/DptNLVsZ0VQ==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.34.8': - resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} + '@rollup/rollup-linux-riscv64-gnu@4.37.0': + resolution: {integrity: sha512-PpWwHMPCVpFZLTfLq7EWJWvrmEuLdGn1GMYcm5MV7PaRgwCEYJAwiN94uBuZev0/J/hFIIJCsYw4nLmXA9J7Pw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.34.8': - resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} + '@rollup/rollup-linux-riscv64-musl@4.37.0': + resolution: {integrity: sha512-DTNwl6a3CfhGTAOYZ4KtYbdS8b+275LSLqJVJIrPa5/JuIufWWZ/QFvkxp52gpmguN95eujrM68ZG+zVxa8zHA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.37.0': + resolution: {integrity: sha512-hZDDU5fgWvDdHFuExN1gBOhCuzo/8TMpidfOR+1cPZJflcEzXdCy1LjnklQdW8/Et9sryOPJAKAQRw8Jq7Tg+A==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.34.8': - resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} + '@rollup/rollup-linux-x64-gnu@4.37.0': + resolution: {integrity: sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.34.8': - resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} + '@rollup/rollup-linux-x64-musl@4.37.0': + resolution: {integrity: sha512-E2lPrLKE8sQbY/2bEkVTGDEk4/49UYRVWgj90MY8yPjpnGBQ+Xi1Qnr7b7UIWw1NOggdFQFOLZ8+5CzCiz143w==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.34.8': - resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} + '@rollup/rollup-win32-arm64-msvc@4.37.0': + resolution: {integrity: sha512-Jm7biMazjNzTU4PrQtr7VS8ibeys9Pn29/1bm4ph7CP2kf21950LgN+BaE2mJ1QujnvOc6p54eWWiVvn05SOBg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.34.8': - resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} + '@rollup/rollup-win32-ia32-msvc@4.37.0': + resolution: {integrity: sha512-e3/1SFm1OjefWICB2Ucstg2dxYDkDTZGDYgwufcbsxTHyqQps1UQf33dFEChBNmeSsTOyrjw2JJq0zbG5GF6RA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.34.8': - resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} + '@rollup/rollup-win32-x64-msvc@4.37.0': + resolution: {integrity: sha512-LWbXUBwn/bcLx2sSsqy7pK5o+Nr+VCoRoAohfJ5C/aBio9nfJmGQqHAhU6pwxV/RmyTk5AqdySma7uwWGlmeuA==} cpu: [x64] os: [win32] @@ -4053,8 +4041,8 @@ packages: peerDependencies: esbuild: '>=0.12 <1' - esbuild@0.25.0: - resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + esbuild@0.25.1: + resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} engines: {node: '>=18'} hasBin: true @@ -5602,8 +5590,8 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - nanoid@3.3.8: - resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -6256,8 +6244,8 @@ packages: rollup: optional: true - rollup@4.34.8: - resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} + rollup@4.37.0: + resolution: {integrity: sha512-iAtQy/L4QFU+rTJ1YUjXqJOJzuwEghqWzCEYD2FEghT7Gsy1VdABntrO4CLopA5IkflTyqNiLNwPcOJ3S7UKLg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -6953,8 +6941,8 @@ packages: vite: optional: true - vite@6.2.0: - resolution: {integrity: sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==} + vite@6.2.3: + resolution: {integrity: sha512-IzwM54g4y9JA/xAeBPNaDXiBF8Jsgl3VBQ2YQ/wOY6fyW3xMdSoltIV3Bo59DErdqdE6RxUfv8W69DvUorE4Eg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -7290,17 +7278,9 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 - '@babel/generator@7.26.9': - dependencies: - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 '@babel/helper-compilation-targets@7.26.5': dependencies: @@ -7318,7 +7298,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.25.9 '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -7343,15 +7323,15 @@ snapshots: '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 transitivePeerDependencies: - supports-color @@ -7360,13 +7340,13 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 '@babel/helper-plugin-utils@7.26.5': {} @@ -7375,7 +7355,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color @@ -7384,14 +7364,14 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 transitivePeerDependencies: - supports-color @@ -7404,8 +7384,8 @@ snapshots: '@babel/helper-wrap-function@7.25.9': dependencies: '@babel/template': 7.26.9 - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 transitivePeerDependencies: - supports-color @@ -7418,15 +7398,11 @@ snapshots: dependencies: '@babel/types': 7.26.10 - '@babel/parser@7.26.9': - dependencies: - '@babel/types': 7.26.9 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color @@ -7453,7 +7429,7 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color @@ -7567,7 +7543,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color @@ -7613,7 +7589,7 @@ snapshots: '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7674,7 +7650,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color @@ -7720,7 +7696,7 @@ snapshots: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.9 + '@babel/traverse': 7.26.10 transitivePeerDependencies: - supports-color @@ -7827,7 +7803,7 @@ snapshots: '@babel/helper-module-imports': 7.25.9 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 transitivePeerDependencies: - supports-color @@ -7995,7 +7971,7 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 esutils: 2.0.3 '@babel/preset-react@7.26.3(@babel/core@7.26.10)': @@ -8028,8 +8004,8 @@ snapshots: '@babel/template@7.26.9': dependencies: '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 '@babel/traverse@7.26.10': dependencies: @@ -8043,28 +8019,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.26.9': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.9 - '@babel/parser': 7.26.9 - '@babel/template': 7.26.9 - '@babel/types': 7.26.9 - debug: 4.4.0 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/types@7.26.10': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/types@7.26.9': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@bcoe/v8-coverage@0.2.3': {} '@bundled-es-modules/cookie@2.0.1': @@ -8532,79 +8491,79 @@ snapshots: '@emotion/weak-memoize@0.4.0': {} - '@esbuild/aix-ppc64@0.25.0': + '@esbuild/aix-ppc64@0.25.1': optional: true - '@esbuild/android-arm64@0.25.0': + '@esbuild/android-arm64@0.25.1': optional: true - '@esbuild/android-arm@0.25.0': + '@esbuild/android-arm@0.25.1': optional: true - '@esbuild/android-x64@0.25.0': + '@esbuild/android-x64@0.25.1': optional: true - '@esbuild/darwin-arm64@0.25.0': + '@esbuild/darwin-arm64@0.25.1': optional: true - '@esbuild/darwin-x64@0.25.0': + '@esbuild/darwin-x64@0.25.1': optional: true - '@esbuild/freebsd-arm64@0.25.0': + '@esbuild/freebsd-arm64@0.25.1': optional: true - '@esbuild/freebsd-x64@0.25.0': + '@esbuild/freebsd-x64@0.25.1': optional: true - '@esbuild/linux-arm64@0.25.0': + '@esbuild/linux-arm64@0.25.1': optional: true - '@esbuild/linux-arm@0.25.0': + '@esbuild/linux-arm@0.25.1': optional: true - '@esbuild/linux-ia32@0.25.0': + '@esbuild/linux-ia32@0.25.1': optional: true - '@esbuild/linux-loong64@0.25.0': + '@esbuild/linux-loong64@0.25.1': optional: true - '@esbuild/linux-mips64el@0.25.0': + '@esbuild/linux-mips64el@0.25.1': optional: true - '@esbuild/linux-ppc64@0.25.0': + '@esbuild/linux-ppc64@0.25.1': optional: true - '@esbuild/linux-riscv64@0.25.0': + '@esbuild/linux-riscv64@0.25.1': optional: true - '@esbuild/linux-s390x@0.25.0': + '@esbuild/linux-s390x@0.25.1': optional: true - '@esbuild/linux-x64@0.25.0': + '@esbuild/linux-x64@0.25.1': optional: true - '@esbuild/netbsd-arm64@0.25.0': + '@esbuild/netbsd-arm64@0.25.1': optional: true - '@esbuild/netbsd-x64@0.25.0': + '@esbuild/netbsd-x64@0.25.1': optional: true - '@esbuild/openbsd-arm64@0.25.0': + '@esbuild/openbsd-arm64@0.25.1': optional: true - '@esbuild/openbsd-x64@0.25.0': + '@esbuild/openbsd-x64@0.25.1': optional: true - '@esbuild/sunos-x64@0.25.0': + '@esbuild/sunos-x64@0.25.1': optional: true - '@esbuild/win32-arm64@0.25.0': + '@esbuild/win32-arm64@0.25.1': optional: true - '@esbuild/win32-ia32@0.25.0': + '@esbuild/win32-ia32@0.25.1': optional: true - '@esbuild/win32-x64@0.25.0': + '@esbuild/win32-x64@0.25.1': optional: true '@eslint-community/eslint-utils@4.4.1(eslint@9.21.0)': @@ -8933,12 +8892,12 @@ snapshots: '@types/yargs': 17.0.33 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.2)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.5.0(typescript@5.8.2)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': dependencies: glob: 10.4.5 magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.8.2) - vite: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + vite: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) optionalDependencies: typescript: 5.8.2 @@ -9373,69 +9332,72 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@rollup/pluginutils@5.1.4(rollup@4.34.8)': + '@rollup/pluginutils@5.1.4(rollup@4.37.0)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.34.8 + rollup: 4.37.0 + + '@rollup/rollup-android-arm-eabi@4.37.0': + optional: true - '@rollup/rollup-android-arm-eabi@4.34.8': + '@rollup/rollup-android-arm64@4.37.0': optional: true - '@rollup/rollup-android-arm64@4.34.8': + '@rollup/rollup-darwin-arm64@4.37.0': optional: true - '@rollup/rollup-darwin-arm64@4.34.8': + '@rollup/rollup-darwin-x64@4.37.0': optional: true - '@rollup/rollup-darwin-x64@4.34.8': + '@rollup/rollup-freebsd-arm64@4.37.0': optional: true - '@rollup/rollup-freebsd-arm64@4.34.8': + '@rollup/rollup-freebsd-x64@4.37.0': optional: true - '@rollup/rollup-freebsd-x64@4.34.8': + '@rollup/rollup-linux-arm-gnueabihf@4.37.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + '@rollup/rollup-linux-arm-musleabihf@4.37.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.34.8': + '@rollup/rollup-linux-arm64-gnu@4.37.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.34.8': + '@rollup/rollup-linux-arm64-musl@4.37.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.34.8': + '@rollup/rollup-linux-loongarch64-gnu@4.37.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + '@rollup/rollup-linux-powerpc64le-gnu@4.37.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + '@rollup/rollup-linux-riscv64-gnu@4.37.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.34.8': + '@rollup/rollup-linux-riscv64-musl@4.37.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.34.8': + '@rollup/rollup-linux-s390x-gnu@4.37.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.34.8': + '@rollup/rollup-linux-x64-gnu@4.37.0': optional: true - '@rollup/rollup-linux-x64-musl@4.34.8': + '@rollup/rollup-linux-x64-musl@4.37.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.34.8': + '@rollup/rollup-win32-arm64-msvc@4.37.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.34.8': + '@rollup/rollup-win32-ia32-msvc@4.37.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.34.8': + '@rollup/rollup-win32-x64-msvc@4.37.0': optional: true '@sideway/address@4.1.5': @@ -9478,13 +9440,13 @@ snapshots: dependencies: storybook: 8.6.3(prettier@3.5.3) - '@storybook/builder-vite@8.6.3(storybook@8.6.3(prettier@3.5.3))(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': + '@storybook/builder-vite@8.6.3(storybook@8.6.3(prettier@3.5.3))(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': dependencies: '@storybook/csf-plugin': 8.6.3(storybook@8.6.3(prettier@3.5.3)) browser-assert: 1.2.1 storybook: 8.6.3(prettier@3.5.3) ts-dedent: 2.2.0 - vite: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + vite: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) '@storybook/components@8.6.3(storybook@8.6.3(prettier@3.5.3))': dependencies: @@ -9495,8 +9457,8 @@ snapshots: '@storybook/theming': 8.6.3(storybook@8.6.3(prettier@3.5.3)) better-opn: 3.0.2 browser-assert: 1.2.1 - esbuild: 0.25.0 - esbuild-register: 3.6.0(esbuild@0.25.0) + esbuild: 0.25.1 + esbuild-register: 3.6.0(esbuild@0.25.1) jsdoc-type-pratt-parser: 4.1.0 process: 0.11.10 recast: 0.23.11 @@ -9536,11 +9498,11 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.6.3(prettier@3.5.3) - '@storybook/react-vite@8.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.34.8)(storybook@8.6.3(prettier@3.5.3))(typescript@5.8.2)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': + '@storybook/react-vite@8.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.37.0)(storybook@8.6.3(prettier@3.5.3))(typescript@5.8.2)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.8.2)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) - '@rollup/pluginutils': 5.1.4(rollup@4.34.8) - '@storybook/builder-vite': 8.6.3(storybook@8.6.3(prettier@3.5.3))(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@5.8.2)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) + '@rollup/pluginutils': 5.1.4(rollup@4.37.0) + '@storybook/builder-vite': 8.6.3(storybook@8.6.3(prettier@3.5.3))(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)) '@storybook/react': 8.6.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.3(prettier@3.5.3))(typescript@5.8.2) find-up: 5.0.0 magic-string: 0.30.17 @@ -9550,7 +9512,7 @@ snapshots: resolve: 1.22.10 storybook: 8.6.3(prettier@3.5.3) tsconfig-paths: 4.2.0 - vite: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + vite: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) transitivePeerDependencies: - rollup - supports-color @@ -9573,9 +9535,9 @@ snapshots: '@storybook/test-runner@0.22.0(@types/node@20.17.22)(babel-plugin-macros@3.1.0)(storybook@8.6.3(prettier@3.5.3))': dependencies: '@babel/core': 7.26.10 - '@babel/generator': 7.26.9 + '@babel/generator': 7.26.10 '@babel/template': 7.26.9 - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 '@jest/types': 29.6.3 '@storybook/csf': 0.1.13 '@swc/core': 1.11.5 @@ -9764,24 +9726,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 '@types/babel__generator': 7.6.3 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.3': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 '@types/babel__template@7.4.1': dependencies: - '@babel/parser': 7.26.9 - '@babel/types': 7.26.9 + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 '@types/cacheable-request@6.0.2': dependencies: @@ -10106,10 +10068,10 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react-swc@3.8.0(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': + '@vitejs/plugin-react-swc@3.8.0(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0))': dependencies: '@swc/core': 1.11.5 - vite: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + vite: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) transitivePeerDependencies: - '@swc/helpers' @@ -11386,15 +11348,15 @@ snapshots: electron-to-chromium@1.5.109: {} - electron-vite@3.0.0(@swc/core@1.11.5)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)): + electron-vite@3.0.0(@swc/core@1.11.5)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)): dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.10) cac: 6.7.14 - esbuild: 0.25.0 + esbuild: 0.25.1 magic-string: 0.30.17 picocolors: 1.1.1 - vite: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + vite: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) optionalDependencies: '@swc/core': 1.11.5 transitivePeerDependencies: @@ -11541,40 +11503,40 @@ snapshots: es6-error@4.1.1: {} - esbuild-register@3.6.0(esbuild@0.25.0): + esbuild-register@3.6.0(esbuild@0.25.1): dependencies: debug: 4.4.0 - esbuild: 0.25.0 + esbuild: 0.25.1 transitivePeerDependencies: - supports-color - esbuild@0.25.0: + esbuild@0.25.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.0 - '@esbuild/android-arm': 0.25.0 - '@esbuild/android-arm64': 0.25.0 - '@esbuild/android-x64': 0.25.0 - '@esbuild/darwin-arm64': 0.25.0 - '@esbuild/darwin-x64': 0.25.0 - '@esbuild/freebsd-arm64': 0.25.0 - '@esbuild/freebsd-x64': 0.25.0 - '@esbuild/linux-arm': 0.25.0 - '@esbuild/linux-arm64': 0.25.0 - '@esbuild/linux-ia32': 0.25.0 - '@esbuild/linux-loong64': 0.25.0 - '@esbuild/linux-mips64el': 0.25.0 - '@esbuild/linux-ppc64': 0.25.0 - '@esbuild/linux-riscv64': 0.25.0 - '@esbuild/linux-s390x': 0.25.0 - '@esbuild/linux-x64': 0.25.0 - '@esbuild/netbsd-arm64': 0.25.0 - '@esbuild/netbsd-x64': 0.25.0 - '@esbuild/openbsd-arm64': 0.25.0 - '@esbuild/openbsd-x64': 0.25.0 - '@esbuild/sunos-x64': 0.25.0 - '@esbuild/win32-arm64': 0.25.0 - '@esbuild/win32-ia32': 0.25.0 - '@esbuild/win32-x64': 0.25.0 + '@esbuild/aix-ppc64': 0.25.1 + '@esbuild/android-arm': 0.25.1 + '@esbuild/android-arm64': 0.25.1 + '@esbuild/android-x64': 0.25.1 + '@esbuild/darwin-arm64': 0.25.1 + '@esbuild/darwin-x64': 0.25.1 + '@esbuild/freebsd-arm64': 0.25.1 + '@esbuild/freebsd-x64': 0.25.1 + '@esbuild/linux-arm': 0.25.1 + '@esbuild/linux-arm64': 0.25.1 + '@esbuild/linux-ia32': 0.25.1 + '@esbuild/linux-loong64': 0.25.1 + '@esbuild/linux-mips64el': 0.25.1 + '@esbuild/linux-ppc64': 0.25.1 + '@esbuild/linux-riscv64': 0.25.1 + '@esbuild/linux-s390x': 0.25.1 + '@esbuild/linux-x64': 0.25.1 + '@esbuild/netbsd-arm64': 0.25.1 + '@esbuild/netbsd-x64': 0.25.1 + '@esbuild/openbsd-arm64': 0.25.1 + '@esbuild/openbsd-x64': 0.25.1 + '@esbuild/sunos-x64': 0.25.1 + '@esbuild/win32-arm64': 0.25.1 + '@esbuild/win32-ia32': 0.25.1 + '@esbuild/win32-x64': 0.25.1 escalade@3.2.0: {} @@ -12872,10 +12834,10 @@ snapshots: jest-snapshot@29.7.0: dependencies: '@babel/core': 7.26.10 - '@babel/generator': 7.26.9 + '@babel/generator': 7.26.10 '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) - '@babel/types': 7.26.9 + '@babel/types': 7.26.10 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -13615,7 +13577,7 @@ snapshots: mute-stream@2.0.0: {} - nanoid@3.3.8: {} + nanoid@3.3.11: {} natural-compare@1.4.0: {} @@ -13956,13 +13918,13 @@ snapshots: postcss@8.4.49: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.5.3: dependencies: - nanoid: 3.3.8 + nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -14078,8 +14040,8 @@ snapshots: react-docgen@7.1.1: dependencies: '@babel/core': 7.26.10 - '@babel/traverse': 7.26.9 - '@babel/types': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 '@types/doctrine': 0.0.9 @@ -14379,38 +14341,39 @@ snapshots: sprintf-js: 1.1.3 optional: true - rollup-plugin-visualizer@5.14.0(rollup@4.34.8): + rollup-plugin-visualizer@5.14.0(rollup@4.37.0): dependencies: open: 8.4.2 picomatch: 4.0.2 source-map: 0.7.4 yargs: 17.7.2 optionalDependencies: - rollup: 4.34.8 + rollup: 4.37.0 - rollup@4.34.8: + rollup@4.37.0: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.34.8 - '@rollup/rollup-android-arm64': 4.34.8 - '@rollup/rollup-darwin-arm64': 4.34.8 - '@rollup/rollup-darwin-x64': 4.34.8 - '@rollup/rollup-freebsd-arm64': 4.34.8 - '@rollup/rollup-freebsd-x64': 4.34.8 - '@rollup/rollup-linux-arm-gnueabihf': 4.34.8 - '@rollup/rollup-linux-arm-musleabihf': 4.34.8 - '@rollup/rollup-linux-arm64-gnu': 4.34.8 - '@rollup/rollup-linux-arm64-musl': 4.34.8 - '@rollup/rollup-linux-loongarch64-gnu': 4.34.8 - '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8 - '@rollup/rollup-linux-riscv64-gnu': 4.34.8 - '@rollup/rollup-linux-s390x-gnu': 4.34.8 - '@rollup/rollup-linux-x64-gnu': 4.34.8 - '@rollup/rollup-linux-x64-musl': 4.34.8 - '@rollup/rollup-win32-arm64-msvc': 4.34.8 - '@rollup/rollup-win32-ia32-msvc': 4.34.8 - '@rollup/rollup-win32-x64-msvc': 4.34.8 + '@rollup/rollup-android-arm-eabi': 4.37.0 + '@rollup/rollup-android-arm64': 4.37.0 + '@rollup/rollup-darwin-arm64': 4.37.0 + '@rollup/rollup-darwin-x64': 4.37.0 + '@rollup/rollup-freebsd-arm64': 4.37.0 + '@rollup/rollup-freebsd-x64': 4.37.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.37.0 + '@rollup/rollup-linux-arm-musleabihf': 4.37.0 + '@rollup/rollup-linux-arm64-gnu': 4.37.0 + '@rollup/rollup-linux-arm64-musl': 4.37.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.37.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.37.0 + '@rollup/rollup-linux-riscv64-gnu': 4.37.0 + '@rollup/rollup-linux-riscv64-musl': 4.37.0 + '@rollup/rollup-linux-s390x-gnu': 4.37.0 + '@rollup/rollup-linux-x64-gnu': 4.37.0 + '@rollup/rollup-linux-x64-musl': 4.37.0 + '@rollup/rollup-win32-arm64-msvc': 4.37.0 + '@rollup/rollup-win32-ia32-msvc': 4.37.0 + '@rollup/rollup-win32-x64-msvc': 4.37.0 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -15194,26 +15157,26 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-plugin-wasm@3.4.1(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)): + vite-plugin-wasm@3.4.1(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)): dependencies: - vite: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + vite: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) - vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)): + vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0)): dependencies: debug: 4.4.0 globrex: 0.1.2 tsconfck: 3.1.0(typescript@5.8.2) optionalDependencies: - vite: 6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) + vite: 6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0) transitivePeerDependencies: - supports-color - typescript - vite@6.2.0(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0): + vite@6.2.3(@types/node@20.17.22)(terser@5.31.1)(yaml@2.7.0): dependencies: - esbuild: 0.25.0 + esbuild: 0.25.1 postcss: 8.5.3 - rollup: 4.34.8 + rollup: 4.37.0 optionalDependencies: '@types/node': 20.17.22 fsevents: 2.3.3 From 58bd4f367b14f00869cf3e029a3d4966b2b670f8 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Thu, 27 Mar 2025 00:20:52 +0100 Subject: [PATCH 15/35] e --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index 9366331762ddc..37f68780b393a 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 9366331762ddc2cdb323fa18e3fdb8d2a2594bb4 +Subproject commit 37f68780b393aea947d77230ce01140cc8ab8972 From bbc1073042067f3a5c3e0f9027a4b89da454f8d7 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Thu, 27 Mar 2025 00:21:25 +0100 Subject: [PATCH 16/35] e --- e | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e b/e index 37f68780b393a..9366331762ddc 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 37f68780b393aea947d77230ce01140cc8ab8972 +Subproject commit 9366331762ddc2cdb323fa18e3fdb8d2a2594bb4 From 0a1e727aaec6c6490e8d4532c71996d10b5595ac Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Thu, 27 Mar 2025 00:43:37 +0100 Subject: [PATCH 17/35] lint --- .../teleport/src/DesktopSession/DesktopSession.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx index e5220e121f9be..f1d4d9dd993b1 100644 --- a/web/packages/teleport/src/DesktopSession/DesktopSession.tsx +++ b/web/packages/teleport/src/DesktopSession/DesktopSession.tsx @@ -35,7 +35,12 @@ import TdpClientCanvas from 'teleport/components/TdpClientCanvas'; import cfg, { UrlDesktopParams } from 'teleport/config'; import { KeyboardHandler } from 'teleport/DesktopSession/KeyboardHandler'; import { AuthenticatedWebSocket } from 'teleport/lib/AuthenticatedWebSocket'; -import {ButtonState, ScrollAxis, TdpClient, TdpClientEvent} from 'teleport/lib/tdp'; +import { + ButtonState, + ScrollAxis, + TdpClient, + TdpClientEvent, +} from 'teleport/lib/tdp'; import { useListener } from 'teleport/lib/tdp/client'; import { shouldShowMfaPrompt, useMfaEmitter } from 'teleport/lib/useMfa'; import { getHostName } from 'teleport/services/api'; From aea59ed4b5e544fd78e404e8e485151a919163fe Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 8 Apr 2025 22:04:58 +0200 Subject: [PATCH 18/35] rework UI after merge --- .../components/DesktopSession/DesktopSession.tsx | 13 +++++++++++++ .../shared/components/DesktopSession/TopBar.tsx | 4 ++-- web/packages/shared/libs/tdp/client.ts | 7 ++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.tsx index fd24992c90f4f..9da45c2f18f1a 100644 --- a/web/packages/shared/components/DesktopSession/DesktopSession.tsx +++ b/web/packages/shared/components/DesktopSession/DesktopSession.tsx @@ -48,6 +48,7 @@ import useDesktopSession, { isSharingClipboard, isSharingDirectory, } from './useDesktopSession'; +import {Latency} from "shared/components/LatencyDiagnostic"; export interface DesktopSessionProps { client: TdpClient; @@ -204,6 +205,17 @@ export function DesktopSession({ useListener(client.onReset, canvasRendererRef.current?.clear); useListener(client.onScreenSpec, canvasRendererRef.current?.setResolution); + const [latencyStats, setLatencyStats] = useState(); + useListener( + client.onLatencyStats, + useCallback(stats => { + setLatencyStats({ + client: stats.browserLatency, + server: stats.desktopLatency, + }); + }, []) + ); + const shouldConnect = aclAttempt.status === 'success' && anotherDesktopActiveAttempt.status === 'success' && @@ -339,6 +351,7 @@ export function DesktopSession({ onCtrlAltDel={handleCtrlAltDel} alerts={alerts} onRemoveAlert={onRemoveAlert} + latency={latencyStats} /> {screenState.state === 'another-session-active' && ( diff --git a/web/packages/shared/components/DesktopSession/TopBar.tsx b/web/packages/shared/components/DesktopSession/TopBar.tsx index 4d00e01dbde33..b636c21e83291 100644 --- a/web/packages/shared/components/DesktopSession/TopBar.tsx +++ b/web/packages/shared/components/DesktopSession/TopBar.tsx @@ -62,8 +62,8 @@ export default function TopBar(props: Props) { - - {latency && } + + {latency && } this.off(TdpClientEvent.TDP_CLIENT_SCREEN_SPEC, listener); }; + onLatencyStats = (listener: (stats: LatencyStats) => void) => { + this.on(TdpClientEvent.LATENCY_STATS, listener); + return () => this.off(TdpClientEvent.LATENCY_STATS, listener); + }; + private async initWasm() { // select the wasm log level let wasmLogLevel = LogType.OFF; From 8b80181c00cf9bfeb48e479f7260d4e621fd6d78 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 8 Apr 2025 22:07:51 +0200 Subject: [PATCH 19/35] Rename fields in backend --- lib/srv/desktop/tdp/proto.go | 10 +++++----- lib/web/desktop.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/srv/desktop/tdp/proto.go b/lib/srv/desktop/tdp/proto.go index a93018da2be0c..c753b48f9fcf2 100644 --- a/lib/srv/desktop/tdp/proto.go +++ b/lib/srv/desktop/tdp/proto.go @@ -1636,17 +1636,17 @@ func decodeSharedDirectoryTruncateResponse(in io.Reader) (SharedDirectoryTruncat return res, err } -// LatencyStats is used to report the latency of the connection(s) to the web UI. +// LatencyStats is used to report the latency of the connection(s) to the client. type LatencyStats struct { - BrowserLatency uint32 - DesktopLatency uint32 + ClientLatency uint32 + ServerLatency uint32 } func (l LatencyStats) Encode() ([]byte, error) { buf := new(bytes.Buffer) buf.WriteByte(byte(TypeLatencyStats)) - writeUint32(buf, l.BrowserLatency) - writeUint32(buf, l.DesktopLatency) + writeUint32(buf, l.ClientLatency) + writeUint32(buf, l.ServerLatency) return buf.Bytes(), nil } diff --git a/lib/web/desktop.go b/lib/web/desktop.go index 032cb0c353a9e..37726c947ac21 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -586,8 +586,8 @@ func monitorDesktopLatency(ctx context.Context, ch chan<- tdp.Message, clock clo ServerPinger: pinger, Reporter: latency.ReporterFunc(func(ctx context.Context, stats latency.Statistics) error { ch <- tdp.LatencyStats{ - BrowserLatency: uint32(stats.Client), - DesktopLatency: uint32(stats.Server), + ClientLatency: uint32(stats.Client), + ServerLatency: uint32(stats.Server), } return nil }), @@ -640,7 +640,7 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger, vers continue } if ls, ok := msg.(tdp.LatencyStats); ok { - log.InfoContext(ctx, "sending latency stats", "browser", ls.BrowserLatency, "desktop", ls.DesktopLatency) + log.DebugContext(ctx, "sending latency stats", "client", ls.ClientLatency, "server", ls.ServerLatency) } encoded, err := msg.Encode() if err != nil { From 84914866d0e36cddb0d93e935b6c4866c80c867c Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 8 Apr 2025 22:15:45 +0200 Subject: [PATCH 20/35] prettier --- .../shared/components/DesktopSession/DesktopSession.tsx | 2 +- web/packages/shared/components/DesktopSession/TopBar.tsx | 2 +- web/packages/shared/libs/tdp/client.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.tsx index 9da45c2f18f1a..13e0f1c80a569 100644 --- a/web/packages/shared/components/DesktopSession/DesktopSession.tsx +++ b/web/packages/shared/components/DesktopSession/DesktopSession.tsx @@ -30,6 +30,7 @@ import { CanvasRenderer, CanvasRendererRef, } from 'shared/components/CanvasRenderer'; +import { Latency } from 'shared/components/LatencyDiagnostic'; import { Attempt, makeSuccessAttempt, useAsync } from 'shared/hooks/useAsync'; import { ButtonState, @@ -48,7 +49,6 @@ import useDesktopSession, { isSharingClipboard, isSharingDirectory, } from './useDesktopSession'; -import {Latency} from "shared/components/LatencyDiagnostic"; export interface DesktopSessionProps { client: TdpClient; diff --git a/web/packages/shared/components/DesktopSession/TopBar.tsx b/web/packages/shared/components/DesktopSession/TopBar.tsx index b636c21e83291..d3b299509b7cf 100644 --- a/web/packages/shared/components/DesktopSession/TopBar.tsx +++ b/web/packages/shared/components/DesktopSession/TopBar.tsx @@ -63,7 +63,7 @@ export default function TopBar(props: Props) { - {latency && } + {latency && } Date: Thu, 10 Apr 2025 19:43:46 +0200 Subject: [PATCH 21/35] Update web/packages/shared/components/DesktopSession/TopBar.tsx Co-authored-by: Grzegorz Zdunek --- web/packages/shared/components/DesktopSession/TopBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/shared/components/DesktopSession/TopBar.tsx b/web/packages/shared/components/DesktopSession/TopBar.tsx index d3b299509b7cf..4d00e01dbde33 100644 --- a/web/packages/shared/components/DesktopSession/TopBar.tsx +++ b/web/packages/shared/components/DesktopSession/TopBar.tsx @@ -62,7 +62,7 @@ export default function TopBar(props: Props) { - + {latency && } Date: Thu, 10 Apr 2025 19:45:12 +0200 Subject: [PATCH 22/35] review comments --- web/packages/shared/libs/tdp/codec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/packages/shared/libs/tdp/codec.ts b/web/packages/shared/libs/tdp/codec.ts index d709183057dd5..1dc94efc54dbb 100644 --- a/web/packages/shared/libs/tdp/codec.ts +++ b/web/packages/shared/libs/tdp/codec.ts @@ -324,10 +324,10 @@ export enum FileType { Directory = 1, } -// | message type (35) | browser_latency uint32 | desktop_latency uint32 | +// | message type (35) | client_latency uint32 | server_latency uint32 | export type LatencyStats = { - browserLatency: number; - desktopLatency: number; + client: number; + server: number; }; function toSharedDirectoryErrCode(errCode: number): SharedDirectoryErrCode { @@ -1133,8 +1133,8 @@ export default class Codec { bufOffset += UINT_32_LEN; return { - browserLatency, - desktopLatency, + client: browserLatency, + server: desktopLatency, }; } From 46c85fee6fcfb3ec2e25c1b4dda4798d21a91f07 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Thu, 10 Apr 2025 19:52:19 +0200 Subject: [PATCH 23/35] fix ui --- .../shared/components/DesktopSession/DesktopSession.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/packages/shared/components/DesktopSession/DesktopSession.tsx b/web/packages/shared/components/DesktopSession/DesktopSession.tsx index 13e0f1c80a569..ba0791878ab4e 100644 --- a/web/packages/shared/components/DesktopSession/DesktopSession.tsx +++ b/web/packages/shared/components/DesktopSession/DesktopSession.tsx @@ -210,8 +210,8 @@ export function DesktopSession({ client.onLatencyStats, useCallback(stats => { setLatencyStats({ - client: stats.browserLatency, - server: stats.desktopLatency, + client: stats.client, + server: stats.server, }); }, []) ); From 512175d7a04c42e6e7e360693fea45724cb84583 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 15 Apr 2025 21:06:39 +0200 Subject: [PATCH 24/35] add env var to disable windows desktop "ping" --- lib/srv/desktop/rdp/rdpclient/client.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index 81f65c51c7beb..fadb6f0ced379 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -435,9 +435,11 @@ func (c *Client) startInputStreaming(stopCh chan struct{}) error { } if m, ok := msg.(tdp.Ping); ok { - conn, err := net.Dial("tcp", c.cfg.Addr) - if err == nil { - conn.Close() + if os.Getenv("TELEPORT_DISABLE_WINDOWS_DESKTOP_PING") != "true" { + conn, err := net.Dial("tcp", c.cfg.Addr) + if err == nil { + conn.Close() + } } c.cfg.Conn.WriteMessage(m) continue From b2ba75fd63bb7a1a78f4889c36d7e5a2671939b8 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Wed, 16 Apr 2025 22:32:00 +0200 Subject: [PATCH 25/35] review comment --- lib/srv/desktop/rdp/rdpclient/client.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index fadb6f0ced379..84be1ac2a17c9 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -77,6 +77,7 @@ import ( "net" "os" "runtime/cgo" + "strconv" "sync" "sync/atomic" "time" @@ -415,6 +416,9 @@ func (c *Client) startInputStreaming(stopCh chan struct{}) error { c.cfg.Logger.InfoContext(context.Background(), "TDP input streaming starting") defer c.cfg.Logger.InfoContext(context.Background(), "TDP input streaming finished") + // we will disable ping only if the env var is truthy + disableDesktopPing, _ := strconv.ParseBool(os.Getenv("TELEPORT_DISABLE_WINDOWS_DESKTOP_PING")) + var withheldResize *tdp.ClientScreenSpec for { select { @@ -435,7 +439,7 @@ func (c *Client) startInputStreaming(stopCh chan struct{}) error { } if m, ok := msg.(tdp.Ping); ok { - if os.Getenv("TELEPORT_DISABLE_WINDOWS_DESKTOP_PING") != "true" { + if !disableDesktopPing { conn, err := net.Dial("tcp", c.cfg.Addr) if err == nil { conn.Close() From 31d5ee0e9b8a12c32455e000122f61bced986ea1 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Wed, 16 Apr 2025 23:03:16 +0200 Subject: [PATCH 26/35] review comment --- lib/srv/desktop/rdp/rdpclient/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index 84be1ac2a17c9..be0891680e58b 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -417,7 +417,7 @@ func (c *Client) startInputStreaming(stopCh chan struct{}) error { defer c.cfg.Logger.InfoContext(context.Background(), "TDP input streaming finished") // we will disable ping only if the env var is truthy - disableDesktopPing, _ := strconv.ParseBool(os.Getenv("TELEPORT_DISABLE_WINDOWS_DESKTOP_PING")) + disableDesktopPing, _ := strconv.ParseBool(os.Getenv("TELEPORT_DISABLE_DESKTOP_LATENCY_DETECTOR_PING")) var withheldResize *tdp.ClientScreenSpec for { From de874d70b217d2a1c35bae5a306b275765418c88 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Fri, 18 Apr 2025 16:49:47 +0200 Subject: [PATCH 27/35] Update lib/web/desktop.go Co-authored-by: Zac Bergquist --- lib/web/desktop.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web/desktop.go b/lib/web/desktop.go index 37726c947ac21..f69b0a1915f7c 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -563,7 +563,7 @@ func (d desktopPinger) Ping(ctx context.Context) error { } for { select { - case p := <-d.ch: + case pong := <-d.ch: if p.UUID == ping.UUID { return nil } From 464132444d7ba1a6c4123e86a57f66795ed0ff71 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 22 Apr 2025 09:14:39 +0200 Subject: [PATCH 28/35] Refactor latency monitoring --- lib/web/desktop.go | 40 ++++++++--------------------- lib/web/latency.go | 62 +++++++++++++++++++++++++++++++++++++++++++++ lib/web/terminal.go | 23 +++++++++++++---- 3 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 lib/web/latency.go diff --git a/lib/web/desktop.go b/lib/web/desktop.go index f69b0a1915f7c..86bd71df16dd1 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -564,7 +564,7 @@ func (d desktopPinger) Ping(ctx context.Context) error { for { select { case pong := <-d.ch: - if p.UUID == ping.UUID { + if pong.UUID == ping.UUID { return nil } case <-ctx.Done(): @@ -573,33 +573,6 @@ func (d desktopPinger) Ping(ctx context.Context) error { } } -// TODO(zmb3): combine with monitorSessionLatency or rename -func monitorDesktopLatency(ctx context.Context, ch chan<- tdp.Message, clock clockwork.Clock, ws *websocket.Conn, pinger desktopPinger) error { - wsPinger, err := latency.NewWebsocketPinger(clock, ws) - if err != nil { - return trace.Wrap(err, "creating websocket pinger") - } - - monitor, err := latency.NewMonitor(latency.MonitorConfig{ - Clock: clock, - ClientPinger: wsPinger, - ServerPinger: pinger, - Reporter: latency.ReporterFunc(func(ctx context.Context, stats latency.Statistics) error { - ch <- tdp.LatencyStats{ - ClientLatency: uint32(stats.Client), - ServerLatency: uint32(stats.Server), - } - return nil - }), - }) - if err != nil { - return trace.Wrap(err, "creating latency monitor") - } - - monitor.Run(ctx) - return nil -} - // proxyWebsocketConn does a bidrectional copy between the websocket // connection to the browser (ws) and the mTLS connection to Windows // Desktop Serivce (wds) @@ -628,7 +601,16 @@ func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger, vers ch: pings, } - go monitorDesktopLatency(ctx, tdpMessagesToSend, clockwork.NewRealClock(), ws, pinger) + go monitorLatency(ctx, clockwork.NewRealClock(), ws, pinger, + latency.ReporterFunc(func(ctx context.Context, stats latency.Statistics) error { + tdpMessagesToSend <- tdp.LatencyStats{ + ClientLatency: uint32(stats.Client), + ServerLatency: uint32(stats.Server), + } + return nil + }), + ) + } // run a goroutine to pick TDP messages up from a channel and send diff --git a/lib/web/latency.go b/lib/web/latency.go new file mode 100644 index 0000000000000..4c8ff9c4e476f --- /dev/null +++ b/lib/web/latency.go @@ -0,0 +1,62 @@ +/* + * * + * * Teleport + * * Copyright (C) 2024 Gravitational, Inc. + * * + * * This program is free software: you can redistribute it and/or modify + * * it under the terms of the GNU Affero General Public License as published by + * * the Free Software Foundation, either version 3 of the License, or + * * (at your option) any later version. + * * + * * This program is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU Affero General Public License for more details. + * * + * * You should have received a copy of the GNU Affero General Public License + * * along with this program. If not, see . + * + */ + +package web + +import ( + "context" + + "github.com/gravitational/teleport/lib/utils/diagnostics/latency" + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" +) + +// monitorLatency implementes the Web UI's latency detector. +// It runs as long as the provided context has not expired. +// +// The latency of the provided websocket is monitored automatically, +// and the latency to the target endpoint is monitored with the provided pinger. +// The results of the latency calculation are reported to the web UI +// with the provided reporter. +func monitorLatency( + ctx context.Context, + clock clockwork.Clock, + ws latency.WebSocket, + endpointPinger latency.Pinger, + reporter latency.Reporter, +) error { + wsPinger, err := latency.NewWebsocketPinger(clock, ws) + if err != nil { + return trace.Wrap(err, "creating websocket pinger") + } + + monitor, err := latency.NewMonitor(latency.MonitorConfig{ + ClientPinger: wsPinger, + ServerPinger: endpointPinger, + Reporter: reporter, + Clock: clock, + }) + if err != nil { + return trace.Wrap(err, "creating latency monitor") + } + + monitor.Run(ctx) + return nil +} diff --git a/lib/web/terminal.go b/lib/web/terminal.go index ec38deb049dd1..47829e849ce93 100644 --- a/lib/web/terminal.go +++ b/lib/web/terminal.go @@ -845,11 +845,24 @@ func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.Telepor monitorCtx, monitorCancel := context.WithCancel(ctx) defer monitorCancel() - go func() { - if err := monitorSessionLatency(monitorCtx, t.clock, t.stream.WSStream, nc.Client); err != nil { - t.logger.WarnContext(monitorCtx, "failure monitoring session latency", "error", err) - } - }() + + sshPinger, err := latency.NewSSHPinger(nc.Client) + if err != nil { + t.logger.WarnContext(monitorCtx, "failure monitoring session latency", "error", err) + } else { + go monitorLatency(monitorCtx, t.clock, t.stream.WSStream, sshPinger, + latency.ReporterFunc( + func(ctx context.Context, statistics latency.Statistics) error { + return trace.Wrap( + t.stream.WSStream.WriteLatency(terminal.SSHSessionLatencyStats{ + WebSocket: statistics.Client, + SSH: statistics.Server, + }), + ) + }, + ), + ) + } sessionDataSent := make(chan struct{}) // If we are joining a session, send the session data right away, we From 4eb3c0ad3f1027d932ae849448cd097650449604 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 22 Apr 2025 21:37:40 +0200 Subject: [PATCH 29/35] fix spelling --- lib/web/latency.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web/latency.go b/lib/web/latency.go index 4c8ff9c4e476f..6e004eba70312 100644 --- a/lib/web/latency.go +++ b/lib/web/latency.go @@ -28,7 +28,7 @@ import ( "github.com/jonboulle/clockwork" ) -// monitorLatency implementes the Web UI's latency detector. +// monitorLatency implements the Web UI's latency detector. // It runs as long as the provided context has not expired. // // The latency of the provided websocket is monitored automatically, From bf49275d8538bed3704f4ff649ef626a74fe4677 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 22 Apr 2025 21:53:08 +0200 Subject: [PATCH 30/35] remove monitorSessionLatency --- lib/web/terminal.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/lib/web/terminal.go b/lib/web/terminal.go index d77a47fca385f..860a997cdd77e 100644 --- a/lib/web/terminal.go +++ b/lib/web/terminal.go @@ -778,36 +778,6 @@ func (t *sshBaseHandler) connectToHost(ctx context.Context, ws terminal.WSConn, } } -func monitorSessionLatency(ctx context.Context, clock clockwork.Clock, stream *terminal.WSStream, sshClient *tracessh.Client) error { - wsPinger, err := latency.NewWebsocketPinger(clock, stream) - if err != nil { - return trace.Wrap(err, "creating websocket pinger") - } - - sshPinger, err := latency.NewSSHPinger(sshClient) - if err != nil { - return trace.Wrap(err, "creating ssh pinger") - } - - monitor, err := latency.NewMonitor(latency.MonitorConfig{ - ClientPinger: wsPinger, - ServerPinger: sshPinger, - Reporter: latency.ReporterFunc(func(ctx context.Context, statistics latency.Statistics) error { - return trace.Wrap(stream.WriteLatency(terminal.SSHSessionLatencyStats{ - WebSocket: statistics.Client, - SSH: statistics.Server, - })) - }), - Clock: clock, - }) - if err != nil { - return trace.Wrap(err, "creating latency monitor") - } - - monitor.Run(ctx) - return nil -} - // streamTerminal opens an SSH connection to the remote host and streams // events back to the web client. func (t *TerminalHandler) streamTerminal(ctx context.Context, tc *client.TeleportClient) { From 3a30f62da613713e16704eb544074586652b235d Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 22 Apr 2025 21:54:29 +0200 Subject: [PATCH 31/35] gci --- lib/web/latency.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/web/latency.go b/lib/web/latency.go index 6e004eba70312..822ab3249ce4a 100644 --- a/lib/web/latency.go +++ b/lib/web/latency.go @@ -23,9 +23,10 @@ package web import ( "context" - "github.com/gravitational/teleport/lib/utils/diagnostics/latency" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" + + "github.com/gravitational/teleport/lib/utils/diagnostics/latency" ) // monitorLatency implements the Web UI's latency detector. From da25c9967495a9ffe3aa404a7f5f92c4fb82b529 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Wed, 23 Apr 2025 22:45:49 +0200 Subject: [PATCH 32/35] review comment --- lib/srv/desktop/rdp/rdpclient/client.go | 9 +++++++-- lib/web/desktop.go | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/srv/desktop/rdp/rdpclient/client.go b/lib/srv/desktop/rdp/rdpclient/client.go index be0891680e58b..5439ba2296301 100644 --- a/lib/srv/desktop/rdp/rdpclient/client.go +++ b/lib/srv/desktop/rdp/rdpclient/client.go @@ -437,15 +437,20 @@ func (c *Client) startInputStreaming(stopCh chan struct{}) error { c.cfg.Logger.WarnContext(context.Background(), "Failed reading TDP input message", "error", err) return err } - if m, ok := msg.(tdp.Ping); ok { + // Upon receiving a ping message, we make a connection + // to the host and send the same message back to the proxy. + // The proxy will then compute the round trip time. if !disableDesktopPing { conn, err := net.Dial("tcp", c.cfg.Addr) if err == nil { conn.Close() } } - c.cfg.Conn.WriteMessage(m) + if err := c.cfg.Conn.WriteMessage(m); err != nil { + c.cfg.Logger.WarnContext(context.Background(), "Failed writing TDP ping message", "error", err) + return err + } continue } diff --git a/lib/web/desktop.go b/lib/web/desktop.go index 86bd71df16dd1..068d65d9ea2db 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -240,7 +240,7 @@ func (h *Handler) createDesktopConnection( // proxyWebsocketConn hangs here until connection is closed handleProxyWebsocketConnErr( ctx, - proxyWebsocketConn(ws, serviceConnTLS, log, version), + proxyWebsocketConn(ctx, ws, serviceConnTLS, log, version), log, ) @@ -576,8 +576,8 @@ func (d desktopPinger) Ping(ctx context.Context) error { // proxyWebsocketConn does a bidrectional copy between the websocket // connection to the browser (ws) and the mTLS connection to Windows // Desktop Serivce (wds) -func proxyWebsocketConn(ws *websocket.Conn, wds net.Conn, log *slog.Logger, version string) error { - ctx, cancel := context.WithCancel(context.Background()) +func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, log *slog.Logger, version string) error { + ctx, cancel := context.WithCancel(ctx) var closeOnce sync.Once close := func() { cancel() From b5b4c6688cd5a0bff68c69edb39370ae9cf1c324 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Wed, 23 Apr 2025 23:25:36 +0200 Subject: [PATCH 33/35] review comment --- lib/web/desktop.go | 50 +++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/lib/web/desktop.go b/lib/web/desktop.go index 068d65d9ea2db..f665aa2006fa4 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -36,6 +36,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/julienschmidt/httprouter" + "golang.org/x/sync/errgroup" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" @@ -586,7 +587,6 @@ func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, l } tdpMessagesToSend := make(chan tdp.Message) - errs := make(chan error, 3) latencySupported, err := utils.MinVerWithoutPreRelease(version, "18.0.0") if err != nil { @@ -613,9 +613,11 @@ func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, l } + var errs errgroup.Group + // run a goroutine to pick TDP messages up from a channel and send // them to the browser - go func() { + errs.Go(func() error { for msg := range tdpMessagesToSend { if ping, ok := msg.(tdp.Ping); ok { pings <- ping @@ -626,25 +628,23 @@ func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, l } encoded, err := msg.Encode() if err != nil { - errs <- err - return + return err } err = ws.WriteMessage(websocket.BinaryMessage, encoded) if utils.IsOKNetworkError(err) { - errs <- nil - return + return err } if err != nil { - errs <- err - return + return err } } - }() + return nil + }) // run a second goroutine to read TDP messages from the Windows // agent and write them to our send channel - go func() { + errs.Go(func() error { defer closeOnce.Do(close) // we avoid using io.Copy here, as we want to make sure @@ -660,8 +660,7 @@ func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, l for { msg, err := tc.ReadMessage() if utils.IsOKNetworkError(err) { - errs <- nil - return + return err } else if err != nil { isFatal := tdp.IsFatalErr(err) severity := tdp.SeverityError @@ -682,16 +681,15 @@ func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, l if sendErr != nil { err = sendErr } - errs <- err - return + return err } tdpMessagesToSend <- msg } - }() + }) // run a goroutine to read TDP messages coming from the browser // and pass them on to the Windows agent - go func() { + errs.Go(func() error { defer closeOnce.Do(close) var buf bytes.Buffer @@ -699,30 +697,22 @@ func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, l _, reader, err := ws.NextReader() switch { case utils.IsOKNetworkError(err): - errs <- nil - return + return err case err != nil: - errs <- err - return + return err } buf.Reset() if _, err := io.Copy(&buf, reader); err != nil { - errs <- err - return + return err } if _, err := wds.Write(buf.Bytes()); err != nil { - errs <- trace.Wrap(err, "sending TDP message to desktop agent") - return + return trace.Wrap(err, "sending TDP message to desktop agent") } } - }() + }) - var retErrs []error - for i := 0; i < 3; i++ { - retErrs = append(retErrs, <-errs) - } - return trace.NewAggregate(retErrs...) + return trace.Wrap(errs.Wait()) } // handleProxyWebsocketConnErr handles the error returned by proxyWebsocketConn by From cc980861b9ae3d4d3e629cad819fd72f492cf202 Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 6 May 2025 21:12:23 +0200 Subject: [PATCH 34/35] Update RFD and version --- lib/web/desktop.go | 2 +- rfd/0037-desktop-access-protocol.md | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/web/desktop.go b/lib/web/desktop.go index f665aa2006fa4..1838f07731618 100644 --- a/lib/web/desktop.go +++ b/lib/web/desktop.go @@ -588,7 +588,7 @@ func proxyWebsocketConn(ctx context.Context, ws *websocket.Conn, wds net.Conn, l tdpMessagesToSend := make(chan tdp.Message) - latencySupported, err := utils.MinVerWithoutPreRelease(version, "18.0.0") + latencySupported, err := utils.MinVerWithoutPreRelease(version, "17.5.0") if err != nil { return trace.Wrap(err) } diff --git a/rfd/0037-desktop-access-protocol.md b/rfd/0037-desktop-access-protocol.md index 49cd7807afa48..77af6b0aba68d 100644 --- a/rfd/0037-desktop-access-protocol.md +++ b/rfd/0037-desktop-access-protocol.md @@ -316,3 +316,22 @@ This message is sent from the client to the server to synchronize the state of k - `0` for \* lock inactive - `1` FOR \* LOCK ACTIVE + +#### 35 - latency stats + +This message is sent from the server to the client to indicate latency +between client and proxy and between proxy and desktop. + +``` +| message type (35) | client_latency uint32 | server_latency uint32 | +``` + +#### 36 - ping + +This message is sent between proxy and Windows desktop service to measure latency between proxy and desktop. +Proxy will send ping message with random UUID and WDS will respond with the same message +after measuring latency to desktop. + +``` +| message type (36) | uuid [16]byte | +``` \ No newline at end of file From b7edc6006866af6030ebaa9d00236e5d4ffcd25e Mon Sep 17 00:00:00 2001 From: Przemko Robakowski Date: Tue, 6 May 2025 21:43:14 +0200 Subject: [PATCH 35/35] fix gaps --- web/packages/shared/components/DesktopSession/TopBar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/packages/shared/components/DesktopSession/TopBar.tsx b/web/packages/shared/components/DesktopSession/TopBar.tsx index 3158c0fb381be..b40d841f8091a 100644 --- a/web/packages/shared/components/DesktopSession/TopBar.tsx +++ b/web/packages/shared/components/DesktopSession/TopBar.tsx @@ -64,7 +64,7 @@ export default function TopBar(props: Props) { {isConnected && ( - + {latency && } - + - +