Skip to content

Commit

Permalink
feat: add agent status to tray app
Browse files Browse the repository at this point in the history
- Adds agent and stopped workspace statuses to the tray app
- The Vpn.Service.Manager now tracks the current list of workspaces and
  agents from the tunnel
- Deletes remnants of the old Package
- Moves App to be completely unpackaged
  • Loading branch information
deansheather committed Feb 7, 2025
1 parent 88a4a97 commit 47c7fdb
Show file tree
Hide file tree
Showing 20 changed files with 839 additions and 560 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,5 @@ FodyWeavers.xsd
.idea/**/shelf

publish
WindowsAppRuntimeInstall-x64.exe
wintun.dll
40 changes: 2 additions & 38 deletions App/App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,13 @@
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
<Nullable>enable</Nullable>
<EnableMsixTooling>true</EnableMsixTooling>
<EnableMsixTooling>false</EnableMsixTooling>
<WindowsPackageType>None</WindowsPackageType>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<!-- To use CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute: -->
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>

<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
Expand All @@ -40,43 +35,12 @@
</PackageReference>
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
</ItemGroup>

<!--
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored.
-->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CoderSdk\CoderSdk.csproj" />
<ProjectReference Include="..\Vpn.Proto\Vpn.Proto.csproj" />
<ProjectReference Include="..\Vpn\Vpn.csproj" />
</ItemGroup>

<!--
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
Explorer "Package and Publish" context menu entry to be enabled for this project even if
the Windows App SDK Nuget package has not yet been restored.
-->
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>

<!-- Publish Properties -->
<PropertyGroup>
<!--
This does not work in CI at the moment, so we need to set it to false
Error: C:\Program Files\dotnet\sdk\9.0.102\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Publish.targets(400,5): error NETSDK1094: Unable to optimize assemblies for performance: a valid runtime package was not found. Either set the PublishReadyToRun property to false, or use a supported runtime identifier when publishing. When targeting .NET 6 or higher, make sure to restore packages with the PublishReadyToRun property set to true. [D:\a\coder-desktop-windows\coder-desktop-windows\App\App.csproj]
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
-->
<PublishReadyToRun>False</PublishReadyToRun>
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
</PropertyGroup>
</Project>
11 changes: 4 additions & 7 deletions App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ public partial class App : Application
{
private readonly IServiceProvider _services;
private TrayWindow? _trayWindow;
private readonly bool _handleClosedEvents = true;

public App()
{
var services = new ServiceCollection();
services.AddSingleton<ICredentialManager, CredentialManager>();
services.AddSingleton<IRpcController, RpcController>();

// TrayWindow pages and view models
services.AddTransient<TrayWindowDisconnectedViewModel>();
services.AddTransient<TrayWindowDisconnectedPage>();
services.AddTransient<TrayWindowLoginRequiredViewModel>();
Expand All @@ -43,14 +43,11 @@ public App()
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
_trayWindow = _services.GetRequiredService<TrayWindow>();
// Just hide the window rather than closing it.
_trayWindow.Closed += (sender, args) =>
{
// TODO: wire up HandleClosedEvents properly
if (_handleClosedEvents)
{
args.Handled = true;
_trayWindow.AppWindow.Hide();
}
args.Handled = true;
_trayWindow.AppWindow.Hide();
};
}
}
9 changes: 7 additions & 2 deletions App/Models/RpcModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Coder.Desktop.Vpn.Proto;

namespace Coder.Desktop.App.Models;

Expand All @@ -23,15 +25,18 @@ public class RpcModel

public VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Stopped;

public List<object> Agents { get; set; } = [];
public List<Workspace> Workspaces { get; set; } = [];

public List<Agent> Agents { get; set; } = [];

