Skip to content

Commit

Permalink
Merge pull request #19 from Fresa/improve-ui-rendering
Browse files Browse the repository at this point in the history
Improve UI rendering
  • Loading branch information
Fresa authored Sep 2, 2021
2 parents a7a6c6d + 10c9a51 commit ab39ee4
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 74 deletions.
151 changes: 102 additions & 49 deletions src/Client/Pages/PortForward.razor
Original file line number Diff line number Diff line change
Expand Up @@ -72,38 +72,49 @@ else
</tr>
</thead>
<tbody>
<Virtualize Context="serviceGroups" Items="@(_portForwards.GroupBy(forward => $"{forward.Model.Namespace}.{forward.Model.Service}").ToList())">
@{ var renderService = true; }
<Virtualize Context="podGroups" Items="serviceGroups.GroupBy(forward => forward.Model.Pod).ToList()">
@{ var renderPod = true; }
<Virtualize Context="portForward" Items="@podGroups.ToList()">
@foreach (var serviceGroups in _portForwards.Values.GroupBy(forward => $"{forward.Model.Namespace}.{forward.Model.Service}"))
{
var renderService = true;
@foreach (var podGroups in serviceGroups.GroupBy(forward => forward.Model.Pod))
{
var renderPod = true;
@foreach (var portForward in podGroups)
{
<tr>
<td class="@(renderService ? "" : "no-borders")">@(renderService ? portForward.Model.Namespace : "")</td>
<td class="@(renderService ? "" : "no-borders")">@(renderService ? portForward.Model.Service : "")</td>
<td class="@(renderPod ? "" : "no-borders")">@(renderPod ? portForward.Model.Pod : "")</td>
<td>@portForward.Model.PodPort</td>
<td>@portForward.Model.ProtocolType</td>
<td>
<MatTextField TValue="int?" @bind-Value="@portForward.Model.LocalPort" />
</td>
<td>
<MatSlideToggle TValue="bool" Value="@portForward.Model.Forwarding" ValueChanged="@(enable => ChangePortForwardAsync(portForward, enable))" />
</td>
<ForwardRefContext TRef="MatSlideToggle<bool>"
@key="@portForward.Model.Forwarding">
<td>
<MatTextField TValue="int?"
Value="@portForward.Model.LocalPort"
OnInput="@(changeEventArgs => OnLocalPortInput(changeEventArgs, portForward, context.Current))"
Disabled="@portForward.Model.Forwarding" />
</td>
<td>
<MatSlideToggle TValue="bool"
@ref="@context.Current"
Value="@portForward.Model.Forwarding"
Disabled="@(!portForward.Model.LocalPort.HasValue)"
ValueChanged="@(enable => ChangePortForwardAsync(portForward, enable))" />
</td>
</ForwardRefContext>
</tr>
@{
renderPod = false;
renderService = false;
}
</Virtualize>
</Virtualize>
</Virtualize>
renderPod = false;
renderService = false;
}
}
}
</tbody>
</table>
}

