1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . Linq ;
4
+ using System . Threading . Tasks ;
4
5
using Coder . Desktop . App . Models ;
5
6
using Coder . Desktop . App . Services ;
6
7
using Coder . Desktop . Vpn . Proto ;
10
11
using Microsoft . UI . Dispatching ;
11
12
using Microsoft . UI . Xaml ;
12
13
using Microsoft . UI . Xaml . Controls ;
14
+ using Exception = System . Exception ;
13
15
14
16
namespace Coder . Desktop . App . ViewModels ;
15
17
@@ -23,22 +25,46 @@ public partial class TrayWindowViewModel : ObservableObject
23
25
24
26
private DispatcherQueue ? _dispatcherQueue ;
25
27
26
- [ ObservableProperty ] public partial VpnLifecycle VpnLifecycle { get ; set ; } = VpnLifecycle . Unknown ;
28
+ [ ObservableProperty ]
29
+ [ NotifyPropertyChangedFor ( nameof ( ShowEnableSection ) ) ]
30
+ [ NotifyPropertyChangedFor ( nameof ( ShowWorkspacesHeader ) ) ]
31
+ [ NotifyPropertyChangedFor ( nameof ( ShowNoAgentsSection ) ) ]
32
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentsSection ) ) ]
33
+ [ NotifyPropertyChangedFor ( nameof ( ShowFailedSection ) ) ]
34
+ public partial VpnLifecycle VpnLifecycle { get ; set ; } = VpnLifecycle . Unknown ;
27
35
28
36
// This is a separate property because we need the switch to be 2-way.
29
37
[ ObservableProperty ] public partial bool VpnSwitchActive { get ; set ; } = false ;
30
38
31
- [ ObservableProperty ] public partial string ? VpnFailedMessage { get ; set ; } = null ;
39
+ [ ObservableProperty ]
40
+ [ NotifyPropertyChangedFor ( nameof ( ShowEnableSection ) ) ]
41
+ [ NotifyPropertyChangedFor ( nameof ( ShowWorkspacesHeader ) ) ]
42
+ [ NotifyPropertyChangedFor ( nameof ( ShowNoAgentsSection ) ) ]
43
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentsSection ) ) ]
44
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentOverflowButton ) ) ]
45
+ [ NotifyPropertyChangedFor ( nameof ( ShowFailedSection ) ) ]
46
+ public partial string ? VpnFailedMessage { get ; set ; } = null ;
32
47
33
48
[ ObservableProperty ]
34
- [ NotifyPropertyChangedFor ( nameof ( NoAgents ) ) ]
35
- [ NotifyPropertyChangedFor ( nameof ( AgentOverflow ) ) ]
36
49
[ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
50
+ [ NotifyPropertyChangedFor ( nameof ( ShowNoAgentsSection ) ) ]
51
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentsSection ) ) ]
52
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentOverflowButton ) ) ]
37
53
public partial List < AgentViewModel > Agents { get ; set ; } = [ ] ;
38
54
39
- public bool NoAgents => Agents . Count == 0 ;
55
+ public bool ShowEnableSection => VpnFailedMessage is null && VpnLifecycle is not VpnLifecycle . Started ;
56
+
57
+ public bool ShowWorkspacesHeader => VpnFailedMessage is null && VpnLifecycle is VpnLifecycle . Started ;
58
+
59
+ public bool ShowNoAgentsSection =>
60
+ VpnFailedMessage is null && Agents . Count == 0 && VpnLifecycle is VpnLifecycle . Started ;
61
+
62
+ public bool ShowAgentsSection =>
63
+ VpnFailedMessage is null && Agents . Count > 0 && VpnLifecycle is VpnLifecycle . Started ;
64
+
65
+ public bool ShowFailedSection => VpnFailedMessage is not null ;
40
66
41
- public bool AgentOverflow => Agents . Count > MaxAgents ;
67
+ public bool ShowAgentOverflowButton => VpnFailedMessage is null && Agents . Count > MaxAgents ;
42
68
43
69
[ ObservableProperty ]
44
70
[ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
@@ -190,24 +216,47 @@ public void VpnSwitch_Toggled(object sender, RoutedEventArgs e)
190
216
{
191
217
if ( sender is not ToggleSwitch toggleSwitch ) return ;
192
218
193
- VpnFailedMessage = "" ;
219
+ VpnFailedMessage = null ;
220
+
221
+ // The start/stop methods will call back to update the state.
222
+ if ( toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Stopped )
223
+ _ = StartVpn ( ) ; // in the background
224
+ else if ( ! toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Started )
225
+ _ = StopVpn ( ) ; // in the background
226
+ else
227
+ toggleSwitch . IsOn = VpnLifecycle is VpnLifecycle . Starting or VpnLifecycle . Started ;
228
+ }
229
+
230
+ private async Task StartVpn ( )
231
+ {
194
232
try
195
233
{
196
- // The start/stop methods will call back to update the state.
197
- if ( toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Stopped )
198
- _rpcController . StartVpn ( ) ;
199
- else if ( ! toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Started )
200
- _rpcController . StopVpn ( ) ;
201
- else
202
- toggleSwitch . IsOn = VpnLifecycle is VpnLifecycle . Starting or VpnLifecycle . Started ;
234
+ await _rpcController . StartVpn ( ) ;
203
235
}
204
- catch
236
+ catch ( Exception e )
205
237
{
206
- // TODO: display error
207
- VpnFailedMessage = e . ToString ( ) ;
238
+ VpnFailedMessage = "Failed to start CoderVPN: " + MaybeUnwrapTunnelError ( e ) ;
208
239
}
209
240
}
210
241
242
+ private async Task StopVpn ( )
243
+ {
244
+ try
245
+ {
246
+ await _rpcController . StopVpn ( ) ;
247
+ }
248
+ catch ( Exception e )
249
+ {
250
+ VpnFailedMessage = "Failed to stop CoderVPN: " + MaybeUnwrapTunnelError ( e ) ;
251
+ }
252
+ }
253
+
254
+ private static string MaybeUnwrapTunnelError ( Exception e )
255
+ {
256
+ if ( e is VpnLifecycleException vpnError ) return vpnError . Message ;
257
+ return e . ToString ( ) ;
258
+ }
259
+
211
260
[ RelayCommand ]
212
261
public void ToggleShowAllAgents ( )
213
262
{
0 commit comments