11using System ;
22using System . Collections . Generic ;
33using System . Linq ;
4+ using System . Threading . Tasks ;
45using Coder . Desktop . App . Models ;
56using Coder . Desktop . App . Services ;
67using Coder . Desktop . Vpn . Proto ;
1011using Microsoft . UI . Dispatching ;
1112using Microsoft . UI . Xaml ;
1213using Microsoft . UI . Xaml . Controls ;
14+ using Exception = System . Exception ;
1315
1416namespace Coder . Desktop . App . ViewModels ;
1517
@@ -23,22 +25,45 @@ public partial class TrayWindowViewModel : ObservableObject
2325
2426 private DispatcherQueue ? _dispatcherQueue ;
2527
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 ;
2734
2835 // This is a separate property because we need the switch to be 2-way.
2936 [ ObservableProperty ] public partial bool VpnSwitchActive { get ; set ; } = false ;
3037
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 ;
3246
3347 [ ObservableProperty ]
34- [ NotifyPropertyChangedFor ( nameof ( NoAgents ) ) ]
35- [ NotifyPropertyChangedFor ( nameof ( AgentOverflow ) ) ]
3648 [ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
49+ [ NotifyPropertyChangedFor ( nameof ( ShowNoAgentsSection ) ) ]
50+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentsSection ) ) ]
51+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentOverflowButton ) ) ]
3752 public partial List < AgentViewModel > Agents { get ; set ; } = [ ] ;
3853
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 ;
4065
41- public bool AgentOverflow => Agents . Count > MaxAgents ;
66+ public bool ShowAgentOverflowButton => VpnFailedMessage is null && Agents . Count > MaxAgents ;
4267
4368 [ ObservableProperty ]
4469 [ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
@@ -190,24 +215,47 @@ public void VpnSwitch_Toggled(object sender, RoutedEventArgs e)
190215 {
191216 if ( sender is not ToggleSwitch toggleSwitch ) return ;
192217
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+ {
194231 try
195232 {
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 ( ) ;
203234 }
204- catch
235+ catch ( Exception e )
205236 {
206- // TODO: display error
207- VpnFailedMessage = e . ToString ( ) ;
237+ VpnFailedMessage = "Failed to start CoderVPN: " + MaybeUnwrapTunnelError ( e ) ;
208238 }
209239 }
210240
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+
211259 [ RelayCommand ]
212260 public void ToggleShowAllAgents ( )
213261 {
0 commit comments