Skip to content

Commit c2791f5

Browse files
authored
feat: add agent status to tray app (#21)
Closes #5
1 parent 641f1bc commit c2791f5

23 files changed

+724
-446
lines changed

.github/workflows/ci.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ jobs:
5151
cache-dependency-path: '**/packages.lock.json'
5252
- name: dotnet restore
5353
run: dotnet restore --locked-mode
54-
- name: dotnet publish
55-
run: dotnet publish --no-restore --configuration Release --output .\publish
56-
- name: Upload artifact
57-
uses: actions/upload-artifact@v4
58-
with:
59-
name: publish
60-
path: .\publish\
54+
#- name: dotnet publish
55+
# run: dotnet publish --no-restore --configuration Release --output .\publish
56+
#- name: Upload artifact
57+
# uses: actions/upload-artifact@v4
58+
# with:
59+
# name: publish
60+
# path: .\publish\

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,5 @@ FodyWeavers.xsd
403403
.idea/**/shelf
404404

405405
publish
406+
WindowsAppRuntimeInstall-x64.exe
407+
wintun.dll

App/App.csproj

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,13 @@
1010
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
1111
<UseWinUI>true</UseWinUI>
1212
<Nullable>enable</Nullable>
13-
<EnableMsixTooling>true</EnableMsixTooling>
13+
<EnableMsixTooling>false</EnableMsixTooling>
14+
<WindowsPackageType>None</WindowsPackageType>
1415
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
1516
<!-- To use CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute: -->
1617
<LangVersion>preview</LangVersion>
1718
</PropertyGroup>
1819

19-
<ItemGroup>
20-
<AppxManifest Include="Package.appxmanifest">
21-
<SubType>Designer</SubType>
22-
</AppxManifest>
23-
</ItemGroup>
24-
2520
<ItemGroup>
2621
<Manifest Include="$(ApplicationManifest)" />
2722
</ItemGroup>
@@ -40,43 +35,12 @@
4035
</PackageReference>
4136
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.2.0" />
4237
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
43-
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.1742" />
4438
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.6.250108002" />
4539
</ItemGroup>
4640

47-
<!--
48-
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
49-
Tools extension to be activated for this project even if the Windows App SDK Nuget
50-
package has not yet been restored.
51-
-->
52-
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
53-
<ProjectCapability Include="Msix" />
54-
</ItemGroup>
5541
<ItemGroup>
5642
<ProjectReference Include="..\CoderSdk\CoderSdk.csproj" />
5743
<ProjectReference Include="..\Vpn.Proto\Vpn.Proto.csproj" />
5844
<ProjectReference Include="..\Vpn\Vpn.csproj" />
5945
</ItemGroup>
60-
61-
<!--
62-
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
63-
Explorer "Package and Publish" context menu entry to be enabled for this project even if
64-
the Windows App SDK Nuget package has not yet been restored.
65-
-->
66-
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
67-
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
68-
</PropertyGroup>
69-
70-
<!-- Publish Properties -->
71-
<PropertyGroup>
72-
<!--
73-
This does not work in CI at the moment, so we need to set it to false
74-
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]
75-
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
76-
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
77-
-->
78-
<PublishReadyToRun>False</PublishReadyToRun>
79-
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
80-
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
81-
</PropertyGroup>
8246
</Project>

App/App.xaml.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ namespace Coder.Desktop.App;
1212
public partial class App : Application
1313
{
1414
private readonly IServiceProvider _services;
15-
private readonly bool _handleClosedEvents = true;
1615

1716
public App()
1817
{
@@ -49,12 +48,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
4948
var trayWindow = _services.GetRequiredService<TrayWindow>();
5049
trayWindow.Closed += (sender, args) =>
5150
{
52-
// TODO: wire up HandleClosedEvents properly
53-
if (_handleClosedEvents)
54-
{
55-
args.Handled = true;
56-
trayWindow.AppWindow.Hide();
57-
}
51+
args.Handled = true;
52+
trayWindow.AppWindow.Hide();
5853
};
5954
}
6055
}

App/Converters/VpnLifecycleToBoolConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Coder.Desktop.App.Converters;
88

