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,46 @@ 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+ [ NotifyPropertyChangedFor ( nameof ( ShowFailedSection ) ) ]
34+ public partial VpnLifecycle VpnLifecycle { get ; set ; } = VpnLifecycle . Unknown ;
2735
2836 // This is a separate property because we need the switch to be 2-way.
2937 [ ObservableProperty ] public partial bool VpnSwitchActive { get ; set ; } = false ;
3038
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 ;
3247
3348 [ ObservableProperty ]
34- [ NotifyPropertyChangedFor ( nameof ( NoAgents ) ) ]
35- [ NotifyPropertyChangedFor ( nameof ( AgentOverflow ) ) ]
3649 [ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
50+ [ NotifyPropertyChangedFor ( nameof ( ShowNoAgentsSection ) ) ]
51+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentsSection ) ) ]
52+ [ NotifyPropertyChangedFor ( nameof ( ShowAgentOverflowButton ) ) ]
3753 public partial List < AgentViewModel > Agents { get ; set ; } = [ ] ;
3854
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 ;
4066
41- public bool AgentOverflow => Agents . Count > MaxAgents ;
67+ public bool ShowAgentOverflowButton => VpnFailedMessage is null && Agents . Count > MaxAgents ;
4268
4369 [ ObservableProperty ]
4470 [ NotifyPropertyChangedFor ( nameof ( VisibleAgents ) ) ]
@@ -190,24 +216,47 @@ public void VpnSwitch_Toggled(object sender, RoutedEventArgs e)
190216 {
191217 if ( sender is not ToggleSwitch toggleSwitch ) return ;
192218
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+ {
194232 try
195233 {
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 ( ) ;
203235 }
204- catch
236+ catch ( Exception e )
205237 {
206- // TODO: display error
207- VpnFailedMessage = e . ToString ( ) ;
238+ VpnFailedMessage = "Failed to start CoderVPN: " + MaybeUnwrapTunnelError ( e ) ;
208239 }
209240 }
210241
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+
211260 [ RelayCommand ]
212261 public void ToggleShowAllAgents ( )
213262 {
0 commit comments