@code
{
private readonly List<PortForwardService> _portForwards = new();
private Dictionary<string, PortForwardService> _portForwards = new();
private Deployment[] _deployments = new Deployment[0];
private Pod[]? _pods;
private readonly CancellationTokenSource _cts = new();
Expand All @@ -117,63 +128,105 @@ else

private readonly SemaphoreSlimGate _gate = SemaphoreSlimGate.OneAtATime;


protected override async Task OnParametersSetAsync()
{
using (await _gate.WaitAsync(CancellationToken)
.ConfigureAwait(false))
using (await _gate.WaitAsync(CancellationToken))
{
if (Error.HasError())
if (Error.HasError)
{
return;
}

try
{
_deployments = await Http.GetFromNewtonsoftJsonAsync<Deployment[]>($"Deployment/{Context}", cancellationToken: CancellationToken)
.ConfigureAwait(false);
_pods = await Http.GetFromNewtonsoftJsonAsync<Pod[]>($"Pod/{Context}", cancellationToken: CancellationToken)
.ConfigureAwait(false);
var services = await Http.GetFromNewtonsoftJsonAsync<Service[]>($"Service/{Context}", cancellationToken: CancellationToken)
.ConfigureAwait(false);
foreach (var service in services)
_deployments = await Http.GetFromNewtonsoftJsonAsync<Deployment[]>($"Deployment/{Context}", cancellationToken: CancellationToken);
_pods = await Http.GetFromNewtonsoftJsonAsync<Pod[]>($"Pod/{Context}", cancellationToken: CancellationToken);
var services = await Http.GetFromNewtonsoftJsonAsync<Service[]>($"Service/{Context}", cancellationToken: CancellationToken);

var portForwards = new Dictionary<string, PortForwardService>();
foreach (var service in services
.OrderBy(service => service.Namespace)
.ThenBy(service => service.Name))
{
foreach (var pod in _pods.WhereServiceIsHosted(service))
foreach (var pod in _pods.WhereServiceIsHosted(service)
.OrderBy(pod => pod.Name))
{
foreach (var port in service.Ports)
foreach (var port in service.Ports
.OrderBy(port => port.Number))
{
var forwarder = new PortForwardService(
new PortForwarder.PortForwarderClient(Channel),
Context,
new global::Port.Shared.PortForward(
service.Namespace,
pod.Name,
service.Name,
port.ProtocolType,
port.Number));
forwarder.OnStateChanged += StateHasChanged;
_portForwards.Add(forwarder);
var id = service.Namespace +
pod.Name +
service.Name +
port.ProtocolType +
port.Number;

if (!_portForwards.Remove(id, out var forwarder))
{
forwarder = new PortForwardService(
new PortForwarder.PortForwarderClient(Channel),
Context,
new global::Port.Shared.PortForward(
service.Namespace,
pod.Name,
service.Name,
port.ProtocolType,
port.Number));
forwarder.OnStateChangedAsync += () => InvokeAsync(StateHasChanged);
forwarder.OnErrorAsync += Error.ProcessErrorAsync;
}
portForwards.Add(id, forwarder);
}
}
}

await Task.WhenAll(_portForwards.Values.Select(forwarder => forwarder.DisposeAsync())
.Where(valueTask => !valueTask.IsCompletedSuccessfully)
.Select(valueTask => valueTask.AsTask()));
_portForwards = portForwards;
}
catch (Exception e)
{
Error.ProcessError(e);
await Error.ProcessErrorAsync(e);
}
}
}

private static Task ChangePortForwardAsync(
private async Task ChangePortForwardAsync(
PortForwardService portForward,
bool forward) =>
forward ?
portForward.ForwardAsync() :
portForward.StopAsync();
bool forward)
{
try
{
if (forward)
await portForward.ForwardAsync();
else
await portForward.StopAsync();

portForward.Model.Forwarding = forward;
}
catch (Exception e)
{
await Error.ProcessErrorAsync(e);
}
}

private static void OnLocalPortInput(
ChangeEventArgs args,
PortForwardService portForward,
MatSlideToggle<bool> toggle)
{
portForward.Model.LocalPort =
int.TryParse(args.Value as string, out var input) ?
input : null;

toggle.InvokeStateHasChanged();
}

public async ValueTask DisposeAsync()
{
_cts.Cancel();
await Task.WhenAll(_portForwards.Select(forwarder => forwarder.DisposeAsync())
await Task.WhenAll(_portForwards.Values.Select(forwarder => forwarder.DisposeAsync())
.Where(valueTask => !valueTask.IsCompletedSuccessfully)
.Select(valueTask => valueTask.AsTask()));
_cts.Dispose();
Expand Down
11 changes: 6 additions & 5 deletions src/Client/PodExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ internal static IEnumerable<Pod> WhereServiceIsHosted(
{
return pods.Where(
pod =>
service.Selectors.Any(
pair =>
service.Selectors.Any() &&
service.Selectors.All(
selector =>
pod.Labels.Any(
valuePair =>
valuePair.Key == pair.Key &&
valuePair.Value == pair.Value)));
label =>
label.Key == selector.Key &&
label.Value == selector.Value)));
}
}
}
33 changes: 23 additions & 10 deletions src/Client/Services/PortForwardService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public PortForwardService(
}

