@@ -23,8 +23,13 @@ namespace Coder.Desktop.App.ViewModels;
2323
2424public interface IAgentViewModelFactory
2525{
26- public AgentViewModel Create ( IAgentExpanderHost expanderHost , Uuid id , string hostname , string hostnameSuffix ,
26+ public AgentViewModel Create ( IAgentExpanderHost expanderHost , Uuid id , string fullyQualifiedDomainName ,
27+ string hostnameSuffix ,
2728 AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl , string ? workspaceName ) ;
29+
30+ public AgentViewModel CreateDummy ( IAgentExpanderHost expanderHost , Uuid id ,
31+ string hostnameSuffix ,
32+ AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl , string workspaceName ) ;
2833}
2934
3035public class AgentViewModelFactory (
@@ -33,14 +38,32 @@ public class AgentViewModelFactory(
3338 ICredentialManager credentialManager ,
3439 IAgentAppViewModelFactory agentAppViewModelFactory ) : IAgentViewModelFactory
3540{
36- public AgentViewModel Create ( IAgentExpanderHost expanderHost , Uuid id , string hostname , string hostnameSuffix ,
41+ public AgentViewModel Create ( IAgentExpanderHost expanderHost , Uuid id , string fullyQualifiedDomainName ,
42+ string hostnameSuffix ,
3743 AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl , string ? workspaceName )
3844 {
3945 return new AgentViewModel ( childLogger , coderApiClientFactory , credentialManager , agentAppViewModelFactory ,
4046 expanderHost , id )
4147 {
42- Hostname = hostname ,
43- HostnameSuffix = hostnameSuffix ,
48+ ConfiguredFqdn = fullyQualifiedDomainName ,
49+ ConfiguredHostname = string . Empty ,
50+ ConfiguredHostnameSuffix = hostnameSuffix ,
51+ ConnectionStatus = connectionStatus ,
52+ DashboardBaseUrl = dashboardBaseUrl ,
53+ WorkspaceName = workspaceName ,
54+ } ;
55+ }
56+
57+ public AgentViewModel CreateDummy ( IAgentExpanderHost expanderHost , Uuid id ,
58+ string hostnameSuffix ,
59+ AgentConnectionStatus connectionStatus , Uri dashboardBaseUrl , string workspaceName )
60+ {
61+ return new AgentViewModel ( childLogger , coderApiClientFactory , credentialManager , agentAppViewModelFactory ,
62+ expanderHost , id )
63+ {
64+ ConfiguredFqdn = string . Empty ,
65+ ConfiguredHostname = workspaceName ,
66+ ConfiguredHostnameSuffix = hostnameSuffix ,
4467 ConnectionStatus = connectionStatus ,
4568 DashboardBaseUrl = dashboardBaseUrl ,
4669 WorkspaceName = workspaceName ,
@@ -84,15 +107,56 @@ public partial class AgentViewModel : ObservableObject, IModelUpdateable<AgentVi
84107
85108 public readonly Uuid Id ;
86109
110+ // This is set only for "dummy" agents that represent unstarted workspaces. If set, then ConfiguredFqdn
111+ // should be empty, otherwise it will override this.
112+ [ ObservableProperty ]
113+ [ NotifyPropertyChangedFor ( nameof ( ViewableHostname ) ) ]
114+ [ NotifyPropertyChangedFor ( nameof ( ViewableHostnameSuffix ) ) ]
115+ [ NotifyPropertyChangedFor ( nameof ( FullyQualifiedDomainName ) ) ]
116+ public required partial string ConfiguredHostname { get ; set ; }
117+
118+ // This should be set if we have an FQDN from the VPN service, and overrides ConfiguredHostname if set.
87119 [ ObservableProperty ]
88- [ NotifyPropertyChangedFor ( nameof ( FullHostname ) ) ]
89- public required partial string Hostname { get ; set ; }
120+ [ NotifyPropertyChangedFor ( nameof ( ViewableHostname ) ) ]
121+ [ NotifyPropertyChangedFor ( nameof ( ViewableHostnameSuffix ) ) ]
122+ [ NotifyPropertyChangedFor ( nameof ( FullyQualifiedDomainName ) ) ]
123+ public required partial string ConfiguredFqdn { get ; set ; }
90124
91125 [ ObservableProperty ]
92- [ NotifyPropertyChangedFor ( nameof ( FullHostname ) ) ]
93- public required partial string HostnameSuffix { get ; set ; } // including leading dot
126+ [ NotifyPropertyChangedFor ( nameof ( ViewableHostname ) ) ]
127+ [ NotifyPropertyChangedFor ( nameof ( ViewableHostnameSuffix ) ) ]
128+ [ NotifyPropertyChangedFor ( nameof ( FullyQualifiedDomainName ) ) ]
129+ public required partial string ConfiguredHostnameSuffix { get ; set ; }
130+
131+
132+ public string FullyQualifiedDomainName
133+ {
134+ get
135+ {
136+ if ( ! string . IsNullOrEmpty ( ConfiguredFqdn ) ) return ConfiguredFqdn ;
137+ return ConfiguredHostname + ConfiguredHostnameSuffix ;
138+ }
139+ }
94140
95- public string FullHostname => Hostname + HostnameSuffix ;
141+ /// <summary>
142+ /// ViewableHostname is the hostname portion of the fully qualified domain name (FQDN) specifically for
143+ /// views that render it differently than the suffix. If the ConfiguredHostnameSuffix doesn't actually
144+ /// match the FQDN, then this will be the entire FQDN, and ViewableHostnameSuffix will be empty.
145+ /// </summary>
146+ public string ViewableHostname => ! FullyQualifiedDomainName . EndsWith ( ConfiguredHostnameSuffix )
147+ ? FullyQualifiedDomainName
148+ : FullyQualifiedDomainName . Substring ( 0 ,
149+ FullyQualifiedDomainName . Length - ConfiguredHostnameSuffix . Length ) ;
150+
151+ /// <summary>
152+ /// ViewableHostnameSuffix is the domain suffix portion of the fully qualified domain name (FQDN)
153+ /// specifically for views that render it differently from the rest of the FQDN. If the
154+ /// ConfiguredHostnameSuffix doesn't actually match the FQDN, then this will be empty and the
155+ /// ViewableHostname will contain the entire FQDN.
156+ /// </summary>
157+ public string ViewableHostnameSuffix => FullyQualifiedDomainName . EndsWith ( ConfiguredHostnameSuffix )
158+ ? ConfiguredHostnameSuffix
159+ : string . Empty ;
96160
97161 [ ObservableProperty ]
98162 [ NotifyPropertyChangedFor ( nameof ( ShowExpandAppsMessage ) ) ]
@@ -202,10 +266,12 @@ public bool TryApplyChanges(AgentViewModel model)
202266
203267 // To avoid spurious UI updates which cause flashing, don't actually
204268 // write to values unless they've changed.
205- if ( Hostname != model . Hostname )
206- Hostname = model . Hostname ;
207- if ( HostnameSuffix != model . HostnameSuffix )
208- HostnameSuffix = model . HostnameSuffix ;
269+ if ( ConfiguredFqdn != model . ConfiguredFqdn )
270+ ConfiguredFqdn = model . ConfiguredFqdn ;
271+ if ( ConfiguredHostname != model . ConfiguredHostname )
272+ ConfiguredHostname = model . ConfiguredHostname ;
273+ if ( ConfiguredHostnameSuffix != model . ConfiguredHostnameSuffix )
274+ ConfiguredHostnameSuffix = model . ConfiguredHostnameSuffix ;
209275 if ( ConnectionStatus != model . ConnectionStatus )
210276 ConnectionStatus = model . ConnectionStatus ;
211277 if ( DashboardBaseUrl != model . DashboardBaseUrl )
@@ -337,12 +403,13 @@ private void ContinueFetchApps(Task<WorkspaceAgent> task)
337403 {
338404 Scheme = scheme ,
339405 Host = "vscode-remote" ,
340- Path = $ "/ssh-remote+{ FullHostname } /{ workspaceAgent . ExpandedDirectory } ",
406+ Path = $ "/ssh-remote+{ FullyQualifiedDomainName } /{ workspaceAgent . ExpandedDirectory } ",
341407 } . Uri ;
342408 }
343409 catch ( Exception e )
344410 {
345- _logger . LogWarning ( e , "Could not craft app URI for display app {displayApp}, app will not appear in list" ,
411+ _logger . LogWarning ( e ,
412+ "Could not craft app URI for display app {displayApp}, app will not appear in list" ,
346413 displayApp ) ;
347414 continue ;
348415 }
@@ -365,7 +432,7 @@ private void CopyHostname(object parameter)
365432 {
366433 RequestedOperation = DataPackageOperation . Copy ,
367434 } ;
368- dataPackage . SetText ( FullHostname ) ;
435+ dataPackage . SetText ( FullyQualifiedDomainName ) ;
369436 Clipboard . SetContent ( dataPackage ) ;
370437
371438 if ( parameter is not FrameworkElement frameworkElement ) return ;
0 commit comments