Replace ClientAppState with stores.

This commit is contained in:
Jared Goodwin 2023-08-07 05:54:07 -07:00
parent 13e3a6a038
commit f076089f60
14 changed files with 240 additions and 201 deletions

View File

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.Web;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Server.Services.Stores;
using Remotely.Shared.Enums;
using Remotely.Shared.ViewModels;
using System;
@ -22,10 +23,7 @@ public partial class ChatCard : AuthComponentBase, IDisposable
public required ChatSession Session { get; set; }
[Inject]
private IClientAppState AppState { get; init; } = null!;
[Inject]
private IChatSessionCache ChatSessionCache { get; init; } = null!;
private IChatSessionStore ChatSessionStore { get; init; } = null!;
[Inject]
private ICircuitConnection CircuitConnection { get; init; } = null!;
@ -63,7 +61,7 @@ public partial class ChatCard : AuthComponentBase, IDisposable
return;
}
if (!ChatSessionCache.TryGetSession(message.DeviceId, out var session))
if (!ChatSessionStore.TryGetSession(message.DeviceId, out var session))
{
return;
}
@ -97,7 +95,7 @@ public partial class ChatCard : AuthComponentBase, IDisposable
private async Task CloseChatCard()
{
_ = ChatSessionCache.TryRemove($"{Session.DeviceId}", out _);
_ = ChatSessionStore.TryRemove($"{Session.DeviceId}", out _);
var message = new ChatSessionsChangedMessage();
await Messenger.Send(message, CircuitConnection.ConnectionId);
}

View File

@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Server.Services.Stores;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
using Remotely.Shared.ViewModels;
@ -19,10 +19,10 @@ public partial class ChatFrame : AuthComponentBase, IAsyncDisposable
private ICollection<ChatSession> _chatSessions = Array.Empty<ChatSession>();
[Inject]
private IClientAppState AppState { get; init; } = null!;
private ISelectedCardsStore CardStore { get; init; } = null!;
[Inject]
private IChatSessionCache ChatCache { get; init; } = null!;
private IChatSessionStore ChatCache { get; init; } = null!;
[Inject]
private ICircuitConnection CircuitConnection { get; init; } = null!;

View File

@ -1,16 +1,16 @@
@attribute [Authorize]
@inherits AuthComponentBase
<div @ref="_card" class="card border-secondary my-3 mr-3 device-card @_theme @GetCardStateClass(Device)"
<div @ref="_card" class="card border-secondary my-3 mr-3 device-card @_theme @GetCardStateClass()"
@onclick="ExpandCard"
@onclick:stopPropagation
@oncontextmenu="ContextMenuOpening"
@oncontextmenu:preventDefault="GetCardState() == DeviceCardState.Normal"
@oncontextmenu:stopPropagation="GetCardState() == DeviceCardState.Normal">
@oncontextmenu:preventDefault="_state == DeviceCardState.Normal"
@oncontextmenu:stopPropagation="_state == DeviceCardState.Normal">
<div class="card-header" @onclick="HandleHeaderClick"
@onclick:stopPropagation="GetCardState() == DeviceCardState.Expanded"
@onclick:preventDefault="GetCardState() == DeviceCardState.Expanded">
@onclick:stopPropagation="_state == DeviceCardState.Expanded"
@onclick:preventDefault="_state == DeviceCardState.Expanded">
<div>
@if (Device.IsOnline)
{

View File

@ -6,6 +6,7 @@ using Remotely.Server.Enums;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Server.Services.Stores;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
using Remotely.Shared.Utilities;
@ -24,6 +25,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
private ElementReference _card;
private Version _currentVersion = new();
private Theme _theme;
private DeviceCardState _state;
private DeviceGroup[] _deviceGroups = Array.Empty<DeviceGroup>();
[Parameter]
@ -34,7 +36,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
[Inject]
private IClientAppState AppState { get; init; } = null!;
private ISelectedCardsStore SelectedCards { get; init; } = null!;
[Inject]
private IThemeProvider ThemeProvider { get; init; } = null!;
@ -46,15 +48,15 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
private IDataService DataService { get; init; } = null!;
[Inject]
private IChatSessionCache ChatCache { get; init; } = null!;
private IChatSessionStore ChatCache { get; init; } = null!;
private bool IsExpanded => GetCardState() == DeviceCardState.Expanded;
private bool IsExpanded => _state == DeviceCardState.Expanded;
private bool IsOutdated =>
Version.TryParse(Device.AgentVersion, out var result) &&
result < _currentVersion;
private bool IsSelected => AppState.DevicesFrameSelectedDevices.Contains(Device.ID);
private bool IsSelected => SelectedCards.SelectedDevices.Contains(Device.ID);
[Inject]
private IJsInterop JsInterop { get; init; } = null!;
@ -76,7 +78,6 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
public void Dispose()
{
AppState.PropertyChanged -= AppState_PropertyChanged;
Messenger.Unregister<DeviceStateChangedMessage, string>(this, CircuitConnection.ConnectionId);
GC.SuppressFinalize(this);
}
@ -88,13 +89,41 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
_theme = await ThemeProvider.GetEffectiveTheme();
_currentVersion = UpgradeService.GetCurrentVersion();
_deviceGroups = DataService.GetDeviceGroups(UserName);
AppState.PropertyChanged += AppState_PropertyChanged;
await Messenger.Register<DeviceCardStateChangedMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleDeviceCardStateChanged);
await Messenger.Register<DeviceStateChangedMessage, string>(
this,
CircuitConnection.ConnectionId,
HandleDeviceStateChanged);
}
private async Task HandleDeviceCardStateChanged(DeviceCardStateChangedMessage message)
{
if (message.DeviceId == Device.ID)
{
if (message.State == _state)
{
return;
}
_state = message.State;
await InvokeAsync(StateHasChanged);
}
else
{
if (_state == DeviceCardState.Normal)
{
return;
}
_state = DeviceCardState.Normal;
await InvokeAsync(StateHasChanged);
return;
}
}
private async Task HandleDeviceStateChanged(DeviceStateChangedMessage message)
{
if (message.Device.ID != Device.ID)
@ -133,19 +162,9 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
await InvokeAsync(StateHasChanged);
}
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(AppState.DevicesFrameFocusedCardState) ||
e.PropertyName == nameof(AppState.DevicesFrameFocusedDevice) ||
e.PropertyName == nameof(AppState.DevicesFrameSelectedDevices))
{
InvokeAsync(StateHasChanged);
}
}
private void ContextMenuOpening(MouseEventArgs args)
{
if (GetCardState() == DeviceCardState.Normal)
if (_state == DeviceCardState.Normal)
{
JsInterop.OpenWindow($"/device-details/{Device.ID}", "_blank");
}
@ -153,40 +172,24 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
private async Task ExpandCard(MouseEventArgs args)
{
if (AppState.DevicesFrameFocusedDevice == Device.ID)
if (_state == DeviceCardState.Expanded)
{
if (AppState.DevicesFrameFocusedCardState == DeviceCardState.Normal)
{
AppState.DevicesFrameFocusedCardState = DeviceCardState.Expanded;
}
return;
}
AppState.DevicesFrameFocusedDevice = Device.ID;
AppState.DevicesFrameFocusedCardState = DeviceCardState.Expanded;
await Messenger.Send(
new DeviceCardStateChangedMessage(Device.ID, DeviceCardState.Expanded),
CircuitConnection.ConnectionId);
JsInterop.ScrollToElement(_card);
await CircuitConnection.TriggerHeartbeat(Device.ID);
}
private DeviceCardState GetCardState()
private string GetCardStateClass()
{
if (AppState.DevicesFrameFocusedDevice == Device.ID)
{
return AppState.DevicesFrameFocusedCardState;
}
return DeviceCardState.Normal;
}
private string GetCardStateClass(Device device)
{
if (AppState.DevicesFrameFocusedDevice == device.ID)
{
return AppState.DevicesFrameFocusedCardState.ToString().ToLower();
}
return string.Empty;
return $"{_state}".ToLower();
}
private string GetProgressMessage(string key)
@ -203,7 +206,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
{
if (IsExpanded)
{
SetCardStateNormal();
_state = DeviceCardState.Normal;
}
}
private async Task HandleValidSubmit()
@ -265,12 +268,6 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
JsInterop.OpenWindow($"/device-details/{Device.ID}", "_blank");
}
private void SetCardStateNormal()
{
AppState.DevicesFrameFocusedDevice = null;
AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal;
}
private void ShowAllDisks()
{
var disksString = JsonSerializer.Serialize(Device.Drives, JsonSerializerHelper.IndentedOptions);
@ -338,13 +335,12 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
if (isSelected)
{
AppState.DevicesFrameSelectedDevices.Add(Device.ID);
SelectedCards.Add(Device.ID);
}
else
{
AppState.DevicesFrameSelectedDevices.Remove(Device.ID);
SelectedCards.Remove(Device.ID);
}
AppState.InvokePropertyChanged(nameof(AppState.DevicesFrameSelectedDevices));
}
private async Task UninstallAgent()
@ -353,8 +349,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
if (result)
{
await CircuitConnection.UninstallAgents(new[] { Device.ID });
AppState.DevicesFrameFocusedDevice = null;
AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal;
_state = DeviceCardState.Normal;
await ParentFrame.Refresh();
}
}

