From 3d98896b1a3c83ee963c9f5e7e7783defb3c797b Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 13 May 2025 15:31:21 +0400 Subject: [PATCH] chore: make hostname suffix mutable in views --- App/ViewModels/AgentViewModel.cs | 98 ++++++++++++++++++---- App/ViewModels/TrayWindowViewModel.cs | 24 ++---- App/Views/Pages/TrayWindowMainPage.xaml | 4 +- App/Views/Pages/TrayWindowMainPage.xaml.cs | 14 ++-- 4 files changed, 99 insertions(+), 41 deletions(-) diff --git a/App/ViewModels/AgentViewModel.cs b/App/ViewModels/AgentViewModel.cs index c44db3e..34b01d7 100644 --- a/App/ViewModels/AgentViewModel.cs +++ b/App/ViewModels/AgentViewModel.cs @@ -23,8 +23,13 @@ namespace Coder.Desktop.App.ViewModels; public interface IAgentViewModelFactory { - public AgentViewModel Create(IAgentExpanderHost expanderHost, Uuid id, string hostname, string hostnameSuffix, + public AgentViewModel Create(IAgentExpanderHost expanderHost, Uuid id, string fullyQualifiedDomainName, + string hostnameSuffix, AgentConnectionStatus connectionStatus, Uri dashboardBaseUrl, string? workspaceName); + + public AgentViewModel CreateDummy(IAgentExpanderHost expanderHost, Uuid id, + string hostnameSuffix, + AgentConnectionStatus connectionStatus, Uri dashboardBaseUrl, string workspaceName); } public class AgentViewModelFactory( @@ -33,14 +38,32 @@ public class AgentViewModelFactory( ICredentialManager credentialManager, IAgentAppViewModelFactory agentAppViewModelFactory) : IAgentViewModelFactory { - public AgentViewModel Create(IAgentExpanderHost expanderHost, Uuid id, string hostname, string hostnameSuffix, + public AgentViewModel Create(IAgentExpanderHost expanderHost, Uuid id, string fullyQualifiedDomainName, + string hostnameSuffix, AgentConnectionStatus connectionStatus, Uri dashboardBaseUrl, string? workspaceName) { return new AgentViewModel(childLogger, coderApiClientFactory, credentialManager, agentAppViewModelFactory, expanderHost, id) { - Hostname = hostname, - HostnameSuffix = hostnameSuffix, + ConfiguredFqdn = fullyQualifiedDomainName, + ConfiguredHostname = string.Empty, + ConfiguredHostnameSuffix = hostnameSuffix, + ConnectionStatus = connectionStatus, + DashboardBaseUrl = dashboardBaseUrl, + WorkspaceName = workspaceName, + }; + } + + public AgentViewModel CreateDummy(IAgentExpanderHost expanderHost, Uuid id, + string hostnameSuffix, + AgentConnectionStatus connectionStatus, Uri dashboardBaseUrl, string workspaceName) + { + return new AgentViewModel(childLogger, coderApiClientFactory, credentialManager, agentAppViewModelFactory, + expanderHost, id) + { + ConfiguredFqdn = string.Empty, + ConfiguredHostname = workspaceName, + ConfiguredHostnameSuffix = hostnameSuffix, ConnectionStatus = connectionStatus, DashboardBaseUrl = dashboardBaseUrl, WorkspaceName = workspaceName, @@ -84,15 +107,55 @@ public partial class AgentViewModel : ObservableObject, IModelUpdateable Hostname + HostnameSuffix; + /// + /// ViewableHostname is the hostname portion of the fully qualified domain name (FQDN) specifically for + /// views that render it differently than the suffix. If the ConfiguredHostnameSuffix doesn't actually + /// match the FQDN, then this will be the entire FQDN, and ViewableHostnameSuffix will be empty. + /// + public string ViewableHostname => !FullyQualifiedDomainName.EndsWith(ConfiguredHostnameSuffix) + ? FullyQualifiedDomainName + : FullyQualifiedDomainName[0..^ConfiguredHostnameSuffix.Length]; + + /// + /// ViewableHostnameSuffix is the domain suffix portion (including leading dot) of the fully qualified + /// domain name (FQDN) specifically for views that render it differently from the rest of the FQDN. If + /// the ConfiguredHostnameSuffix doesn't actually match the FQDN, then this will be empty and the + /// ViewableHostname will contain the entire FQDN. + /// + public string ViewableHostnameSuffix => FullyQualifiedDomainName.EndsWith(ConfiguredHostnameSuffix) + ? ConfiguredHostnameSuffix + : string.Empty; [ObservableProperty] [NotifyPropertyChangedFor(nameof(ShowExpandAppsMessage))] @@ -202,10 +265,12 @@ public bool TryApplyChanges(AgentViewModel model) // To avoid spurious UI updates which cause flashing, don't actually // write to values unless they've changed. - if (Hostname != model.Hostname) - Hostname = model.Hostname; - if (HostnameSuffix != model.HostnameSuffix) - HostnameSuffix = model.HostnameSuffix; + if (ConfiguredFqdn != model.ConfiguredFqdn) + ConfiguredFqdn = model.ConfiguredFqdn; + if (ConfiguredHostname != model.ConfiguredHostname) + ConfiguredHostname = model.ConfiguredHostname; + if (ConfiguredHostnameSuffix != model.ConfiguredHostnameSuffix) + ConfiguredHostnameSuffix = model.ConfiguredHostnameSuffix; if (ConnectionStatus != model.ConnectionStatus) ConnectionStatus = model.ConnectionStatus; if (DashboardBaseUrl != model.DashboardBaseUrl) @@ -337,12 +402,13 @@ private void ContinueFetchApps(Task task) { Scheme = scheme, Host = "vscode-remote", - Path = $"/ssh-remote+{FullHostname}/{workspaceAgent.ExpandedDirectory}", + Path = $"/ssh-remote+{FullyQualifiedDomainName}/{workspaceAgent.ExpandedDirectory}", }.Uri; } catch (Exception e) { - _logger.LogWarning(e, "Could not craft app URI for display app {displayApp}, app will not appear in list", + _logger.LogWarning(e, + "Could not craft app URI for display app {displayApp}, app will not appear in list", displayApp); continue; } @@ -365,7 +431,7 @@ private void CopyHostname(object parameter) { RequestedOperation = DataPackageOperation.Copy, }; - dataPackage.SetText(FullHostname); + dataPackage.SetText(FullyQualifiedDomainName); Clipboard.SetContent(dataPackage); if (parameter is not FrameworkElement frameworkElement) return; diff --git a/App/ViewModels/TrayWindowViewModel.cs b/App/ViewModels/TrayWindowViewModel.cs index b0c9a8b..1dccab0 100644 --- a/App/ViewModels/TrayWindowViewModel.cs +++ b/App/ViewModels/TrayWindowViewModel.cs @@ -29,6 +29,7 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost { private const int MaxAgents = 5; private const string DefaultDashboardUrl = "https://coder.com"; + private const string DefaultHostnameSuffix = ".coder"; private readonly IServiceProvider _services; private readonly IRpcController _rpcController; @@ -90,6 +91,8 @@ public partial class TrayWindowViewModel : ObservableObject, IAgentExpanderHost [ObservableProperty] public partial string DashboardUrl { get; set; } = DefaultDashboardUrl; + private string _hostnameSuffix = DefaultHostnameSuffix; + public TrayWindowViewModel(IServiceProvider services, IRpcController rpcController, ICredentialManager credentialManager, IAgentViewModelFactory agentViewModelFactory) { @@ -181,14 +184,6 @@ private void UpdateFromRpcModel(RpcModel rpcModel) if (string.IsNullOrWhiteSpace(fqdn)) continue; - var fqdnPrefix = fqdn; - var fqdnSuffix = ""; - if (fqdn.Contains('.')) - { - fqdnPrefix = fqdn[..fqdn.LastIndexOf('.')]; - fqdnSuffix = fqdn[fqdn.LastIndexOf('.')..]; - } - var lastHandshakeAgo = DateTime.UtcNow.Subtract(agent.LastHandshake.ToDateTime()); var connectionStatus = lastHandshakeAgo < TimeSpan.FromMinutes(5) ? AgentConnectionStatus.Green @@ -199,8 +194,8 @@ private void UpdateFromRpcModel(RpcModel rpcModel) agents.Add(_agentViewModelFactory.Create( this, uuid, - fqdnPrefix, - fqdnSuffix, + fqdn, + _hostnameSuffix, connectionStatus, credentialModel.CoderUrl, workspace?.Name)); @@ -214,15 +209,12 @@ private void UpdateFromRpcModel(RpcModel rpcModel) if (!Uuid.TryFrom(workspace.Id.Span, out var uuid)) continue; - agents.Add(_agentViewModelFactory.Create( + agents.Add(_agentViewModelFactory.CreateDummy( this, // Workspace ID is fine as a stand-in here, it shouldn't // conflict with any agent IDs. uuid, - // We assume that it's a single-agent workspace. - workspace.Name, - // TODO: this needs to get the suffix from the server - ".coder", + _hostnameSuffix, AgentConnectionStatus.Gray, credentialModel.CoderUrl, workspace.Name)); @@ -233,7 +225,7 @@ private void UpdateFromRpcModel(RpcModel rpcModel) { if (a.ConnectionStatus != b.ConnectionStatus) return a.ConnectionStatus.CompareTo(b.ConnectionStatus); - return string.Compare(a.FullHostname, b.FullHostname, StringComparison.Ordinal); + return string.Compare(a.FullyQualifiedDomainName, b.FullyQualifiedDomainName, StringComparison.Ordinal); }); if (Agents.Count < MaxAgents) ShowAllAgents = false; diff --git a/App/Views/Pages/TrayWindowMainPage.xaml b/App/Views/Pages/TrayWindowMainPage.xaml index b66aa6e..f3549c2 100644 --- a/App/Views/Pages/TrayWindowMainPage.xaml +++ b/App/Views/Pages/TrayWindowMainPage.xaml @@ -169,9 +169,9 @@ TextTrimming="CharacterEllipsis" TextWrapping="NoWrap"> - - diff --git a/App/Views/Pages/TrayWindowMainPage.xaml.cs b/App/Views/Pages/TrayWindowMainPage.xaml.cs index 5911092..e1cbab3 100644 --- a/App/Views/Pages/TrayWindowMainPage.xaml.cs +++ b/App/Views/Pages/TrayWindowMainPage.xaml.cs @@ -18,9 +18,9 @@ public TrayWindowMainPage(TrayWindowViewModel viewModel) } // HACK: using XAML to populate the text Runs results in an additional - // whitespace Run being inserted between the Hostname and the - // HostnameSuffix. You might think, "OK let's populate the entire TextBlock - // content from code then!", but this results in the ItemsRepeater + // whitespace Run being inserted between the ViewableHostname and the + // ViewableHostnameSuffix. You might think, "OK let's populate the entire + // TextBlock content from code then!", but this results in the ItemsRepeater // corrupting it and firing events off to the wrong AgentModel. // // This is the best solution I came up with that worked. @@ -28,12 +28,12 @@ public void AgentHostnameText_OnLoaded(object sender, RoutedEventArgs e) { if (sender is not TextBlock textBlock) return; - var nonEmptyRuns = new List(); + var nonWhitespaceRuns = new List(); foreach (var inline in textBlock.Inlines) - if (inline is Run run && !string.IsNullOrWhiteSpace(run.Text)) - nonEmptyRuns.Add(run); + if (inline is Run run && run.Text != " ") + nonWhitespaceRuns.Add(run); textBlock.Inlines.Clear(); - foreach (var run in nonEmptyRuns) textBlock.Inlines.Add(run); + foreach (var run in nonWhitespaceRuns) textBlock.Inlines.Add(run); } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy