Skip to content

Commit 3d98896

Browse files
committed
chore: make hostname suffix mutable in views
1 parent cd845d4 commit 3d98896

File tree

4 files changed

+99
-41
lines changed

4 files changed

+99
-41
lines changed

App/ViewModels/AgentViewModel.cs

+82-16
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@ namespace Coder.Desktop.App.ViewModels;
2323

2424
public 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

3035
public 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;

App/ViewModels/TrayWindowViewModel.cs

+8-16
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
2929
{
3030
private const int MaxAgents = 5;
3131
private const string DefaultDashboardUrl = "https://coder.com";
32+
private const string DefaultHostnameSuffix = ".coder";
3233

3334
private readonly IServiceProvider _services;
3435
private readonly IRpcController _rpcController;
@@ -90,6 +91,8 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost
9091

9192
[ObservableProperty] public partial string DashboardUrl { get; set; } = DefaultDashboardUrl;
9293

94+
private string _hostnameSuffix = DefaultHostnameSuffix;
95+
9396
public TrayWindowViewModel(IServiceProvider services, IRpcController rpcController,
9497
ICredentialManager credentialManager, IAgentViewModelFactory agentViewModelFactory)
9598
{
@@ -181,14 +184,6 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
181184
if (string.IsNullOrWhiteSpace(fqdn))
182185
continue;
183186

184-
var fqdnPrefix = fqdn;
185-
var fqdnSuffix = "";
186-
if (fqdn.Contains('.'))
187-
{
188-
fqdnPrefix = fqdn[..fqdn.LastIndexOf('.')];
189-
fqdnSuffix = fqdn[fqdn.LastIndexOf('.')..];
190-
}
191-
192187
var lastHandshakeAgo = DateTime.UtcNow.Subtract(agent.LastHandshake.ToDateTime());
193188
var connectionStatus = lastHandshakeAgo < TimeSpan.FromMinutes(5)
194189
? AgentConnectionStatus.Green
@@ -199,8 +194,8 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
199194
agents.Add(_agentViewModelFactory.Create(
200195
this,
201196
uuid,
202-
fqdnPrefix,
203-
fqdnSuffix,
197+
fqdn,
198+
_hostnameSuffix,
204199
connectionStatus,
205200
credentialModel.CoderUrl,
206201
workspace?.Name));
@@ -214,15 +209,12 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
214209
if (!Uuid.TryFrom(workspace.Id.Span, out var uuid))
215210
continue;
216211

217-
agents.Add(_agentViewModelFactory.Create(
212+
agents.Add(_agentViewModelFactory.CreateDummy(
218213
this,
219214
// Workspace ID is fine as a stand-in here, it shouldn't
220215
// conflict with any agent IDs.
221216
uuid,
222-
// We assume that it's a single-agent workspace.
223-
workspace.Name,
224-
// TODO: this needs to get the suffix from the server
225-
".coder",
217+
_hostnameSuffix,
226218
AgentConnectionStatus.Gray,
227219
credentialModel.CoderUrl,
228220
workspace.Name));
@@ -233,7 +225,7 @@ private void UpdateFromRpcModel(RpcModel rpcModel)
233225
{
234226
if (a.ConnectionStatus != b.ConnectionStatus)
235227
return a.ConnectionStatus.CompareTo(b.ConnectionStatus);
236-
return string.Compare(a.FullHostname, b.FullHostname, StringComparison.Ordinal);
228+
return string.Compare(a.FullyQualifiedDomainName, b.FullyQualifiedDomainName, StringComparison.Ordinal);
237229
});
238230

239231
if (Agents.Count < MaxAgents) ShowAllAgents = false;

App/Views/Pages/TrayWindowMainPage.xaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@
169169
TextTrimming="CharacterEllipsis"
170170
TextWrapping="NoWrap">
171171

172-
<Run Text="{x:Bind Hostname, Mode=OneWay}"
172+
<Run Text="{x:Bind ViewableHostname, Mode=OneWay}"
173173
Foreground="{ThemeResource DefaultTextForegroundThemeBrush}" />
174-
<Run Text="{x:Bind HostnameSuffix, Mode=OneWay}"
174+
<Run Text="{x:Bind ViewableHostnameSuffix, Mode=OneWay}"
175175
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
176176
</TextBlock>
177177

App/Views/Pages/TrayWindowMainPage.xaml.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,22 @@ public TrayWindowMainPage(TrayWindowViewModel viewModel)
1818
}
1919

2020
// HACK: using XAML to populate the text Runs results in an additional
21-
// whitespace Run being inserted between the Hostname and the
22-
// HostnameSuffix. You might think, "OK let's populate the entire TextBlock
23-
// content from code then!", but this results in the ItemsRepeater
21+
// whitespace Run being inserted between the ViewableHostname and the
22+
// ViewableHostnameSuffix. You might think, "OK let's populate the entire
23+
// TextBlock content from code then!", but this results in the ItemsRepeater
2424
// corrupting it and firing events off to the wrong AgentModel.
2525
//
2626
// This is the best solution I came up with that worked.
2727
public void AgentHostnameText_OnLoaded(object sender, RoutedEventArgs e)
2828
{
2929
if (sender is not TextBlock textBlock) return;
3030

31-
var nonEmptyRuns = new List<Run>();
31+
var nonWhitespaceRuns = new List<Run>();
3232
foreach (var inline in textBlock.Inlines)
33-
if (inline is Run run && !string.IsNullOrWhiteSpace(run.Text))
34-
nonEmptyRuns.Add(run);
33+
if (inline is Run run && run.Text != " ")
34+
nonWhitespaceRuns.Add(run);
3535

3636
textBlock.Inlines.Clear();
37-
foreach (var run in nonEmptyRuns) textBlock.Inlines.Add(run);
37+
foreach (var run in nonWhitespaceRuns) textBlock.Inlines.Add(run);
3838
}
3939
}

0 commit comments

Comments
 (0)