internal Port.Shared.PortForward Model { get; }
internal event Action OnStateChanged = () => { };
internal event Func<Task> OnStateChangedAsync = () => Task.CompletedTask;
internal event Func<Exception, Task> OnErrorAsync = _ => Task.CompletedTask;

internal Task ForwardAsync()
{
Expand All @@ -34,8 +35,8 @@ internal Task ForwardAsync()
Context = _context,
Namespace = Model.Namespace,
Pod = Model.Pod,
PodPort = (uint) Model.PodPort,
LocalPort = (uint) (Model.LocalPort ??
PodPort = (uint)Model.PodPort,
LocalPort = (uint)(Model.LocalPort ??
throw new ArgumentNullException(
nameof(Model.LocalPort))),
ProtocolType = Model.ProtocolType switch
Expand All @@ -59,8 +60,8 @@ internal async Task StopAsync()
Context = _context,
Namespace = Model.Namespace,
Pod = Model.Pod,
PodPort = (uint) Model.PodPort,
LocalPort = (uint) (Model.LocalPort ??
PodPort = (uint)Model.PodPort,
LocalPort = (uint)(Model.LocalPort ??
throw new ArgumentNullException(
nameof(Model.LocalPort))),
ProtocolType = Model.ProtocolType switch
Expand All @@ -73,8 +74,13 @@ internal async Task StopAsync()
};

using var stream = _portForwarder.StopForwardingAsync(stopRequest);
await stream.ResponseAsync.ConfigureAwait(false);
Model.Forwarding = false;
try
{
await stream.ResponseAsync.ConfigureAwait(false);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
{
}
}

private async Task StartListenOnForwardResponseAsync(
Expand All @@ -83,8 +89,8 @@ private async Task StartListenOnForwardResponseAsync(
try
{
await foreach (var message in stream.ResponseStream
.ReadAllAsync(
CancellationToken))
.ReadAllAsync(CancellationToken)
.ConfigureAwait(false))
{
switch (message.EventCase)
{
Expand All @@ -110,7 +116,8 @@ private async Task StartListenOnForwardResponseAsync(
throw new ArgumentOutOfRangeException();
}

OnStateChanged();
await OnStateChangedAsync()
.ConfigureAwait(false);
}
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
Expand All @@ -119,6 +126,12 @@ private async Task StartListenOnForwardResponseAsync(
catch (Exception) when (CancellationToken.IsCancellationRequested)
{
}
catch (Exception ex)
{
Model.Forwarding = false;
await OnErrorAsync(ex)
.ConfigureAwait(false);
}
finally
{
stream.Dispose();
Expand Down
12 changes: 5 additions & 7 deletions src/Client/Shared/Contexts.razor
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ else
_location = positionOfLastSegment >= 0 ?
args.Location.Substring(positionOfLastSegment + 1)
: "";
StateHasChanged();
InvokeAsync(StateHasChanged);
};

return Task.CompletedTask;
Expand All @@ -68,10 +68,9 @@ else
private readonly SemaphoreSlimGate _gate = SemaphoreSlimGate.OneAtATime;
protected override async Task OnParametersSetAsync()
{
using (await _gate.WaitAsync(CancellationToken)
.ConfigureAwait(false))
using (await _gate.WaitAsync(CancellationToken))
{
if (Error.HasError())
if (Error.HasError)
{
return;
}
Expand All @@ -83,12 +82,11 @@ else

try
{
_contexts = await Http.GetFromNewtonsoftJsonAsync<Context[]>("Context", cancellationToken: CancellationToken)
.ConfigureAwait(false);
_contexts = await Http.GetFromNewtonsoftJsonAsync<Context[]>("Context", cancellationToken: CancellationToken);
}
catch (Exception e)
{
Error.ProcessError(e);
await Error.ProcessErrorAsync(e);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/Client/Shared/Error.razor
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@

private string? _error;

public bool HasError() => _error != null;
public bool HasError => _error != null;

public void ProcessError(Exception ex)
public Task ProcessErrorAsync(Exception ex)
{
_error = ex.Message;

StateHasChanged();
return InvokeAsync(StateHasChanged);
}

public void OnReload()
Expand Down

0 comments on commit ab39ee4

Please sign in to comment.