public RpcModel Clone()
{
return new RpcModel
{
RpcLifecycle = RpcLifecycle,
VpnLifecycle = VpnLifecycle,
Agents = Agents,
Workspaces = Workspaces.ToList(),
Agents = Agents.ToList(),
};
}
}
3 changes: 0 additions & 3 deletions App/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"profiles": {
"App (Package)": {
"commandName": "MsixPackage"
},
"App (Unpackaged)": {
"commandName": "Project"
}
Expand Down
10 changes: 7 additions & 3 deletions App/Services/CredentialManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Threading.Tasks;
using Coder.Desktop.App.Models;
using Coder.Desktop.Vpn.Utilities;
using CoderSdk;

namespace Coder.Desktop.App.Services;

Expand Down Expand Up @@ -64,18 +63,23 @@ public async Task SetCredentials(string coderUrl, string apiToken, CancellationT
if (apiToken.Length != 33)
throw new ArgumentOutOfRangeException(nameof(apiToken), "API token must be 33 characters long");

// TODO: this code seems to hang?
/*
try
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(5));
var sdkClient = new CoderApiClient(uri);
// TODO: we should probably perform a version check here too,
// rather than letting the service do it on Start
_ = await sdkClient.GetBuildInfo(ct);
_ = await sdkClient.GetUser(User.Me, ct);
_ = await sdkClient.GetBuildInfo(cts.Token);
_ = await sdkClient.GetUser(User.Me, cts.Token);
}
catch (Exception e)
{
throw new InvalidOperationException("Could not connect to or verify Coder server", e);
}
*/

WriteCredentials(new RawCredentials
{
Expand Down
56 changes: 52 additions & 4 deletions App/Services/RpcController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public async Task Reconnect(CancellationToken ct = default)
{
state.RpcLifecycle = RpcLifecycle.Connecting;
state.VpnLifecycle = VpnLifecycle.Stopped;
state.Workspaces.Clear();
state.Agents.Clear();
});

Expand Down Expand Up @@ -126,6 +127,7 @@ public async Task Reconnect(CancellationToken ct = default)
{
state.RpcLifecycle = RpcLifecycle.Disconnected;
state.VpnLifecycle = VpnLifecycle.Stopped;
state.Workspaces.Clear();
state.Agents.Clear();
});
throw new RpcOperationException("Failed to reconnect to the RPC server", e);
Expand All @@ -134,10 +136,18 @@ public async Task Reconnect(CancellationToken ct = default)
MutateState(state =>
{
state.RpcLifecycle = RpcLifecycle.Connected;
// TODO: fetch current state
state.VpnLifecycle = VpnLifecycle.Stopped;
state.VpnLifecycle = VpnLifecycle.Stopping; // prevents clicking the toggle
state.Workspaces.Clear();
state.Agents.Clear();
});

var statusReply = await _speaker.SendRequestAwaitReply(new ClientMessage
{
Status = new StatusRequest(),
}, ct);
if (statusReply.MsgCase != ServiceMessage.MsgOneofCase.Status)
throw new InvalidOperationException($"Unexpected reply message type: {statusReply.MsgCase}");
ApplyStatusUpdate(statusReply.Status);
}

public async Task StartVpn(CancellationToken ct = default)
Expand Down Expand Up @@ -234,9 +244,40 @@ private async Task<IDisposable> AcquireOperationLockNowAsync()
return locker;
}

private void ApplyStatusUpdate(Status status)
{
MutateState(state =>
{
state.VpnLifecycle = status.Lifecycle switch
{
Status.Types.Lifecycle.Unknown => VpnLifecycle.Stopping, // disables the switch
Status.Types.Lifecycle.Starting => VpnLifecycle.Starting,
Status.Types.Lifecycle.Started => VpnLifecycle.Started,
Status.Types.Lifecycle.Stopping => VpnLifecycle.Stopping,
Status.Types.Lifecycle.Stopped => VpnLifecycle.Stopped,
_ => VpnLifecycle.Stopped,
};
state.Workspaces.Clear();
state.Workspaces.AddRange(status.PeerUpdate.UpsertedWorkspaces);
state.Agents.Clear();
state.Agents.AddRange(status.PeerUpdate.UpsertedAgents);
});
}

private void SpeakerOnReceive(ReplyableRpcMessage<ClientMessage, ServiceMessage> message)
{
// TODO: this
switch (message.Message.MsgCase)
{
case ServiceMessage.MsgOneofCase.Status:
ApplyStatusUpdate(message.Message.Status);
break;
case ServiceMessage.MsgOneofCase.Start:
case ServiceMessage.MsgOneofCase.Stop:
case ServiceMessage.MsgOneofCase.None:
default:
// TODO: log unexpected message
break;
}
}

private async Task DisposeSpeaker()
Expand All @@ -251,7 +292,14 @@ private async Task DisposeSpeaker()
private void SpeakerOnError(Exception e)
{
Debug.WriteLine($"Error: {e}");
Reconnect(CancellationToken.None).Wait();
try
{
Reconnect(CancellationToken.None).Wait();
}
catch
{
// best effort to immediately reconnect
}
}

private void AssertRpcConnected()
Expand Down
Loading

0 comments on commit 47c7fdb

Please sign in to comment.