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,45 @@ 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
+ public partial VpnLifecycle VpnLifecycle { get ; set ; } = VpnLifecycle . Unknown ;
27
34
28
35
// This is a separate property because we need the switch to be 2-way.
29
36
[ ObservableProperty ] public partial bool VpnSwitchActive { get ; set ; } = false ;
30
37
31
- [ ObservableProperty ] public partial string ? VpnFailedMessage { get ; set ; } = null ;
38
+ [ ObservableProperty ]
39
+ [ NotifyPropertyChangedFor ( nameof ( ShowEnableSection ) ) ]
40
+ [ NotifyPropertyChangedFor ( nameof ( ShowWorkspacesHeader ) ) ]
41
+ [ NotifyPropertyChangedFor ( nameof ( ShowNoAgentsSection ) ) ]
42
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentsSection ) ) ]
43
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentOverflowButton ) ) ]
44
+ [ NotifyPropertyChangedFor ( nameof ( ShowFailedSection ) ) ]
45
+ public partial string ? VpnFailedMessage { get ; set ; } = null ;
32
46
33
47
[ ObservableProperty ]
34
- [ NotifyPropertyChangedFor ( nameof ( NoAgents ) ) ]
35
- [ NotifyPropertyChangedFor ( nameof ( AgentOverflow ) ) ]
36
48
[ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
49
+ [ NotifyPropertyChangedFor ( nameof ( ShowNoAgentsSection ) ) ]
50
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentsSection ) ) ]
51
+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentOverflowButton ) ) ]
37
52
public partial List < AgentViewModel > Agents { get ; set ; } = [ ] ;
38
53
39
- public bool NoAgents => Agents . Count == 0 ;
54
+ public bool ShowEnableSection => VpnFailedMessage is null && VpnLifecycle is not VpnLifecycle . Started ;
55
+
56
+ public bool ShowWorkspacesHeader => VpnFailedMessage is null && VpnLifecycle is VpnLifecycle . Started ;
57
+
58
+ public bool ShowNoAgentsSection =>
59
+ VpnFailedMessage is null && Agents . Count == 0 && VpnLifecycle is VpnLifecycle . Started ;
60
+
61
+ public bool ShowAgentsSection =>
62
+ VpnFailedMessage is null && Agents . Count > 0 && VpnLifecycle is VpnLifecycle . Started ;
63
+
64
+ public bool ShowFailedSection => VpnFailedMessage is not null ;
40
65
41
- public bool AgentOverflow => Agents . Count > MaxAgents ;
66
+ public bool ShowAgentOverflowButton => VpnFailedMessage is null && Agents . Count > MaxAgents ;
42
67
43
68
[ ObservableProperty ]
44
69
[ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
@@ -190,24 +215,47 @@ public void VpnSwitch_Toggled(object sender, RoutedEventArgs e)
190
215
{
191
216
if ( sender is not ToggleSwitch toggleSwitch ) return ;
192
217
193
- VpnFailedMessage = "" ;
218
+ VpnFailedMessage = null ;
219
+
220
+ // The start/stop methods will call back to update the state.
221
+ if ( toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Stopped )
222
+ _ = StartVpn ( ) ; // in the background
223
+ else if ( ! toggleSwitch . IsOn && VpnLifecycle is VpnLifecycle . Started )
224
+ _ = StopVpn ( ) ; // in the background
225
+ else
226
+ toggleSwitch . IsOn = VpnLifecycle is VpnLifecycle . Starting or VpnLifecycle . Started ;
227
+ }
228
+
229
+ private async Task StartVpn ( )
230
+ {
194
231
try
195
232
{
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 ;
233
+ await _rpcController . StartVpn ( ) ;
203
234
}
204
- catch
235
+ catch ( Exception e )
205
236
{
206
- // TODO: display error
207
- VpnFailedMessage = e . ToString ( ) ;
237
+ VpnFailedMessage = "Failed to start CoderVPN: " + MaybeUnwrapTunnelError ( e ) ;
208
238
}
209
239
}
210
240
241
+ private async Task StopVpn ( )
242
+ {
243
+ try
244
+ {
245
+ await _rpcController . StopVpn ( ) ;
246
+ }
247
+ catch ( Exception e )
248
+ {
249
+ VpnFailedMessage = "Failed to stop CoderVPN: " + MaybeUnwrapTunnelError ( e ) ;
250
+ }
251
+ }
252
+
253
+ private static string MaybeUnwrapTunnelError ( Exception e )
254
+ {
255
+ if ( e is VpnLifecycleException vpnError ) return vpnError . Message ;
256
+ return e . ToString ( ) ;
257
+ }
258
+
211
259
[ RelayCommand ]
212
260
public void ToggleShowAllAgents ( )
213
261
{
0 commit comments