Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: show vpn start/stop failure in app#44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
deansheather merged 2 commits intomainfromdean/vpn-start-error
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletionApp/Converters/AgentStatusToColorConverter.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,7 @@ namespace Coder.Desktop.App.Converters;
public class AgentStatusToColorConverter : IValueConverter
{
private static readonly SolidColorBrush Green = new(Color.FromArgb(255, 52, 199, 89));
private static readonly SolidColorBrush Yellow = new(Color.FromArgb(255,204, 1, 0));
private static readonly SolidColorBrush Yellow = new(Color.FromArgb(255,255, 204, 1));
private static readonly SolidColorBrush Red = new(Color.FromArgb(255, 255, 59, 48));
private static readonly SolidColorBrush Gray = new(Color.FromArgb(255, 142, 142, 147));

Expand Down
12 changes: 0 additions & 12 deletionsApp/Converters/InverseBoolToVisibilityConverter.cs
View file
Open in desktop

This file was deleted.

14 changes: 0 additions & 14 deletionsApp/Converters/VpnLifecycleToVisibilityConverter.cs
View file
Open in desktop

This file was deleted.

33 changes: 22 additions & 11 deletionsApp/Services/RpcController.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -146,7 +146,8 @@ public async Task Reconnect(CancellationToken ct = default)
Status = new StatusRequest(),
}, ct);
if (statusReply.MsgCase != ServiceMessage.MsgOneofCase.Status)
throw new InvalidOperationException($"Unexpected reply message type: {statusReply.MsgCase}");
throw new VpnLifecycleException(
$"Failed to get VPN status. Unexpected reply message type: {statusReply.MsgCase}");
ApplyStatusUpdate(statusReply.Status);
}

Expand All@@ -172,20 +173,26 @@ public async Task StartVpn(CancellationToken ct = default)
ApiToken = credentials.ApiToken,
},
}, ct);
if (reply.MsgCase != ServiceMessage.MsgOneofCase.Start)
throw new InvalidOperationException($"Unexpected reply message type: {reply.MsgCase}");
}
catch (Exception e)
{
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Stopped; });
throw new RpcOperationException("Failed to send start command to service", e);
}

if (reply.MsgCase != ServiceMessage.MsgOneofCase.Start)
{
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Unknown; });
throw new VpnLifecycleException($"Failed to start VPN. Unexpected reply message type: {reply.MsgCase}");
}

if (!reply.Start.Success)
{
// We use Stopped instead of Unknown here as it's usually the case
// that a failed start got cleaned up successfully.
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Stopped; });
throw new VpnLifecycleException("Failed to start VPN",
new InvalidOperationException($"Service reported failure: {reply.Start.ErrorMessage}"));
throw new VpnLifecycleException(
$"Failed to start VPN.Service reported failure: {reply.Start.ErrorMessage}");
}

MutateState(state => { state.VpnLifecycle = VpnLifecycle.Started; });
Expand All@@ -212,16 +219,20 @@ public async Task StopVpn(CancellationToken ct = default)
}
finally
{
// Technically the state is unknown now.
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Stopped; });
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Unknown; });
}

if (reply.MsgCase != ServiceMessage.MsgOneofCase.Stop)
throw new VpnLifecycleException("Failed to stop VPN",
new InvalidOperationException($"Unexpected reply message type: {reply.MsgCase}"));
{
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Unknown; });
throw new VpnLifecycleException($"Failed to stop VPN. Unexpected reply message type: {reply.MsgCase}");
}

if (!reply.Stop.Success)
throw new VpnLifecycleException("Failed to stop VPN",
new InvalidOperationException($"Service reported failure: {reply.Stop.ErrorMessage}"));
{
MutateState(state => { state.VpnLifecycle = VpnLifecycle.Unknown; });
throw new VpnLifecycleException($"Failed to stop VPN. Service reported failure: {reply.Stop.ErrorMessage}");
}
}

public async ValueTask DisposeAsync()
Expand Down
82 changes: 65 additions & 17 deletionsApp/ViewModels/TrayWindowViewModel.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Coder.Desktop.App.Models;
using Coder.Desktop.App.Services;
using Coder.Desktop.Vpn.Proto;
Expand All@@ -10,6 +11,7 @@
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Exception = System.Exception;

namespace Coder.Desktop.App.ViewModels;

Expand All@@ -23,22 +25,45 @@ public partial class TrayWindowViewModel : ObservableObject

