diff --git a/Server/Components/Devices/ChatCard.razor.cs b/Server/Components/Devices/ChatCard.razor.cs index aa9eb922..3b338a6f 100644 --- a/Server/Components/Devices/ChatCard.razor.cs +++ b/Server/Components/Devices/ChatCard.razor.cs @@ -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); } diff --git a/Server/Components/Devices/ChatFrame.razor.cs b/Server/Components/Devices/ChatFrame.razor.cs index bd4aa71d..4f743e45 100644 --- a/Server/Components/Devices/ChatFrame.razor.cs +++ b/Server/Components/Devices/ChatFrame.razor.cs @@ -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 _chatSessions = Array.Empty(); [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!; diff --git a/Server/Components/Devices/DeviceCard.razor b/Server/Components/Devices/DeviceCard.razor index fffab6b4..804bc161 100644 --- a/Server/Components/Devices/DeviceCard.razor +++ b/Server/Components/Devices/DeviceCard.razor @@ -1,16 +1,16 @@ @attribute [Authorize] @inherits AuthComponentBase -
+ @oncontextmenu:preventDefault="_state == DeviceCardState.Normal" + @oncontextmenu:stopPropagation="_state == DeviceCardState.Normal">
+ @onclick:stopPropagation="_state == DeviceCardState.Expanded" + @onclick:preventDefault="_state == DeviceCardState.Expanded">
@if (Device.IsOnline) { diff --git a/Server/Components/Devices/DeviceCard.razor.cs b/Server/Components/Devices/DeviceCard.razor.cs index e3202289..c7dac200 100644 --- a/Server/Components/Devices/DeviceCard.razor.cs +++ b/Server/Components/Devices/DeviceCard.razor.cs @@ -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(); [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(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( + this, + CircuitConnection.ConnectionId, + HandleDeviceCardStateChanged); + await Messenger.Register( 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(); } } diff --git a/Server/Components/Devices/DevicesFrame.razor.cs b/Server/Components/Devices/DevicesFrame.razor.cs index 63384906..6ba13235 100644 --- a/Server/Components/Devices/DevicesFrame.razor.cs +++ b/Server/Components/Devices/DevicesFrame.razor.cs @@ -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(this, CircuitConnection.ConnectionId); Messenger.Unregister(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(); var stdErr = result.ErrorOutput ?? Array.Empty(); 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() diff --git a/Server/Components/Devices/Terminal.razor b/Server/Components/Devices/Terminal.razor index 363ca0e4..d1bc403b 100644 --- a/Server/Components/Devices/Terminal.razor +++ b/Server/Components/Devices/Terminal.razor @@ -20,7 +20,7 @@
- @foreach (var line in AppState.TerminalLines) + @foreach (var line in TerminalStore.TerminalLines) {
@line.Text diff --git a/Server/Components/Devices/Terminal.razor.cs b/Server/Components/Devices/Terminal.razor.cs index 217c7031..6e4f060f 100644 --- a/Server/Components/Devices/Terminal.razor.cs +++ b/Server/Components/Devices/Terminal.razor.cs @@ -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 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(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 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)) diff --git a/Server/Hubs/CircuitConnection.cs b/Server/Hubs/CircuitConnection.cs index 48e1683c..c3853188 100644 --- a/Server/Hubs/CircuitConnection.cs +++ b/Server/Hubs/CircuitConnection.cs @@ -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 _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 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; diff --git a/Server/Models/Messages/DeviceCardStateChangedMessage.cs b/Server/Models/Messages/DeviceCardStateChangedMessage.cs new file mode 100644 index 00000000..49833582 --- /dev/null +++ b/Server/Models/Messages/DeviceCardStateChangedMessage.cs @@ -0,0 +1,5 @@ +using Remotely.Server.Enums; + +namespace Remotely.Server.Models.Messages; + +public record DeviceCardStateChangedMessage(string DeviceId, DeviceCardState State); \ No newline at end of file diff --git a/Server/Program.cs b/Server/Program.cs index ac6a68bb..749a007b 100644 --- a/Server/Program.cs +++ b/Server/Program.cs @@ -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(); services.AddScoped(x => (CircuitHandler)x.GetRequiredService()); services.AddSingleton(); services.AddScoped(); -services.AddScoped(); +services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddScoped(); -services.AddScoped(); +services.AddScoped(); +services.AddScoped(); services.AddSingleton(WeakReferenceMessenger.Default); services.AddRemoteControlServer(config => diff --git a/Server/Services/ChatSessionCache.cs b/Server/Services/Stores/ChatSessionStore.cs similarity index 86% rename from Server/Services/ChatSessionCache.cs rename to Server/Services/Stores/ChatSessionStore.cs index 2eda35a9..5ccd23f9 100644 --- a/Server/Services/ChatSessionCache.cs +++ b/Server/Services/Stores/ChatSessionStore.cs @@ -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 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 _sessions = new(); public void AddOrUpdate( - string deviceId, - ChatSession chatSession, + string deviceId, + ChatSession chatSession, Func updateFactory) { _sessions.AddOrUpdate(deviceId, chatSession, updateFactory); diff --git a/Server/Services/Stores/SelectedCardsStore.cs b/Server/Services/Stores/SelectedCardsStore.cs new file mode 100644 index 00000000..323121b6 --- /dev/null +++ b/Server/Services/Stores/SelectedCardsStore.cs @@ -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 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 _selectedDevices = new(); + + public IEnumerable 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); + } + } +} \ No newline at end of file diff --git a/Server/Services/ClientAppState.cs b/Server/Services/Stores/TerminalStore.cs similarity index 66% rename from Server/Services/ClientAppState.cs rename to Server/Services/Stores/TerminalStore.cs index 4629a2ee..c00b67d9 100644 --- a/Server/Services/ClientAppState.cs +++ b/Server/Services/Stores/TerminalStore.cs @@ -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 DevicesFrameSelectedDevices { get; } + event EventHandler? TerminalLinesChanged; ConcurrentQueue 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 _terminalHistory = new(); private int _terminalHistoryIndex = 0; - public DeviceCardState DevicesFrameFocusedCardState - { - get => Get(); - set => Set(value); - } - public string? DevicesFrameFocusedDevice - { - get => Get(); - set => Set(value); - } - - public ConcurrentList DevicesFrameSelectedDevices { get; } = new(); + public event EventHandler? TerminalLinesChanged; public ConcurrentQueue 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); } -} \ No newline at end of file + + public void InvokeLinesChanged() + { + TerminalLinesChanged?.Invoke(this, EventArgs.Empty); + } +} diff --git a/Tests/Server.Tests/CircuitConnectionTests.cs b/Tests/Server.Tests/CircuitConnectionTests.cs index 03b51f55..ed0ec588 100644 --- a/Tests/Server.Tests/CircuitConnectionTests.cs +++ b/Tests/Server.Tests/CircuitConnectionTests.cs @@ -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 _authService; - private Mock _clientAppState; + private Mock _clientAppState; private HubContextFixture _agentHubContextFixture; private Mock _appConfig; private Mock _circuitManager; @@ -52,7 +44,7 @@ public class CircuitConnectionTests _dataService = IoCActivator.ServiceProvider.GetRequiredService(); _authService = new Mock(); - _clientAppState = new Mock(); + _clientAppState = new Mock(); _agentHubContextFixture = new HubContextFixture(); _appConfig = new Mock(); _circuitManager = new Mock();