using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using Remotely.Server.Auth; using Remotely.Server.Enums; using Remotely.Server.Hubs; using Remotely.Server.Models; using Remotely.Server.Services; using Remotely.Shared.Models; using Remotely.Shared.Utilities; using Remotely.Shared.ViewModels; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; namespace Remotely.Server.Components.Devices { public partial class DeviceCard : AuthComponentBase, IDisposable { private ElementReference _card; private ConcurrentDictionary _fileUploadProgressLookup = new(); [Parameter] public Device Device { get; set; } [CascadingParameter] public DevicesFrame ParentFrame { get; set; } [Parameter] public ConcurrentDictionary RemoteControlTargetLookup { get; set; } [Inject] private IClientAppState AppState { get; set; } [Inject] private ICircuitConnection CircuitConnection { get; set; } [Inject] private IDataService DataService { get; set; } [Inject] private IModalService ModalService { get; set; } private bool IsExpanded => GetCardState() == DeviceCardState.Expanded; private bool IsSelected => AppState.DevicesFrameSelectedDevices.Contains(Device.ID); [Inject] private IJsInterop JsInterop { get; set; } private string Theme => AppState.EffectiveTheme.ToString().ToLower(); [Inject] private IToastService ToastService { get; set; } public void Dispose() { AppState.PropertyChanged -= AppState_PropertyChanged; GC.SuppressFinalize(this); } protected override Task OnInitializedAsync() { AppState.PropertyChanged += AppState_PropertyChanged; return base.OnInitializedAsync(); } 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 string GetProgressMessage(string key) { if (_fileUploadProgressLookup.TryGetValue(key, out var value)) { return $"{MathHelper.GetFormattedPercent(value)} - {key}"; } return string.Empty; } private void ExpandCard() { if (AppState.DevicesFrameFocusedDevice == Device.ID) { if (AppState.DevicesFrameFocusedCardState == DeviceCardState.Normal) { AppState.DevicesFrameFocusedCardState = DeviceCardState.Expanded; } return; } AppState.DevicesFrameFocusedDevice = Device.ID; AppState.DevicesFrameFocusedCardState = DeviceCardState.Expanded; JsInterop.ScrollToElement(_card); } private DeviceCardState GetCardState() { 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; } private async Task OnFileInputChanged(InputFileChangeEventArgs args) { ToastService.ShowToast("File upload started."); var fileId = await DataService.AddSharedFile(args.File, User.OrganizationID, OnFileInputProgress); var transferId = Guid.NewGuid().ToString(); var result = await CircuitConnection.TransferFileFromBrowserToAgent(Device.ID, transferId, new[] { fileId }); if (!result) { ToastService.ShowToast("Device not found.", classString: "bg-warning"); } else { ToastService.ShowToast("File upload completed."); } } private void OnFileInputProgress(double percentComplete, string fileName) { if (_fileUploadProgressLookup.TryGetValue(fileName, out var existingValue) && percentComplete < 1 && percentComplete - existingValue < .05) { // Avoid too frequent of updates. return; } _fileUploadProgressLookup.AddOrUpdate(fileName, percentComplete, (k, v) => percentComplete); InvokeAsync(StateHasChanged); } private Task HandleValidSubmit() { DataService.UpdateDevice(Device.ID, Device.Tags, Device.Alias, Device.DeviceGroupID, Device.Notes, Device.WebRtcSetting); ToastService.ShowToast("Device settings saved."); return Task.CompletedTask; } private void OpenDeviceDetails() { JsInterop.OpenWindow($"/DeviceDetails/{Device.ID}", "_blank"); } private void SetCardStateNormal() { AppState.DevicesFrameFocusedDevice = null; AppState.DevicesFrameFocusedCardState = DeviceCardState.Normal; } private void ShowAllDisks() { var disksString = JsonSerializer.Serialize(Device.Drives, JsonSerializerHelper.IndentedOptions); void modalBody(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { builder.AddMarkupContent(0, $"
{disksString}
"); } ModalService.ShowModal($"All Disks for {Device.DeviceName}", modalBody); } private void StartChat() { var existingSession = AppState.DevicesFrameChatSessions.FirstOrDefault(x => x.DeviceId == Device.ID); if (existingSession is null) { AppState.DevicesFrameChatSessions.Add(new ChatSession() { DeviceId = Device.ID, DeviceName = Device.DeviceName, IsExpanded = true }); } else { existingSession.IsExpanded = true; } AppState.InvokePropertyChanged(nameof(AppState.DevicesFrameChatSessions)); } private void StartRemoteControl(bool viewOnly) { var targetDevice = AgentHub.ServiceConnections.FirstOrDefault(x => x.Value.ID == Device.ID); RemoteControlTargetLookup[Device.ID] = new RemoteControlTarget() { ViewOnlyMode = viewOnly, ServiceConnectionId = targetDevice.Key }; CircuitConnection.RemoteControl(Device.ID); } private void ToggleIsSelected(ChangeEventArgs args) { var isSelected = (bool)args.Value; if (isSelected) { AppState.DevicesFrameSelectedDevices.Add(Device.ID); } else { AppState.DevicesFrameSelectedDevices.Remove(Device.ID); } AppState.InvokePropertyChanged(nameof(AppState.DevicesFrameSelectedDevices)); } private async Task UninstallAgent() { var result = await JsInterop.Confirm("Are you sure you want to uninstall this agent? This is permanent!"); if (result) { await CircuitConnection.UninstallAgents(new[] { Device.ID }); ParentFrame.Refresh(); } } } }