private DispatcherQueue? _dispatcherQueue;

[ObservableProperty] public partial VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Unknown;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowEnableSection))]
[NotifyPropertyChangedFor(nameof(ShowWorkspacesHeader))]
[NotifyPropertyChangedFor(nameof(ShowNoAgentsSection))]
[NotifyPropertyChangedFor(nameof(ShowAgentsSection))]
public partial VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Unknown;

// This is a separate property because we need the switch to be 2-way.
[ObservableProperty] public partial bool VpnSwitchActive { get; set; } = false;

[ObservableProperty] public partial string? VpnFailedMessage { get; set; } = null;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowEnableSection))]
[NotifyPropertyChangedFor(nameof(ShowWorkspacesHeader))]
[NotifyPropertyChangedFor(nameof(ShowNoAgentsSection))]
[NotifyPropertyChangedFor(nameof(ShowAgentsSection))]
[NotifyPropertyChangedFor(nameof(ShowAgentOverflowButton))]
[NotifyPropertyChangedFor(nameof(ShowFailedSection))]
public partial string? VpnFailedMessage { get; set; } = null;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(NoAgents))]
[NotifyPropertyChangedFor(nameof(AgentOverflow))]
[NotifyPropertyChangedFor(nameof(VisibleAgents))]
[NotifyPropertyChangedFor(nameof(ShowNoAgentsSection))]
[NotifyPropertyChangedFor(nameof(ShowAgentsSection))]
[NotifyPropertyChangedFor(nameof(ShowAgentOverflowButton))]
public partial List<AgentViewModel> Agents { get; set; } = [];

public bool NoAgents => Agents.Count == 0;
public bool ShowEnableSection => VpnFailedMessage is null && VpnLifecycle is not VpnLifecycle.Started;

public bool ShowWorkspacesHeader => VpnFailedMessage is null && VpnLifecycle is VpnLifecycle.Started;

public bool ShowNoAgentsSection =>
VpnFailedMessage is null && Agents.Count == 0 && VpnLifecycle is VpnLifecycle.Started;

public bool ShowAgentsSection =>
VpnFailedMessage is null && Agents.Count > 0 && VpnLifecycle is VpnLifecycle.Started;

public bool ShowFailedSection => VpnFailedMessage is not null;

public boolAgentOverflow => Agents.Count > MaxAgents;
public boolShowAgentOverflowButton => VpnFailedMessage is null && Agents.Count > MaxAgents;

[ObservableProperty]
[NotifyPropertyChangedFor(nameof(VisibleAgents))]
Expand DownExpand Up@@ -190,24 +215,47 @@ public void VpnSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (sender is not ToggleSwitch toggleSwitch) return;

VpnFailedMessage = "";
VpnFailedMessage = null;

// The start/stop methods will call back to update the state.
if (toggleSwitch.IsOn && VpnLifecycle is VpnLifecycle.Stopped)
_ = StartVpn(); // in the background
else if (!toggleSwitch.IsOn && VpnLifecycle is VpnLifecycle.Started)
_ = StopVpn(); // in the background
else
toggleSwitch.IsOn = VpnLifecycle is VpnLifecycle.Starting or VpnLifecycle.Started;
}

private async Task StartVpn()
{
try
{
// The start/stop methods will call back to update the state.
if (toggleSwitch.IsOn && VpnLifecycle is VpnLifecycle.Stopped)
_rpcController.StartVpn();
else if (!toggleSwitch.IsOn && VpnLifecycle is VpnLifecycle.Started)
_rpcController.StopVpn();
else
toggleSwitch.IsOn = VpnLifecycle is VpnLifecycle.Starting or VpnLifecycle.Started;
await _rpcController.StartVpn();
}
catch
catch (Exception e)
{
// TODO: display error
VpnFailedMessage = e.ToString();
VpnFailedMessage = "Failed to start CoderVPN: " + MaybeUnwrapTunnelError(e);
}
}

private async Task StopVpn()
{
try
{
await _rpcController.StopVpn();
}
catch (Exception e)
{
VpnFailedMessage = "Failed to stop CoderVPN: " + MaybeUnwrapTunnelError(e);
}
}

private static string MaybeUnwrapTunnelError(Exception e)
{
if (e is VpnLifecycleException vpnError) return vpnError.Message;
return e.ToString();
}

[RelayCommand]
public void ToggleShowAllAgents()
{
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp