1
- using System ;
2
- using System . Collections . Generic ;
3
- using System . Collections . ObjectModel ;
4
- using System . ComponentModel ;
5
- using System . Linq ;
6
- using System . Threading ;
7
- using System . Threading . Tasks ;
8
- using Windows . ApplicationModel . DataTransfer ;
9
1
using Coder . Desktop . App . Services ;
10
2
using Coder . Desktop . App . Utils ;
11
3
using Coder . Desktop . CoderSdk ;
18
10
using Microsoft . UI . Xaml ;
19
11
using Microsoft . UI . Xaml . Controls ;
20
12
using Microsoft . UI . Xaml . Controls . Primitives ;
13
+ using System ;
14
+ using System . Collections . Generic ;
15
+ using System . Collections . ObjectModel ;
16
+ using System . ComponentModel ;
17
+ using System . Linq ;
18
+ using System . Text ;
19
+ using System . Threading ;
20
+ using System . Threading . Tasks ;
21
+ using System . Xml . Linq ;
22
+ using Windows . ApplicationModel . DataTransfer ;
21
23
22
24
namespace Coder . Desktop . App . ViewModels ;
23
25
24
26
public interface IAgentViewModelFactory
25
27
{
26
28
public AgentViewModel Create ( IAgentExpanderHost expanderHost , Uuid id , string fullyQualifiedDomainName ,
27
- string hostnameSuffix ,
28
- AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl , string ? workspaceName ) ;
29
-
29
+ string hostnameSuffix , AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl ,
30
+ string ? workspaceName , bool ? didP2p , string ? preferredDerp , TimeSpan ? latency , TimeSpan ? preferredDerpLatency , DateTime ? lastHandshake ) ;
30
31
public AgentViewModel CreateDummy ( IAgentExpanderHost expanderHost , Uuid id ,
31
32
string hostnameSuffix ,
32
33
AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl , string workspaceName ) ;
@@ -40,8 +41,11 @@ public class AgentViewModelFactory(
40
41
{
41
42
public AgentViewModel Create ( IAgentExpanderHost expanderHost , Uuid id , string fullyQualifiedDomainName ,
42
43
string hostnameSuffix ,
43
- AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl , string ? workspaceName )
44
+ AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl ,
45
+ string ? workspaceName , bool ? didP2p , string ? preferredDerp , TimeSpan ? latency , TimeSpan ? preferredDerpLatency ,
46
+ DateTime ? lastHandshake )
44
47
{
48
+ System . Diagnostics . Debug . WriteLine ( $ "Creating agent: { didP2p } { preferredDerp } { latency } { lastHandshake } ") ;
45
49
return new AgentViewModel ( childLogger , coderApiClientFactory , credentialManager , agentAppViewModelFactory ,
46
50
expanderHost , id )
47
51
{
@@ -51,6 +55,11 @@ public AgentViewModel Create(IAgentExpanderHost expanderHost, Uuid id, string fu
51
55
ConnectionStatus = connectionStatus ,
52
56
DashboardBaseUrl = dashboardBaseUrl ,
53
57
WorkspaceName = workspaceName ,
58
+ DidP2p = didP2p ,
59
+ PreferredDerp = preferredDerp ,
60
+ Latency = latency ,
61
+ PreferredDerpLatency = preferredDerpLatency ,
62
+ LastHandshake = lastHandshake ,
54
63
} ;
55
64
}
56
65
@@ -73,10 +82,10 @@ public AgentViewModel CreateDummy(IAgentExpanderHost expanderHost, Uuid id,
73
82
74
83
public enum AgentConnectionStatus
75
84
{
76
- Green ,
77
- Yellow ,
78
- Red ,
79
- Gray ,
85
+ Healthy ,
86
+ Unhealthy ,
87
+ NoRecentHandshake ,
88
+ Offline ,
80
89
}
81
90
82
91
public partial class AgentViewModel : ObservableObject , IModelUpdateable < AgentViewModel >
@@ -182,6 +191,75 @@ public string FullyQualifiedDomainName
182
191
[ NotifyPropertyChangedFor ( nameof ( ExpandAppsMessage ) ) ]
183
192
public partial bool AppFetchErrored { get ; set ; } = false ;
184
193
194
+ [ ObservableProperty ]
195
+ [ NotifyPropertyChangedFor ( nameof ( ConnectionTooltip ) ) ]
196
+ public partial bool ? DidP2p { get ; set ; } = false ;
197
+
198
+ [ ObservableProperty ]
199
+ [ NotifyPropertyChangedFor ( nameof ( ConnectionTooltip ) ) ]
200
+ public partial string ? PreferredDerp { get ; set ; } = null ;
201
+
202
+ [ ObservableProperty ]
203
+ [ NotifyPropertyChangedFor ( nameof ( ConnectionTooltip ) ) ]
204
+ public partial TimeSpan ? Latency { get ; set ; } = null ;
205
+
206
+ [ ObservableProperty ]
207
+ [ NotifyPropertyChangedFor ( nameof ( ConnectionTooltip ) ) ]
208
+ public partial TimeSpan ? PreferredDerpLatency { get ; set ; } = null ;
209
+
210
+ [ ObservableProperty ]
211
+ [ NotifyPropertyChangedFor ( nameof ( ConnectionTooltip ) ) ]
212
+ public partial DateTime ? LastHandshake { get ; set ; } = null ;
213
+
214
+ public string ConnectionTooltip { get
215
+ {
216
+ var description = new StringBuilder ( ) ;
217
+
218
+ if ( DidP2p != null && DidP2p . Value && Latency != null )
219
+ {
220
+ description . Append ( $ """
221
+ You're connected peer-to-peer.
222
+
223
+ You ↔ { Latency . Value . Milliseconds } ms ↔ { WorkspaceName }
224
+ """
225
+ ) ;
226
+ }
227
+ else if ( PreferredDerpLatency != null )
228
+ {
229
+ description . Append ( $ """
230
+ You're connected through a DERP relay.
231
+ We'll switch over to peer-to-peer when available.
232
+
233
+ Total latency: { PreferredDerpLatency . Value . Milliseconds } ms
234
+ """
235
+ ) ;
236
+
237
+ if ( PreferredDerp != null && Latency != null )
238
+ {
239
+ description . Append ( $ "\n You ↔ { PreferredDerp } : { PreferredDerpLatency . Value . Milliseconds } ms") ;
240
+
241
+ var derpToWorkspaceEstimatedLatency = Latency - PreferredDerpLatency ;
242
+
243
+ // Guard against negative values if the two readings were taken at different times
244
+ if ( derpToWorkspaceEstimatedLatency > TimeSpan . Zero )
245
+ {
246
+ description . Append ( $ "\n { PreferredDerp } ms ↔ { WorkspaceName } : { derpToWorkspaceEstimatedLatency . Value . Milliseconds } ms") ;
247
+ }
248
+ }
249
+ }
250
+ if ( LastHandshake != null )
251
+ description . Append ( $ "\n \n Last handshake: { LastHandshake ? . ToString ( ) ?? "Unknown" } ") ;
252
+
253
+ var tooltip = description . ToString ( ) . TrimEnd ( '\n ' , ' ' ) ;
254
+
255
+ if ( string . IsNullOrEmpty ( tooltip ) )
256
+ return "No connection information available." ;
257
+
258
+ return tooltip ;
259
+ }
260
+ }
261
+
262
+
185
263
// We only show 6 apps max, which fills the entire width of the tray
186
264
// window.
187
265
public IEnumerable < AgentAppViewModel > VisibleApps => Apps . Count > MaxAppsPerRow ? Apps . Take ( MaxAppsPerRow ) : Apps ;
@@ -192,7 +270,7 @@ public string? ExpandAppsMessage
192
270
{
193
271
get
194
272
{
195
- if ( ConnectionStatus == AgentConnectionStatus . Gray )
273
+ if ( ConnectionStatus == AgentConnectionStatus . Offline )
196
274
return "Your workspace is offline." ;
197
275
if ( FetchingApps && Apps . Count == 0 )
198
276
// Don't show this message if we have any apps already. When
@@ -285,6 +363,16 @@ public bool TryApplyChanges(AgentViewModel model)
285
363
DashboardBaseUrl = model . DashboardBaseUrl ;
286
364
if ( WorkspaceName != model . WorkspaceName )
287
365
WorkspaceName = model . WorkspaceName ;
366
+ if ( DidP2p != model . DidP2p )
367
+ DidP2p = model . DidP2p ;
368
+ if ( PreferredDerp != model . PreferredDerp )
369
+ PreferredDerp = model . PreferredDerp ;
370
+ if ( Latency != model . Latency )
371
+ Latency = model . Latency ;
372
+ if ( PreferredDerpLatency != model . PreferredDerpLatency )
373
+ PreferredDerpLatency = model . PreferredDerpLatency ;
374
+ if ( LastHandshake != model . LastHandshake )
375
+ LastHandshake = model . LastHandshake ;
288
376
289
377
// Apps are not set externally.
290
378
@@ -307,7 +395,7 @@ public void SetExpanded(bool expanded)
307
395
308
396
partial void OnConnectionStatusChanged ( AgentConnectionStatus oldValue , AgentConnectionStatus newValue )
309
397
{
310
- if ( IsExpanded && newValue is not AgentConnectionStatus . Gray ) FetchApps ( ) ;
398
+ if ( IsExpanded && newValue is not AgentConnectionStatus . Offline ) FetchApps ( ) ;
311
399
}
312
400
313
401
private void FetchApps ( )
@@ -316,7 +404,7 @@ private void FetchApps()
316
404
FetchingApps = true ;
317
405
318
406
// If the workspace is off, then there's no agent and there's no apps.
319
- if ( ConnectionStatus == AgentConnectionStatus . Gray )
407
+ if ( ConnectionStatus == AgentConnectionStatus . Offline )
320
408
{
321
409
FetchingApps = false ;
322
410
Apps . Clear ( ) ;
0 commit comments