View File

@ -6,6 +6,7 @@ using Remotely.Server.Enums;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Server.Services.Stores;
using Remotely.Shared.Attributes;
using Remotely.Shared.Entities;
using Remotely.Shared.Utilities;
@ -41,7 +42,10 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
private ListSortDirection _sortDirection;
[Inject]
private IClientAppState AppState { get; init; } = null!;
private ISelectedCardsStore CardStore { get; init; } = null!;
[Inject]
private ITerminalStore TerminalStore { get; init; } = null!;
[Inject]
private ICircuitConnection CircuitConnection { get; init; } = null!;
@ -63,13 +67,12 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
Messenger.Unregister<ScriptResultMessage, string>(this, CircuitConnection.ConnectionId);
Messenger.Unregister<DeviceStateChangedMessage, string>(this, CircuitConnection.ConnectionId);
AppState.PropertyChanged -= AppState_PropertyChanged;
GC.SuppressFinalize(this);
}
private async Task HandleDisplayNotificationMessage(DisplayNotificationMessage message)
{
AppState.AddTerminalLine(message.ConsoleText);
TerminalStore.AddTerminalLine(message.ConsoleText);
ToastService.ShowToast(message.ToastText, classString: message.ClassName);
await InvokeAsync(StateHasChanged);
}
@ -101,8 +104,6 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
CircuitConnection.ConnectionId,
HandleScriptResultMessage);
AppState.PropertyChanged += AppState_PropertyChanged;
_deviceGroups.Clear();
_deviceGroups.AddRange(DataService.GetDeviceGroups(UserName));
@ -162,36 +163,27 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
return;
}
AppState.AddTerminalLine($"{deviceResult.Value.DeviceName} @ {result.TimeStamp}", "font-weight-bold");
TerminalStore.AddTerminalLine($"{deviceResult.Value.DeviceName} @ {result.TimeStamp}", "font-weight-bold");
var stdOut = result.StandardOutput ?? Array.Empty<string>();
var stdErr = result.ErrorOutput ?? Array.Empty<string>();
foreach (var line in stdOut)
{
AppState.AddTerminalLine(line, "text-info");
TerminalStore.AddTerminalLine(line, "text-info");
}
foreach (var line in stdErr)
{
AppState.AddTerminalLine(line, "text-danger");
TerminalStore.AddTerminalLine(line, "text-danger");
}
AppState.InvokePropertyChanged(nameof(AppState.TerminalLines));
TerminalStore.InvokeLinesChanged();
}
private void AppState_PropertyChanged(object? sender, PropertyChangedEventArgs e)
private async Task ClearSelectedCard()
{
if (e.PropertyName == nameof(AppState.DevicesFrameFocusedCardState) ||
e.PropertyName == nameof(AppState.DevicesFrameFocusedDevice) ||
e.PropertyName == nameof(AppState.DevicesFrameSelectedDevices))
{
InvokeAsync(StateHasChanged);
}
}
private void ClearSelectedCard()
{
AppState.DevicesFrameFocusedDevice = string.Empty;
AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal;
await Messenger.Send(
new DeviceCardStateChangedMessage(string.Empty, DeviceCardState.Normal),
CircuitConnection.ConnectionId);
}
private async Task FilterDevices()
@ -204,7 +196,7 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
foreach (var device in _allDevices)
{
if (AppState.DevicesFrameSelectedDevices.Contains(device.ID))
if (CardStore.SelectedDevices.Contains(device.ID))
{
appendDevices.Add(device);
}
@ -328,22 +320,19 @@ public partial class DevicesFrame : AuthComponentBase, IDisposable
private void SelectAllCards()
{
if (AppState.DevicesFrameSelectedDevices.Any())
if (CardStore.SelectedDevices.Any())
{
AppState.DevicesFrameSelectedDevices.Clear();
CardStore.Clear();
}
else
{
foreach (var device in _filteredDevices)
{
if (!AppState.DevicesFrameSelectedDevices.Contains(device.ID))
{
AppState.DevicesFrameSelectedDevices.Add(device.ID);
}
_ = CardStore.Add(device.ID);
};
}
AppState.InvokePropertyChanged(nameof(AppState.DevicesFrameSelectedDevices));
CardStore.InvokeSelectionsChanged();
}
private void ToggleSortDirection()

View File

@ -20,7 +20,7 @@
<div class="terminal-body border border-secondary bg-dark">
<div @ref="_terminalWindow" class="terminal-window px-2 py-1">
@foreach (var line in AppState.TerminalLines)
@foreach (var line in TerminalStore.TerminalLines)
{
<div @key="line.Id" class="ml-1 terminal-line @line.ClassName" title="@line.Title">
@line.Text

View File

@ -8,6 +8,7 @@ using Remotely.Server.Components.ModalContents;
using Remotely.Server.Hubs;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Server.Services.Stores;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
using Remotely.Shared.Models;
@ -32,7 +33,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
private ElementReference _terminalWindow;
[Inject]
private IClientAppState AppState { get; init; } = null!;
private ISelectedCardsStore CardStore { get; init; } = null!;
[Inject]
private ICircuitConnection CircuitConnection { get; init; } = null!;
@ -40,9 +41,6 @@ public partial class Terminal : AuthComponentBase, IDisposable
[Inject]
private IDataService DataService { get; init; } = null!;
[Inject]
private IMessenger Messenger { get; init; } = null!;
private string InputText
{
get => _inputText ?? string.Empty;
@ -62,6 +60,8 @@ public partial class Terminal : AuthComponentBase, IDisposable
[Inject]
private ILogger<Terminal> Logger { get; init; } = null!;
[Inject]
private IMessenger Messenger { get; init; } = null!;
[Inject]
private IModalService ModalService { get; init; } = null!;
@ -79,22 +79,23 @@ public partial class Terminal : AuthComponentBase, IDisposable
RunOnNextConnect = false,
Initiator = User.UserName,
InputType = ScriptInputType.OneTimeScript,
Devices = DataService.GetDevices(AppState.DevicesFrameSelectedDevices)
Devices = DataService.GetDevices(CardStore.SelectedDevices)
};
await DataService.AddScriptRun(scriptRun);
await CircuitConnection.RunScript(AppState.DevicesFrameSelectedDevices, script.Id, scriptRun.Id, ScriptInputType.OneTimeScript, false);
await CircuitConnection.RunScript(CardStore.SelectedDevices, script.Id, scriptRun.Id, ScriptInputType.OneTimeScript, false);
ToastService.ShowToast($"Running script on {scriptRun.Devices.Count} devices.");
});
[Inject]
private ITerminalStore TerminalStore { get; init; } = null!;
[Inject]
private IToastService ToastService { get; init; } = null!;
public void Dispose()
{
AppState.PropertyChanged -= AppState_PropertyChanged;
Messenger.Unregister<PowerShellCompletionsMessage, string>(this, CircuitConnection.ConnectionId);
GC.SuppressFinalize(this);
}
@ -115,26 +116,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
this,
CircuitConnection.ConnectionId,
HandlePowerShellCompletionsMessage);
AppState.PropertyChanged += AppState_PropertyChanged;
}
private async Task HandlePowerShellCompletionsMessage(PowerShellCompletionsMessage message)
{
var completion = message.Completion;
var intent = message.Intent;
switch (intent)
{
case CompletionIntent.ShowAll:
await DisplayCompletions(completion.CompletionMatches);
break;
case CompletionIntent.NextResult:
ApplyCompletion(completion);
break;
default:
break;
}
AppState.InvokePropertyChanged(nameof(AppState.TerminalLines));
TerminalStore.TerminalLinesChanged += TerminalStore_TerminalLinesChanged;
}
private void ApplyCompletion(PwshCommandCompletion completion)
@ -161,21 +143,12 @@ public partial class Terminal : AuthComponentBase, IDisposable
}
}
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(AppState.TerminalLines))
{
InvokeAsync(StateHasChanged);
JsInterop.ScrollToEnd(_terminalWindow);
}
}
private async Task DisplayCompletions(List<PwshCompletionResult> completionMatches)
{
var deviceId = AppState.DevicesFrameSelectedDevices.FirstOrDefault();
if (string.IsNullOrWhiteSpace(deviceId))
{
return;
var deviceId = CardStore.SelectedDevices.FirstOrDefault();
if (string.IsNullOrWhiteSpace(deviceId))
{
return;
}
var deviceResult = await DataService.GetDevice(deviceId);
@ -186,15 +159,16 @@ public partial class Terminal : AuthComponentBase, IDisposable
return;
}
AppState.AddTerminalLine(
$"Completions for {deviceResult.Value.DeviceName}",
TerminalStore.AddTerminalLine(
$"Completions for {deviceResult.Value.DeviceName}",
className: "font-weight-bold");
foreach (var match in completionMatches)
{
AppState.AddTerminalLine(match.CompletionText, className: "", title: match.ToolTip);
TerminalStore.AddTerminalLine(match.CompletionText, className: "", title: match.ToolTip);
}
}
private void EvaluateInputKeypress(KeyboardEventArgs ev)
{
if (ev.Key.Equals("Enter", StringComparison.OrdinalIgnoreCase))
@ -204,14 +178,14 @@ public partial class Terminal : AuthComponentBase, IDisposable
return;
}
var devices = AppState.DevicesFrameSelectedDevices.ToArray();
var devices = CardStore.SelectedDevices.ToArray();
if (!devices.Any())
{
ToastService.ShowToast("You must select at least one device.", classString: "bg-warning");
return;
}
CircuitConnection.ExecuteCommandOnAgent(_shell, InputText, devices);
AppState.AddTerminalHistory(InputText);
TerminalStore.AddTerminalHistory(InputText);
InputText = string.Empty;
}
}
@ -227,11 +201,11 @@ public partial class Terminal : AuthComponentBase, IDisposable
if (ev.Key.Equals("ArrowUp", StringComparison.OrdinalIgnoreCase))
{
InputText = AppState.GetTerminalHistory(false);
InputText = TerminalStore.GetTerminalHistory(false);
}
else if (ev.Key.Equals("ArrowDown", StringComparison.OrdinalIgnoreCase))
{
InputText = AppState.GetTerminalHistory(true);
InputText = TerminalStore.GetTerminalHistory(true);
}
else if (ev.Key.Equals("Tab", StringComparison.OrdinalIgnoreCase))
{
@ -241,7 +215,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
return;
}
if (!AppState.DevicesFrameSelectedDevices.Any())
if (!CardStore.SelectedDevices.Any())
{
ToastService.ShowToast("No devices are selected.", classString: "bg-warning");
return;
@ -256,7 +230,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
}
else if (ev.CtrlKey && ev.Key.Equals(" ", StringComparison.OrdinalIgnoreCase))
{
if (!AppState.DevicesFrameSelectedDevices.Any())
if (!CardStore.SelectedDevices.Any())
{
return;
}
@ -265,8 +239,8 @@ public partial class Terminal : AuthComponentBase, IDisposable
}
else if (ev.CtrlKey && ev.Key.Equals("q", StringComparison.OrdinalIgnoreCase))
{
AppState.TerminalLines.Clear();
AppState.InvokePropertyChanged(nameof(AppState.TerminalLines));
TerminalStore.TerminalLines.Clear();
TerminalStore.InvokeLinesChanged();
}
}
@ -281,6 +255,25 @@ public partial class Terminal : AuthComponentBase, IDisposable
await CircuitConnection.GetPowerShellCompletions(_lastCompletionInput, _lastCursorIndex, CompletionIntent.NextResult, forward);
}
private async Task HandlePowerShellCompletionsMessage(PowerShellCompletionsMessage message)
{
var completion = message.Completion;
var intent = message.Intent;
switch (intent)
{
case CompletionIntent.ShowAll:
await DisplayCompletions(completion.CompletionMatches);
break;
case CompletionIntent.NextResult:
ApplyCompletion(completion);
break;
default:
break;
}
TerminalStore.InvokeLinesChanged();
}
private async Task ShowAllCompletions()
{
if (string.IsNullOrWhiteSpace(_lastCompletionInput))
@ -303,7 +296,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
return;
}
if (!AppState.DevicesFrameSelectedDevices.Any())
if (!CardStore.SelectedDevices.Any())
{
ToastService.ShowToast("You must select at least one device.", classString: "bg-warning");
return;
@ -342,6 +335,12 @@ public partial class Terminal : AuthComponentBase, IDisposable
"starting on the remote device."
});
}
private async void TerminalStore_TerminalLinesChanged(object? sender, EventArgs e)
{
await InvokeAsync(StateHasChanged);
JsInterop.ScrollToEnd(_terminalWindow);
}
private void ToggleTerminalOpen()
{
if (string.IsNullOrWhiteSpace(_terminalOpenClass))

View File

@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
using Remotely.Server.Models;
using Remotely.Server.Models.Messages;
using Remotely.Server.Services;
using Remotely.Server.Services.Stores;
using Remotely.Shared;
using Remotely.Shared.Entities;
using Remotely.Shared.Enums;
@ -71,7 +72,7 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
{
private readonly IHubContext<AgentHub, IAgentHubClient> _agentHubContext;
private readonly IApplicationConfig _appConfig;
private readonly IClientAppState _appState;
private readonly ISelectedCardsStore _cardStore;
private readonly IAuthService _authService;
private readonly ICircuitManager _circuitManager;
private readonly IDataService _dataService;
@ -86,7 +87,7 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
public CircuitConnection(
IAuthService authService,
IDataService dataService,
IClientAppState appState,
ISelectedCardsStore cardStore,
IHubContext<AgentHub, IAgentHubClient> agentHubContext,
IApplicationConfig appConfig,
ICircuitManager circuitManager,
@ -99,7 +100,7 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
{
_dataService = dataService;
_agentHubContext = agentHubContext;
_appState = appState;
_cardStore = cardStore;
_appConfig = appConfig;
_authService = authService;
_circuitManager = circuitManager;
@ -160,7 +161,7 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
public Task GetPowerShellCompletions(string inputText, int currentIndex, CompletionIntent intent, bool? forward)
{
var device = _appState.DevicesFrameSelectedDevices.FirstOrDefault();
var device = _cardStore.SelectedDevices.FirstOrDefault();
if (device is null)
{
return Task.CompletedTask;

View File

@ -0,0 +1,5 @@
using Remotely.Server.Enums;
namespace Remotely.Server.Models.Messages;
public record DeviceCardStateChangedMessage(string DeviceId, DeviceCardState State);

View File

@ -31,6 +31,7 @@ using Microsoft.AspNetCore.RateLimiting;
using RatePolicyNames = Remotely.Server.RateLimiting.PolicyNames;
using Remotely.Shared.Entities;
using Immense.SimpleMessenger;
using Remotely.Server.Services.Stores;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
@ -204,14 +205,15 @@ services.AddScoped<ILoaderService, LoaderService>();
services.AddScoped(x => (CircuitHandler)x.GetRequiredService<ICircuitConnection>());
services.AddSingleton<ICircuitManager, CircuitManager>();
services.AddScoped<IAuthService, AuthService>();
services.AddScoped<IClientAppState, ClientAppState>();
services.AddScoped<ISelectedCardsStore, SelectedCardsStore>();
services.AddScoped<IExpiringTokenService, ExpiringTokenService>();
services.AddScoped<IScriptScheduleDispatcher, ScriptScheduleDispatcher>();
services.AddSingleton<IOtpProvider, OtpProvider>();
services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>();
services.AddSingleton<ILogsManager, LogsManager>();
services.AddScoped<IThemeProvider, ThemeProvider>();
services.AddScoped<IChatSessionCache, ChatSessionCache>();
services.AddScoped<IChatSessionStore, ChatSessionStore>();
services.AddScoped<ITerminalStore, TerminalStore>();
services.AddSingleton(WeakReferenceMessenger.Default);
services.AddRemoteControlServer(config =>

View File

@ -5,13 +5,13 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Remotely.Server.Services;
namespace Remotely.Server.Services.Stores;
public interface IChatSessionCache
public interface IChatSessionStore
{
void AddOrUpdate(
string deviceId,
ChatSession chatSession,
string deviceId,
ChatSession chatSession,
Func<string, ChatSession, ChatSession> updateFactory);
bool ContainsKey(string deviceId);
@ -21,13 +21,13 @@ public interface IChatSessionCache
bool TryRemove(string deviceId, [NotNullWhen(true)] out ChatSession? session);
}
public class ChatSessionCache : IChatSessionCache
public class ChatSessionStore : IChatSessionStore
{
private readonly ConcurrentDictionary<string, ChatSession> _sessions = new();
public void AddOrUpdate(
string deviceId,
ChatSession chatSession,
string deviceId,
ChatSession chatSession,
Func<string, ChatSession, ChatSession> updateFactory)
{
_sessions.AddOrUpdate(deviceId, chatSession, updateFactory);

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Remotely.Server.Services.Stores;
public interface ISelectedCardsStore
{
event EventHandler? SelectedDevicesChanged;
IEnumerable<string> SelectedDevices { get; }
bool Add(string deviceId);
bool Contains(string deviceId);
bool Remove(string deviceId);
void Clear();
void InvokeSelectionsChanged();
}
public class SelectedCardsStore : ISelectedCardsStore
{
private readonly object _lock = new();
private readonly HashSet<string> _selectedDevices = new();
public IEnumerable<string> SelectedDevices
{
get
{
lock (_lock)
{
return _selectedDevices.ToArray();
}
}
}
public event EventHandler? SelectedDevicesChanged;
public bool Add(string deviceId)
{
lock (_lock)
{
return _selectedDevices.Add(deviceId);
}
}
public void Clear()
{
_selectedDevices.Clear();
}
public bool Contains(string deviceId)
{
lock ( _lock)
{
return _selectedDevices.Contains(deviceId);
}
}
public void InvokeSelectionsChanged()
{
SelectedDevicesChanged?.Invoke(this, EventArgs.Empty);
}
public bool Remove(string deviceId)
{
lock (_lock)
{
return _selectedDevices.Remove(deviceId);
}
}
}

View File

@ -1,19 +1,13 @@
using Remotely.Server.Enums;
using Remotely.Shared.Enums;
using Remotely.Shared.Primitives;
using Remotely.Shared.ViewModels;
using Remotely.Shared.ViewModels;
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
namespace Remotely.Server.Services;
namespace Remotely.Server.Services.Stores;
public interface IClientAppState : INotifyPropertyChanged, IInvokePropertyChanged
public interface ITerminalStore
{
DeviceCardState DevicesFrameFocusedCardState { get; set; }
string? DevicesFrameFocusedDevice { get; set; }
ConcurrentList<string> DevicesFrameSelectedDevices { get; }
event EventHandler? TerminalLinesChanged;
ConcurrentQueue<TerminalLineItem> TerminalLines { get; }
void AddTerminalHistory(string content);
@ -21,29 +15,18 @@ public interface IClientAppState : INotifyPropertyChanged, IInvokePropertyChange
void AddTerminalLine(string content, string className = "", string title = "");
string GetTerminalHistory(bool forward);
}
public class ClientAppState : ViewModelBase, IClientAppState
void InvokeLinesChanged();
}
public class TerminalStore : ITerminalStore
{
private readonly ConcurrentQueue<string> _terminalHistory = new();
private int _terminalHistoryIndex = 0;
public DeviceCardState DevicesFrameFocusedCardState
{
get => Get<DeviceCardState>();
set => Set(value);
}
public string? DevicesFrameFocusedDevice
{
get => Get<string>();
set => Set(value);
}
public ConcurrentList<string> DevicesFrameSelectedDevices { get; } = new();
public event EventHandler? TerminalLinesChanged;
public ConcurrentQueue<TerminalLineItem> TerminalLines { get; } = new();
public void AddTerminalHistory(string content)
{
while (_terminalHistory.Count > 500)
@ -92,4 +75,9 @@ public class ClientAppState : ViewModelBase, IClientAppState
}
return _terminalHistory.ElementAt(_terminalHistoryIndex);
}
}
public void InvokeLinesChanged()
{
TerminalLinesChanged?.Invoke(this, EventArgs.Empty);
}
}

View File

@ -1,25 +1,17 @@
#nullable enable
using Castle.Core.Logging;
using Immense.RemoteControl.Server.Services;
using Immense.SimpleMessenger;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Remotely.Server.Hubs;
using Remotely.Server.Services;
using Remotely.Server.Services.Stores;
using Remotely.Server.Tests.Mocks;
using Remotely.Shared.Dtos;
using Remotely.Shared.Extensions;
using Remotely.Shared.Interfaces;
using Remotely.Shared.Models;
using Remotely.Tests;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Server.Tests;
@ -31,7 +23,7 @@ public class CircuitConnectionTests
private TestData _testData;
private IDataService _dataService;
private Mock<IAuthService> _authService;
private Mock<IClientAppState> _clientAppState;
private Mock<ISelectedCardsStore> _clientAppState;
private HubContextFixture<AgentHub, IAgentHubClient> _agentHubContextFixture;
private Mock<IApplicationConfig> _appConfig;
private Mock<ICircuitManager> _circuitManager;
@ -52,7 +44,7 @@ public class CircuitConnectionTests
_dataService = IoCActivator.ServiceProvider.GetRequiredService<IDataService>();
_authService = new Mock<IAuthService>();
_clientAppState = new Mock<IClientAppState>();
_clientAppState = new Mock<ISelectedCardsStore>();
_agentHubContextFixture = new HubContextFixture<AgentHub, IAgentHubClient>();
_appConfig = new Mock<IApplicationConfig>();
_circuitManager = new Mock<ICircuitManager>();