9+
[DependencyProperty<bool>("Unknown", DefaultValue = false)]
910
[DependencyProperty<bool>("Starting", DefaultValue = false)]
1011
[DependencyProperty<bool>("Started", DefaultValue = false)]
1112
[DependencyProperty<bool>("Stopping", DefaultValue = false)]
@@ -18,6 +19,7 @@ public object Convert(object value, Type targetType, object parameter, string la
1819

1920
return lifecycle switch
2021
{
22+
VpnLifecycle.Unknown => Unknown,
2123
VpnLifecycle.Starting => Starting,
2224
VpnLifecycle.Started => Started,
2325
VpnLifecycle.Stopping => Stopping,

App/Models/RpcModel.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Collections.Generic;
2+
using System.Linq;
3+
using Coder.Desktop.Vpn.Proto;
24

35
namespace Coder.Desktop.App.Models;
46

@@ -11,6 +13,7 @@ public enum RpcLifecycle
1113

1214
public enum VpnLifecycle
1315
{
16+
Unknown,
1417
Stopped,
1518
Starting,
1619
Started,
@@ -21,17 +24,20 @@ public class RpcModel
2124
{
2225
public RpcLifecycle RpcLifecycle { get; set; } = RpcLifecycle.Disconnected;
2326

24-
public VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Stopped;
27+
public VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Unknown;
2528

26-
public List<object> Agents { get; set; } = [];
29+
public List<Workspace> Workspaces { get; set; } = [];
30+
31+
public List<Agent> Agents { get; set; } = [];
2732

2833
public RpcModel Clone()
2934
{
3035
return new RpcModel
3136
{
3237
RpcLifecycle = RpcLifecycle,
3338
VpnLifecycle = VpnLifecycle,
34-
Agents = Agents,
39+
Workspaces = Workspaces.ToList(),
40+
Agents = Agents.ToList(),
3541
};
3642
}
3743
}

App/Properties/launchSettings.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
{
22
"profiles": {
3-
"App (Package)": {
4-
"commandName": "MsixPackage"
5-
},
63
"App (Unpackaged)": {
74
"commandName": "Project"
85
}

App/Services/CredentialManager.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ public async Task SetCredentials(string coderUrl, string apiToken, CancellationT
6666

6767
try
6868
{
69+
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
70+
cts.CancelAfter(TimeSpan.FromSeconds(15));
6971
var sdkClient = new CoderApiClient(uri);
7072
sdkClient.SetSessionToken(apiToken);
7173
// TODO: we should probably perform a version check here too,
7274
// rather than letting the service do it on Start
73-
_ = await sdkClient.GetBuildInfo(ct);
74-
_ = await sdkClient.GetUser(User.Me, ct);
75+
_ = await sdkClient.GetBuildInfo(cts.Token);
76+
_ = await sdkClient.GetUser(User.Me, cts.Token);
7577
}
7678
catch (Exception e)
7779
{

App/Services/RpcController.cs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public async Task Reconnect(CancellationToken ct = default)
9696
{
9797
state.RpcLifecycle = RpcLifecycle.Connecting;
9898
state.VpnLifecycle = VpnLifecycle.Stopped;
99+
state.Workspaces.Clear();
99100
state.Agents.Clear();
100101
});
101102

@@ -125,7 +126,8 @@ public async Task Reconnect(CancellationToken ct = default)
125126
MutateState(state =>
126127
{
127128
state.RpcLifecycle = RpcLifecycle.Disconnected;
128-
state.VpnLifecycle = VpnLifecycle.Stopped;
129+
state.VpnLifecycle = VpnLifecycle.Unknown;
130+
state.Workspaces.Clear();
129131
state.Agents.Clear();
130132
});
131133
throw new RpcOperationException("Failed to reconnect to the RPC server", e);
@@ -134,10 +136,18 @@ public async Task Reconnect(CancellationToken ct = default)
134136
MutateState(state =>
135137
{
136138
state.RpcLifecycle = RpcLifecycle.Connected;
137-
// TODO: fetch current state
138-
state.VpnLifecycle = VpnLifecycle.Stopped;
139+
state.VpnLifecycle = VpnLifecycle.Unknown;
140+
state.Workspaces.Clear();
139141
state.Agents.Clear();
140142
});
143+
144+
var statusReply = await _speaker.SendRequestAwaitReply(new ClientMessage
145+
{
146+
Status = new StatusRequest(),
147+
}, ct);
148+
if (statusReply.MsgCase != ServiceMessage.MsgOneofCase.Status)
149+
throw new InvalidOperationException($"Unexpected reply message type: {statusReply.MsgCase}");
150+
ApplyStatusUpdate(statusReply.Status);
141151
}
142152

143153
public async Task StartVpn(CancellationToken ct = default)
@@ -234,9 +244,40 @@ private async Task<IDisposable> AcquireOperationLockNowAsync()
234244
return locker;
235245
}
236246

247+
private void ApplyStatusUpdate(Status status)
248+
{
249+
MutateState(state =>
250+
{
251+
state.VpnLifecycle = status.Lifecycle switch
252+
{
253+
Status.Types.Lifecycle.Unknown => VpnLifecycle.Unknown,
254+
Status.Types.Lifecycle.Starting => VpnLifecycle.Starting,
255+
Status.Types.Lifecycle.Started => VpnLifecycle.Started,
256+
Status.Types.Lifecycle.Stopping => VpnLifecycle.Stopping,
257+
Status.Types.Lifecycle.Stopped => VpnLifecycle.Stopped,
258+
_ => VpnLifecycle.Stopped,
259+
};
260+
state.Workspaces.Clear();
261+
state.Workspaces.AddRange(status.PeerUpdate.UpsertedWorkspaces);
262+
state.Agents.Clear();
263+
state.Agents.AddRange(status.PeerUpdate.UpsertedAgents);
264+
});
265+
}
266+
237267
private void SpeakerOnReceive(ReplyableRpcMessage<ClientMessage, ServiceMessage> message)
238268
{
239-
// TODO: this
269+
switch (message.Message.MsgCase)
270+
{
271+
case ServiceMessage.MsgOneofCase.Status:
272+
ApplyStatusUpdate(message.Message.Status);
273+
break;
274+
case ServiceMessage.MsgOneofCase.Start:
275+
case ServiceMessage.MsgOneofCase.Stop:
276+
case ServiceMessage.MsgOneofCase.None:
277+
default:
278+
// TODO: log unexpected message
279+
break;
280+
}
240281
}
241282

242283
private async Task DisposeSpeaker()
@@ -251,7 +292,14 @@ private async Task DisposeSpeaker()
251292
private void SpeakerOnError(Exception e)
252293
{
253294
Debug.WriteLine($"Error: {e}");
254-
Reconnect(CancellationToken.None).Wait();
295+
try
296+
{
297+
Reconnect(CancellationToken.None).Wait();
298+
}
299+
catch
300+
{
301+
// best effort to immediately reconnect
302+
}
255303
}
256304

257305
private void AssertRpcConnected()

0 commit comments

Comments
 (0)
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