@@ -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,55 @@ 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 ;  }  // including leading dot 
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 [ 0 ..^ ConfiguredHostnameSuffix . Length ] ; 
149+ 
150+     /// <summary> 
151+     /// ViewableHostnameSuffix is the domain suffix portion (including leading dot) of the fully qualified 
152+     /// domain name (FQDN) specifically for views that render it differently from the rest of the FQDN. If 
153+     /// the ConfiguredHostnameSuffix doesn't actually match the FQDN, then this will be empty and the 
154+     /// ViewableHostname will contain the entire FQDN. 
155+     /// </summary> 
156+     public  string  ViewableHostnameSuffix  =>  FullyQualifiedDomainName . EndsWith ( ConfiguredHostnameSuffix ) 
157+         ?  ConfiguredHostnameSuffix 
158+         :  string . Empty ; 
96159
97160    [ ObservableProperty ] 
98161    [ NotifyPropertyChangedFor ( nameof ( ShowExpandAppsMessage ) ) ] 
@@ -202,10 +265,12 @@ public bool TryApplyChanges(AgentViewModel model)
202265
203266        // To avoid spurious UI updates which cause flashing, don't actually 
204267        // 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 ; 
268+         if  ( ConfiguredFqdn  !=  model . ConfiguredFqdn ) 
269+             ConfiguredFqdn  =  model . ConfiguredFqdn ; 
270+         if  ( ConfiguredHostname  !=  model . ConfiguredHostname ) 
271+             ConfiguredHostname  =  model . ConfiguredHostname ; 
272+         if  ( ConfiguredHostnameSuffix  !=  model . ConfiguredHostnameSuffix ) 
273+             ConfiguredHostnameSuffix  =  model . ConfiguredHostnameSuffix ; 
209274        if  ( ConnectionStatus  !=  model . ConnectionStatus ) 
210275            ConnectionStatus  =  model . ConnectionStatus ; 
211276        if  ( DashboardBaseUrl  !=  model . DashboardBaseUrl ) 
@@ -337,12 +402,13 @@ private void ContinueFetchApps(Task<WorkspaceAgent> task)
337402                { 
338403                    Scheme  =  scheme , 
339404                    Host  =  "vscode-remote" , 
340-                     Path  =  $ "/ssh-remote+{ FullHostname } /{ workspaceAgent . ExpandedDirectory } ", 
405+                     Path  =  $ "/ssh-remote+{ FullyQualifiedDomainName } /{ workspaceAgent . ExpandedDirectory } ", 
341406                } . Uri ; 
342407            } 
343408            catch  ( Exception  e ) 
344409            { 
345-                 _logger . LogWarning ( e ,  "Could not craft app URI for display app {displayApp}, app will not appear in list" , 
410+                 _logger . LogWarning ( e , 
411+                     "Could not craft app URI for display app {displayApp}, app will not appear in list" , 
346412                    displayApp ) ; 
347413                continue ; 
348414            } 
@@ -365,7 +431,7 @@ private void CopyHostname(object parameter)
365431        { 
366432            RequestedOperation  =  DataPackageOperation . Copy , 
367433        } ; 
368-         dataPackage . SetText ( FullHostname ) ; 
434+         dataPackage . SetText ( FullyQualifiedDomainName ) ; 
369435        Clipboard . SetContent ( dataPackage ) ; 
370436
371437        if  ( parameter  is  not FrameworkElement  frameworkElement )  return ; 
0 commit comments