mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Another pass of nullable refactoring.
This commit is contained in:
parent
0f9ea4957b
commit
ac6487c6c5
@ -1,4 +1,5 @@
|
||||
using IWshRuntimeLibrary;
|
||||
#nullable enable
|
||||
using IWshRuntimeLibrary;
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using Microsoft.Win32;
|
||||
using Remotely.Agent.Installer.Win.Utilities;
|
||||
@ -26,14 +27,14 @@ public class InstallerService
|
||||
private readonly string _platform = Environment.Is64BitOperatingSystem ? "x64" : "x86";
|
||||
private readonly JavaScriptSerializer _serializer = new JavaScriptSerializer();
|
||||
|
||||
public event EventHandler<string> ProgressMessageChanged;
|
||||
public event EventHandler<int> ProgressValueChanged;
|
||||
public event EventHandler<string>? ProgressMessageChanged;
|
||||
public event EventHandler<int>? ProgressValueChanged;
|
||||
|
||||
public async Task<bool> Install(string serverUrl,
|
||||
string organizationId,
|
||||
string deviceGroup,
|
||||
string deviceAlias,
|
||||
string deviceUuid,
|
||||
string? deviceGroup,
|
||||
string? deviceAlias,
|
||||
string? deviceUuid,
|
||||
bool createSupportShortcut)
|
||||
{
|
||||
try
|
||||
@ -174,8 +175,8 @@ public class InstallerService
|
||||
|
||||
private async Task CreateDeviceOnServer(string deviceUuid,
|
||||
string serverUrl,
|
||||
string deviceGroup,
|
||||
string deviceAlias,
|
||||
string? deviceGroup,
|
||||
string? deviceAlias,
|
||||
string organizationId)
|
||||
{
|
||||
try
|
||||
@ -199,9 +200,10 @@ public class InstallerService
|
||||
{
|
||||
await sw.WriteAsync(_serializer.Serialize(setupOptions));
|
||||
}
|
||||
using (var response = await wr.GetResponseAsync() as HttpWebResponse)
|
||||
{
|
||||
Logger.Write($"Create device response: {response.StatusCode}");
|
||||
using var response = await wr.GetResponseAsync();
|
||||
if (response is HttpWebResponse httpResponse)
|
||||
{
|
||||
Logger.Write($"Create device response: {httpResponse.StatusCode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,7 +263,7 @@ public class InstallerService
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgressMessageChanged.Invoke(this, "Downloading Remotely agent.");
|
||||
ProgressMessageChanged?.Invoke(this, "Downloading Remotely agent.");
|
||||
using (var client = new WebClient())
|
||||
{
|
||||
client.DownloadProgressChanged += (sender, args) =>
|
||||
@ -273,7 +275,7 @@ public class InstallerService
|
||||
}
|
||||
}
|
||||
|
||||
ProgressMessageChanged.Invoke(this, "Extracting Remotely files.");
|
||||
ProgressMessageChanged?.Invoke(this, "Extracting Remotely files.");
|
||||
ProgressValueChanged?.Invoke(this, 0);
|
||||
|
||||
var tempDir = Path.Combine(Path.GetTempPath(), "RemotelyUpdate");
|
||||
@ -321,7 +323,7 @@ public class InstallerService
|
||||
ProgressValueChanged?.Invoke(this, 0);
|
||||
}
|
||||
|
||||
private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl, string deviceUuid)
|
||||
private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl, string? deviceUuid)
|
||||
{
|
||||
ConnectionInfo connectionInfo;
|
||||
var connectionInfoPath = Path.Combine(_installPath, "ConnectionInfo.json");
|
||||
@ -345,7 +347,7 @@ public class InstallerService
|
||||
{
|
||||
connectionInfo.ServerVerificationToken = null;
|
||||
}
|
||||
connectionInfo.DeviceID = deviceUuid;
|
||||
connectionInfo.DeviceID = deviceUuid!;
|
||||
}
|
||||
connectionInfo.OrganizationID = organizationId;
|
||||
connectionInfo.Host = serverUrl;
|
||||
|
||||
@ -465,7 +465,13 @@ public class MainWindowViewModel : ViewModelBase
|
||||
|
||||
HeaderMessage = "Installing Remotely...";
|
||||
|
||||
if (await _installer.Install(ServerUrl, OrganizationID, DeviceGroup, DeviceAlias, DeviceUuid, CreateSupportShortcut))
|
||||
if (await _installer.Install(
|
||||
ServerUrl,
|
||||
OrganizationID!,
|
||||
DeviceGroup,
|
||||
DeviceAlias,
|
||||
DeviceUuid,
|
||||
CreateSupportShortcut))
|
||||
{
|
||||
IsServiceInstalled = true;
|
||||
Progress = 0;
|
||||
|
||||
@ -5,5 +5,5 @@ namespace Remotely.Agent.Models;
|
||||
public class ChatSession
|
||||
{
|
||||
public int ProcessID { get; set; }
|
||||
public NamedPipeClientStream PipeStream { get; set; }
|
||||
public NamedPipeClientStream? PipeStream { get; set; }
|
||||
}
|
||||
|
||||
@ -10,12 +10,15 @@ using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Remotely.Agent.Services;
|
||||
|
||||
@ -36,15 +39,16 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IWakeOnLanService _wakeOnLanService;
|
||||
private readonly ILogger<AgentHubConnection> _logger;
|
||||
private readonly ILogger _fileLogger;
|
||||
private readonly IEnumerable<ILoggerProvider> _loggerProviders;
|
||||
private readonly IScriptExecutor _scriptExecutor;
|
||||
private readonly IUninstaller _uninstaller;
|
||||
private readonly IUpdater _updater;
|
||||
|
||||
private ConnectionInfo _connectionInfo;
|
||||
private HubConnection _hubConnection;
|
||||
private Timer _heartbeatTimer;
|
||||
private ConnectionInfo? _connectionInfo;
|
||||
private HubConnection? _hubConnection;
|
||||
private Timer? _heartbeatTimer;
|
||||
private bool _isServerVerified;
|
||||
private FileLogger? _fileLogger;
|
||||
|
||||
public AgentHubConnection(
|
||||
IConfigService configService,
|
||||
@ -69,24 +73,41 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
_httpFactory = httpFactory;
|
||||
_wakeOnLanService = wakeOnLanService;
|
||||
_logger = logger;
|
||||
_fileLogger = loggerProviders
|
||||
.OfType<FileLoggerProvider>()
|
||||
.FirstOrDefault()
|
||||
?.CreateLogger(nameof(AgentHubConnection));
|
||||
_loggerProviders = loggerProviders;
|
||||
}
|
||||
|
||||
public bool IsConnected => _hubConnection?.State == HubConnectionState.Connected;
|
||||
|
||||
public async Task Connect()
|
||||
{
|
||||
using var throttle = new SemaphoreSlim(1, 1);
|
||||
var count = 1;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var waitSeconds = Math.Min(60, Math.Pow(count, 2));
|
||||
// This will allow the first attempt to go through immediately, but
|
||||
// subsequent attempts will have an exponential delay.
|
||||
_ = await throttle.WaitAsync(TimeSpan.FromSeconds(waitSeconds));
|
||||
|
||||
_logger.LogInformation("Attempting to connect to server.");
|
||||
|
||||
_connectionInfo = _configService.GetConnectionInfo();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_connectionInfo.OrganizationID))
|
||||
{
|
||||
_logger.LogError("Organization ID is not set. Please set it in the config file.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_connectionInfo.Host))
|
||||
{
|
||||
_logger.LogError("Host (server URL) is not set. Please set it in the config file.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_hubConnection is not null)
|
||||
{
|
||||
await _hubConnection.DisposeAsync();
|
||||
@ -116,13 +137,11 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
// The above can be caused by temporary issues on the server. So we'll do
|
||||
// nothing here and wait for it to get resolved.
|
||||
_logger.LogError("There was an issue registering with the server. The server might be undergoing maintenance, or the supplied organization ID might be incorrect.");
|
||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!await VerifyServer())
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -144,10 +163,7 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while connecting to server.");
|
||||
await Task.Delay(5_000);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +177,17 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_connectionInfo is null || _hubConnection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_connectionInfo.OrganizationID))
|
||||
{
|
||||
_logger.LogError("Organization ID is not set. Please set it in the config file.");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentInfo = await _deviceInfoService.CreateDevice(_connectionInfo.DeviceID, _connectionInfo.OrganizationID);
|
||||
await _hubConnection.SendAsync("DeviceHeartbeat", currentInfo);
|
||||
}
|
||||
@ -172,6 +199,11 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
|
||||
private async Task<bool> CheckForServerMigration()
|
||||
{
|
||||
if (_connectionInfo is null || _hubConnection is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var serverUrl = await _hubConnection.InvokeAsync<string>("GetServerUrl");
|
||||
|
||||
if (Uri.TryCreate(serverUrl, UriKind.Absolute, out var serverUri) &&
|
||||
@ -187,17 +219,22 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
private async void HeartbeatTimer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
private async void HeartbeatTimer_Elapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
await SendHeartbeat();
|
||||
}
|
||||
|
||||
private async Task HubConnection_Reconnected(string arg)
|
||||
private async Task HubConnection_Reconnected(string? arg)
|
||||
{
|
||||
if (_connectionInfo is null || _hubConnection is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Reconnected to server.");
|
||||
await _updater.CheckForUpdates();
|
||||
|
||||
var device = await _deviceInfoService.CreateDevice(_connectionInfo.DeviceID, _connectionInfo.OrganizationID);
|
||||
var device = await _deviceInfoService.CreateDevice(_connectionInfo.DeviceID, $"{_connectionInfo.OrganizationID}");
|
||||
|
||||
if (!await _hubConnection.InvokeAsync<bool>("DeviceCameOnline", device))
|
||||
{
|
||||
@ -214,6 +251,10 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
|
||||
private void RegisterMessageHandlers()
|
||||
{
|
||||
if (_hubConnection is null)
|
||||
{
|
||||
throw new InvalidOperationException("Hub connection is null.");
|
||||
}
|
||||
|
||||
_hubConnection.On("ChangeWindowsSession", async (string viewerConnectionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, int targetSessionID) =>
|
||||
{
|
||||
@ -263,6 +304,10 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
|
||||
_hubConnection.On("DeleteLogs", () =>
|
||||
{
|
||||
if (TryGetFileLogger(out var fileLogger))
|
||||
{
|
||||
fileLogger.DeleteLogs();
|
||||
}
|
||||
if (_fileLogger is FileLogger logger)
|
||||
{
|
||||
logger.DeleteLogs();
|
||||
@ -466,13 +511,14 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
|
||||
foreach (var fileID in fileIDs)
|
||||
{
|
||||
var url = $"{_connectionInfo.Host}/API/FileSharing/{fileID}";
|
||||
var url = $"{_connectionInfo?.Host}/API/FileSharing/{fileID}";
|
||||
using var client = _httpFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken);
|
||||
using var response = await client.GetAsync(url);
|
||||
|
||||
var filename = response.Content.Headers.ContentDisposition.FileName;
|
||||
var legalChars = filename.ToCharArray().Where(x => !Path.GetInvalidFileNameChars().Any(y => x == y));
|
||||
var filename = response.Content.Headers.ContentDisposition?.FileName ?? Path.GetRandomFileName();
|
||||
var invalidChars = Path.GetInvalidFileNameChars().ToHashSet();
|
||||
var legalChars = filename.ToCharArray().Where(x => !invalidChars.Contains(x));
|
||||
|
||||
filename = new string(legalChars.ToArray());
|
||||
|
||||
@ -499,8 +545,32 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
private bool TryGetFileLogger([NotNullWhen(true)] out FileLogger? fileLogger)
|
||||
{
|
||||
if (_fileLogger is null)
|
||||
{
|
||||
var logger = _loggerProviders
|
||||
.OfType<FileLoggerProvider>()
|
||||
.FirstOrDefault()
|
||||
?.CreateLogger(nameof(AgentHubConnection));
|
||||
|
||||
if (logger is FileLogger loggerImpl)
|
||||
{
|
||||
_fileLogger = loggerImpl;
|
||||
}
|
||||
}
|
||||
|
||||
fileLogger = _fileLogger;
|
||||
return fileLogger is not null;
|
||||
}
|
||||
|
||||
private async Task<bool> VerifyServer()
|
||||
{
|
||||
if (_connectionInfo is null || _hubConnection is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_connectionInfo.ServerVerificationToken))
|
||||
{
|
||||
_isServerVerified = true;
|
||||
|
||||
@ -39,7 +39,7 @@ public class ChatClientService : IChatClientService
|
||||
{
|
||||
return;
|
||||
}
|
||||
chatSession.PipeStream.Dispose();
|
||||
chatSession.PipeStream?.Dispose();
|
||||
var chatProcess = Process.GetProcessById(chatSession.ProcessID);
|
||||
if (chatProcess?.HasExited == false)
|
||||
{
|
||||
@ -111,7 +111,7 @@ public class ChatClientService : IChatClientService
|
||||
|
||||
chatSession = (ChatSession)_chatClients.Get(senderConnectionID);
|
||||
|
||||
if (!chatSession.PipeStream.IsConnected)
|
||||
if (chatSession.PipeStream?.IsConnected != true)
|
||||
{
|
||||
_chatClients.Remove(senderConnectionID);
|
||||
await hubConnection.SendAsync("DisplayMessage", "Chat disconnected. Please try again.", "Chat disconnected.", "bg-warning", senderConnectionID);
|
||||
@ -142,7 +142,7 @@ public class ChatClientService : IChatClientService
|
||||
if (!string.IsNullOrWhiteSpace(messageJson))
|
||||
{
|
||||
var chatMessage = JsonSerializer.Deserialize<ChatMessage>(messageJson);
|
||||
await hubConnection.SendAsync("Chat", chatMessage.Message, false, senderConnectionID);
|
||||
await hubConnection.SendAsync("Chat", $"{chatMessage?.Message}", false, senderConnectionID);
|
||||
}
|
||||
}
|
||||
await hubConnection.SendAsync("Chat", string.Empty, true, senderConnectionID);
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Immense.RemoteControl.Shared;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Remotely.Agent.Services;
|
||||
@ -18,16 +20,17 @@ public interface IConfigService
|
||||
public class ConfigService : IConfigService
|
||||
{
|
||||
private static readonly object _fileLock = new();
|
||||
private ConnectionInfo _connectionInfo;
|
||||
private readonly string _debugGuid = "f2b0a595-5ea8-471b-975f-12e70e0f3497";
|
||||
private readonly ILogger<ConfigService> _logger;
|
||||
private ConnectionInfo? _connectionInfo;
|
||||
|
||||
public ConfigService(ILogger<ConfigService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> _commandLineArgs;
|
||||
private Dictionary<string, string>? _commandLineArgs;
|
||||
|
||||
private Dictionary<string, string> CommandLineArgs
|
||||
{
|
||||
get
|
||||
@ -36,13 +39,14 @@ public class ConfigService : IConfigService
|
||||
{
|
||||
_commandLineArgs = new Dictionary<string, string>();
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
|
||||
for (var i = 1; i < args.Length; i += 2)
|
||||
{
|
||||
var key = args?[i];
|
||||
var key = args[i];
|
||||
if (key != null)
|
||||
{
|
||||
key = key.Trim().Replace("-", "").ToLower();
|
||||
var value = args?[i + 1];
|
||||
var value = args[i + 1];
|
||||
if (value != null)
|
||||
{
|
||||
_commandLineArgs[key] = args[i + 1].Trim();
|
||||
@ -57,41 +61,52 @@ public class ConfigService : IConfigService
|
||||
|
||||
public ConnectionInfo GetConnectionInfo()
|
||||
{
|
||||
// For debugging purposes (i.e. launch of a bunch of instances).
|
||||
if (CommandLineArgs.TryGetValue("organization", out var orgID) &&
|
||||
CommandLineArgs.TryGetValue("host", out var hostName) &&
|
||||
CommandLineArgs.TryGetValue("device", out var deviceID))
|
||||
{
|
||||
return new ConnectionInfo()
|
||||
try
|
||||
{ // For debugging purposes (i.e. launch of a bunch of instances).
|
||||
if (CommandLineArgs.TryGetValue("organization", out var orgID) &&
|
||||
CommandLineArgs.TryGetValue("host", out var hostName) &&
|
||||
CommandLineArgs.TryGetValue("device", out var deviceID))
|
||||
{
|
||||
DeviceID = deviceID,
|
||||
Host = hostName,
|
||||
OrganizationID = orgID
|
||||
};
|
||||
}
|
||||
|
||||
if (Environment.UserInteractive && Debugger.IsAttached)
|
||||
{
|
||||
return new ConnectionInfo()
|
||||
{
|
||||
DeviceID = _debugGuid,
|
||||
Host = "http://localhost:5000",
|
||||
OrganizationID = orgID
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (_connectionInfo == null)
|
||||
{
|
||||
lock (_fileLock)
|
||||
{
|
||||
if (!File.Exists("ConnectionInfo.json"))
|
||||
return new ConnectionInfo()
|
||||
{
|
||||
_logger.LogError("No connection info available. Please create ConnectionInfo.json file with appropriate values.");
|
||||
return null;
|
||||
}
|
||||
_connectionInfo = JsonSerializer.Deserialize<ConnectionInfo>(File.ReadAllText("ConnectionInfo.json"));
|
||||
DeviceID = deviceID,
|
||||
Host = hostName,
|
||||
OrganizationID = orgID
|
||||
};
|
||||
}
|
||||
|
||||
if (Environment.UserInteractive && Debugger.IsAttached)
|
||||
{
|
||||
return new ConnectionInfo()
|
||||
{
|
||||
DeviceID = _debugGuid,
|
||||
Host = "http://localhost:5000",
|
||||
OrganizationID = orgID
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (_connectionInfo == null)
|
||||
{
|
||||
lock (_fileLock)
|
||||
{
|
||||
if (!File.Exists("ConnectionInfo.json"))
|
||||
{
|
||||
_logger.LogError("No connection info available. Please create ConnectionInfo.json file with appropriate values.");
|
||||
throw new InvalidOperationException("Config file does not exist.");
|
||||
}
|
||||
_connectionInfo = JsonSerializer.Deserialize<ConnectionInfo>(File.ReadAllText("ConnectionInfo.json"));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to retrieve connection info.");
|
||||
}
|
||||
|
||||
if (_connectionInfo is null)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load config data.");
|
||||
}
|
||||
|
||||
return _connectionInfo;
|
||||
|
||||
@ -45,14 +45,15 @@ public abstract class DeviceInfoGeneratorBase
|
||||
{
|
||||
try
|
||||
{
|
||||
DriveInfo systemDrive;
|
||||
DriveInfo? systemDrive;
|
||||
var allDrives = DriveInfo.GetDrives();
|
||||
|
||||
if (EnvironmentHelper.IsWindows)
|
||||
{
|
||||
var rootPath = Path.GetPathRoot(Environment.SystemDirectory) ?? "C:\\";
|
||||
systemDrive = allDrives.FirstOrDefault(x =>
|
||||
x.IsReady &&
|
||||
x.RootDirectory.FullName.Contains(Path.GetPathRoot(Environment.SystemDirectory ?? Environment.CurrentDirectory)));
|
||||
x.RootDirectory.FullName.Contains(rootPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -97,7 +98,7 @@ public abstract class DeviceInfoGeneratorBase
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting drive info.");
|
||||
return null;
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Immense.RemoteControl.Shared.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Models;
|
||||
@ -8,12 +9,13 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace Remotely.Agent.Services;
|
||||
|
||||
public interface IExternalScriptingShell
|
||||
{
|
||||
Process ShellProcess { get; }
|
||||
Process? ShellProcess { get; }
|
||||
Task Init(ScriptingShell shell, string shellProcessName, string lineEnding, string connectionId);
|
||||
Task<ScriptResult> WriteInput(string input, TimeSpan timeout);
|
||||
}
|
||||
@ -27,13 +29,13 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
private readonly SemaphoreSlim _writeLock = new(1, 1);
|
||||
private string _errorOut = string.Empty;
|
||||
private string _lastInputID = string.Empty;
|
||||
private string _lineEnding;
|
||||
private string _lineEnding = Environment.NewLine;
|
||||
private System.Timers.Timer _processIdleTimeout = new(TimeSpan.FromMinutes(10))
|
||||
{
|
||||
AutoReset = false
|
||||
};
|
||||
|
||||
private string _senderConnectionId;
|
||||
private string? _senderConnectionId;
|
||||
private ScriptingShell _shell;
|
||||
private string _standardOut = string.Empty;
|
||||
|
||||
@ -44,7 +46,8 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
}
|
||||
public Process ShellProcess { get; set; }
|
||||
|
||||
public Process? ShellProcess { get; private set; }
|
||||
|
||||
|
||||
// TODO: Turn into cache and factory.
|
||||
@ -94,9 +97,9 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
RedirectStandardOutput = true
|
||||
};
|
||||
|
||||
var connectionInfo = _configService.GetConnectionInfo();
|
||||
psi.Environment.Add("DeviceId", connectionInfo.DeviceID);
|
||||
psi.Environment.Add("ServerUrl", connectionInfo.Host);
|
||||
var configInfo = _configService.GetConnectionInfo();
|
||||
psi.Environment.Add("DeviceId", configInfo.DeviceID);
|
||||
psi.Environment.Add("ServerUrl", configInfo.Host);
|
||||
|
||||
ShellProcess = new Process
|
||||
{
|
||||
@ -133,6 +136,11 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
|
||||
try
|
||||
{
|
||||
if (ShellProcess?.HasExited != false)
|
||||
{
|
||||
throw new InvalidOperationException("Shell process is not running.");
|
||||
}
|
||||
|
||||
_processIdleTimeout.Stop();
|
||||
_processIdleTimeout.Start();
|
||||
_outputDone.Reset();
|
||||
@ -190,7 +198,7 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
StandardOutput = _standardOut.Split(Environment.NewLine),
|
||||
ErrorOutput = _errorOut.Split(Environment.NewLine),
|
||||
HadErrors = !string.IsNullOrWhiteSpace(_errorOut) ||
|
||||
(ShellProcess.HasExited && ShellProcess.ExitCode != 0)
|
||||
(ShellProcess?.HasExited == true && ShellProcess.ExitCode != 0)
|
||||
};
|
||||
}
|
||||
|
||||
@ -210,12 +218,12 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
.Concat(_errorOut.Split(Environment.NewLine))
|
||||
.ToArray(),
|
||||
HadErrors = !string.IsNullOrWhiteSpace(_errorOut) ||
|
||||
(ShellProcess.HasExited && ShellProcess.ExitCode != 0)
|
||||
(ShellProcess?.HasExited == true && ShellProcess.ExitCode != 0)
|
||||
};
|
||||
ProcessIdleTimeout_Elapsed(this, null);
|
||||
RemoveSession();
|
||||
return partialResult;
|
||||
}
|
||||
private void ProcessIdleTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
private void ProcessIdleTimeout_Elapsed(object? sender, ElapsedEventArgs e)
|
||||
{
|
||||
RemoveSession();
|
||||
}
|
||||
@ -223,6 +231,10 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
private void RemoveSession()
|
||||
{
|
||||
ShellProcess?.Kill();
|
||||
if (_senderConnectionId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_sessions.TryRemove(_senderConnectionId, out _);
|
||||
}
|
||||
|
||||
@ -236,7 +248,7 @@ public class ExternalScriptingShell : IExternalScriptingShell
|
||||
|
||||
private void ShellProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e?.Data?.Contains(_lastInputID) == true)
|
||||
if (e.Data?.Contains(_lastInputID) == true)
|
||||
{
|
||||
_outputDone.Set();
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ public class AppLauncherLinux : IAppLauncher
|
||||
xauthority,
|
||||
display,
|
||||
args);
|
||||
return Process.Start(psi).Id;
|
||||
return Process.Start(psi)?.Id ?? throw new InvalidOperationException("Failed to launch desktop app.");
|
||||
}
|
||||
|
||||
private string GetXorgAuth()
|
||||
|
||||
@ -56,7 +56,7 @@ public class DeviceInfoGeneratorLinux : DeviceInfoGeneratorBase, IDeviceInformat
|
||||
private string GetCurrentUser()
|
||||
{
|
||||
var users = _processInvoker.InvokeProcessOutput("users", "");
|
||||
return users?.Split()?.FirstOrDefault()?.Trim();
|
||||
return users?.Split()?.FirstOrDefault()?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
public (double usedGB, double totalGB) GetMemoryInGB()
|
||||
@ -67,21 +67,23 @@ public class DeviceInfoGeneratorLinux : DeviceInfoGeneratorBase, IDeviceInformat
|
||||
var resultsArr = results.Split("\n".ToCharArray());
|
||||
var freeKB = resultsArr
|
||||
.FirstOrDefault(x => x.Trim().StartsWith("MemAvailable"))
|
||||
.Trim()
|
||||
.Split(" ".ToCharArray(), 2)
|
||||
.Last() // 9168236 kB
|
||||
.Trim()
|
||||
.Split(' ')
|
||||
.First(); // 9168236
|
||||
?.Trim()
|
||||
?.Split(" ".ToCharArray(), 2)
|
||||
?.Last() // 9168236 kB
|
||||
?.Trim()
|
||||
?.Split(' ')
|
||||
?.First() // 9168236
|
||||
?? "0";
|
||||
|
||||
var totalKB = resultsArr
|
||||
.FirstOrDefault(x => x.Trim().StartsWith("MemTotal"))
|
||||
.Trim()
|
||||
.Split(" ".ToCharArray(), 2)
|
||||
.Last() // 16637468 kB
|
||||
.Trim()
|
||||
.Split(' ')
|
||||
.First(); // 16637468
|
||||
?.Trim()
|
||||
?.Split(" ".ToCharArray(), 2)
|
||||
?.Last() // 16637468 kB
|
||||
?.Trim()
|
||||
?.Split(' ')
|
||||
?.First() // 16637468
|
||||
?? "0";
|
||||
|
||||
var freeGB = Math.Round(double.Parse(freeKB) / 1024 / 1024, 2);
|
||||
var totalGB = Math.Round(double.Parse(totalKB) / 1024 / 1024, 2);
|
||||
|
||||
@ -105,7 +105,7 @@ public class UpdaterLinux : IUpdater
|
||||
await InstallLatestVersion();
|
||||
|
||||
}
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotModified)
|
||||
catch (WebException ex) when (ex.Response is HttpWebResponse http && http.StatusCode == HttpStatusCode.NotModified)
|
||||
{
|
||||
_logger.LogInformation("Service Updater: Version is current.");
|
||||
return;
|
||||
@ -180,7 +180,7 @@ public class UpdaterLinux : IUpdater
|
||||
}
|
||||
}
|
||||
|
||||
private async void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
private async void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
await CheckForUpdates();
|
||||
}
|
||||
|
||||
@ -114,6 +114,6 @@ public class DeviceInfoGeneratorMac : DeviceInfoGeneratorBase, IDeviceInformatio
|
||||
private string GetCurrentUser()
|
||||
{
|
||||
var users = _processInvoker.InvokeProcessOutput("users", "");
|
||||
return users?.Split()?.FirstOrDefault()?.Trim();
|
||||
return users?.Split()?.FirstOrDefault()?.Trim() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ public class UpdaterMac : IUpdater
|
||||
await InstallLatestVersion();
|
||||
|
||||
}
|
||||
catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotModified)
|
||||
catch (WebException ex) when (ex.Response is HttpWebResponse http && http.StatusCode == HttpStatusCode.NotModified)
|
||||
{
|
||||
_logger.LogInformation("Service Updater: Version is current.");
|
||||
return;
|
||||
@ -166,7 +166,7 @@ public class UpdaterMac : IUpdater
|
||||
}
|
||||
}
|
||||
|
||||
private async void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
private async void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
await CheckForUpdates();
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ namespace Remotely.Agent.Services;
|
||||
|
||||
public interface IPsCoreShell
|
||||
{
|
||||
string SenderConnectionId { get; set; }
|
||||
string? SenderConnectionId { get; set; }
|
||||
|
||||
CommandCompletion GetCompletions(string inputText, int currentIndex, bool? forward);
|
||||
Task<ScriptResult> WriteInput(string input);
|
||||
@ -25,15 +25,16 @@ public class PsCoreShell : IPsCoreShell
|
||||
private readonly ConnectionInfo _connectionInfo;
|
||||
private readonly ILogger<PsCoreShell> _logger;
|
||||
private readonly PowerShell _powershell;
|
||||
private CommandCompletion _lastCompletion;
|
||||
private string _lastInputText;
|
||||
private CommandCompletion? _lastCompletion;
|
||||
private string? _lastInputText;
|
||||
|
||||
public PsCoreShell(
|
||||
IConfigService configService,
|
||||
ILogger<PsCoreShell> logger)
|
||||
{
|
||||
_configService = configService;
|
||||
_logger = logger;
|
||||
_connectionInfo = _configService.GetConnectionInfo();
|
||||
_logger = logger;
|
||||
|
||||
_powershell = PowerShell.Create();
|
||||
|
||||
@ -47,7 +48,8 @@ public class PsCoreShell : IPsCoreShell
|
||||
_powershell.Invoke();
|
||||
}
|
||||
|
||||
public string SenderConnectionId { get; set; }
|
||||
public string? SenderConnectionId { get; set; }
|
||||
|
||||
// TODO: Turn into cache and factory.
|
||||
public static IPsCoreShell GetCurrent(string senderConnectionId)
|
||||
{
|
||||
@ -99,7 +101,10 @@ public class PsCoreShell : IPsCoreShell
|
||||
ps.AddScript("$args[0] | Out-String");
|
||||
ps.AddArgument(results);
|
||||
var result = await ps.InvokeAsync();
|
||||
var hostOutput = result[0].BaseObject.ToString();
|
||||
|
||||
var hostOutput = result.Count > 0 ?
|
||||
$"{result[0].BaseObject}" :
|
||||
string.Empty;
|
||||
|
||||
var verboseOut = _powershell.Streams.Verbose.ReadAll().Select(x => x.Message);
|
||||
var debugOut = _powershell.Streams.Debug.ReadAll().Select(x => x.Message);
|
||||
@ -110,7 +115,8 @@ public class PsCoreShell : IPsCoreShell
|
||||
var standardOut = hostOutput.Split(Environment.NewLine)
|
||||
.Concat(infoOut)
|
||||
.Concat(debugOut)
|
||||
.Concat(verboseOut);
|
||||
.Concat(verboseOut)
|
||||
.Select(x => $"{x}");
|
||||
|
||||
var errorAndWarningOut = errorOut.Concat(warningOut).ToArray();
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Immense.RemoteControl.Shared.Extensions;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Shared;
|
||||
using Remotely.Shared.Enums;
|
||||
@ -103,8 +105,7 @@ public class ScriptExecutor : IScriptExecutor
|
||||
scriptRunId,
|
||||
initiator);
|
||||
|
||||
var connectionInfo = _configService.GetConnectionInfo();
|
||||
var url = $"{connectionInfo.Host}/API/SavedScripts/{savedScriptId}";
|
||||
var url = $"{_configService.GetConnectionInfo().Host}/API/SavedScripts/{savedScriptId}";
|
||||
using var hc = new HttpClient();
|
||||
hc.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken);
|
||||
var response = await hc.GetAsync(url);
|
||||
@ -193,9 +194,9 @@ public class ScriptExecutor : IScriptExecutor
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
throw new InvalidOperationException($"Unknown shell type: {shell}");
|
||||
}
|
||||
private async Task<ScriptResult> SendResultsToApi(object result, string expiringToken)
|
||||
private async Task<ScriptResult?> SendResultsToApi(object result, string expiringToken)
|
||||
{
|
||||
var targetURL = _configService.GetConnectionInfo().Host + $"/API/ScriptResults";
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ public class DeviceInfoGeneratorWin : DeviceInfoGeneratorBase, IDeviceInformatio
|
||||
var (usedStorage, totalStorage) = GetSystemDriveInfo();
|
||||
var (usedMemory, totalMemory) = GetMemoryInGB();
|
||||
|
||||
device.CurrentUser = Win32Interop.GetActiveSessions().LastOrDefault()?.Username;
|
||||
device.CurrentUser = Win32Interop.GetActiveSessions().LastOrDefault()?.Username ?? string.Empty;
|
||||
device.Drives = GetAllDrives();
|
||||
device.UsedStorage = usedStorage;
|
||||
device.TotalStorage = totalStorage;
|
||||
|
||||
@ -81,7 +81,7 @@ public class LoginController : ControllerBase
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var result = await _signInManager.PasswordSignInAsync(login.Email, login.Password, false, true);
|
||||
var result = await _signInManager.PasswordSignInAsync($"{login.Email}", $"{login.Password}", false, true);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("API login successful for {loginEmail}.", login.Email);
|
||||
|
||||
@ -288,7 +288,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
if (!ModelState.IsValid || string.IsNullOrWhiteSpace(invite.InvitedUser))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
@ -75,6 +75,13 @@ public class RemoteControlController : ControllerBase
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rcRequest.Email) ||
|
||||
string.IsNullOrWhiteSpace(rcRequest.Password) ||
|
||||
string.IsNullOrWhiteSpace(rcRequest.DeviceID))
|
||||
{
|
||||
return BadRequest("Request body is missing required values.");
|
||||
}
|
||||
|
||||
var userResult = await _dataService.GetUserByName(rcRequest.Email);
|
||||
if (!userResult.IsSuccess)
|
||||
{
|
||||
|
||||
@ -23,7 +23,7 @@ public class ConfirmEmailModel : PageModel
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
public string? StatusMessage { get; set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string userId, string code)
|
||||
{
|
||||
|
||||
@ -25,7 +25,7 @@ public class ConfirmEmailChangeModel : PageModel
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
public string? StatusMessage { get; set; }
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string userId, string email, string code)
|
||||
{
|
||||
|
||||
@ -37,13 +37,13 @@ public class ForgotPasswordModel : PageModel
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
public InputModel Input { get; set; } = new();
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
public string Email { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
@ -65,7 +65,7 @@ public class ForgotPasswordModel : PageModel
|
||||
"/Account/ResetPassword",
|
||||
pageHandler: null,
|
||||
values: new { area = "Identity", code },
|
||||
protocol: Request.Scheme);
|
||||
protocol: Request.Scheme)!;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Sending password reset for user {username}. Reset URL: {callbackUrl}", user.UserName, callbackUrl);
|
||||
|
||||
@ -38,30 +38,30 @@ public class LoginModel : PageModel
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
public InputModel Input { get; set; } = null!;
|
||||
|
||||
public IList<AuthenticationScheme> ExternalLogins { get; set; }
|
||||
public IList<AuthenticationScheme>? ExternalLogins { get; set; }
|
||||
|
||||
public string ReturnUrl { get; set; }
|
||||
public string? ReturnUrl { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string ErrorMessage { get; set; }
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
public required string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
public required string Password { get; set; }
|
||||
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
|
||||
public async Task OnGetAsync(string returnUrl = null)
|
||||
public async Task OnGetAsync(string? returnUrl = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
{
|
||||
@ -78,7 +78,7 @@ public class LoginModel : PageModel
|
||||
ReturnUrl = returnUrl;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
||||
public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
|
||||
{
|
||||
returnUrl ??= Url.Content("~/");
|
||||
|
||||
@ -96,7 +96,7 @@ public class LoginModel : PageModel
|
||||
}
|
||||
if (result.RequiresTwoFactor)
|
||||
{
|
||||
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
|
||||
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, Input.RememberMe });
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
@ -108,6 +108,11 @@ public class LoginModel : PageModel
|
||||
if (await _dataService.TempPasswordSignIn(Input.Email, Input.Password))
|
||||
{
|
||||
var user = await _userManager.FindByNameAsync(Input.Email);
|
||||
if (user is null)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Account not found.");
|
||||
return Page();
|
||||
}
|
||||
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
||||
var callbackUrl = Url.Page(
|
||||
@ -116,7 +121,7 @@ public class LoginModel : PageModel
|
||||
values: new { area = "Identity", code },
|
||||
protocol: Request.Scheme);
|
||||
|
||||
return Redirect(callbackUrl);
|
||||
return Redirect(callbackUrl!);
|
||||
}
|
||||
|
||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
{
|
||||
if (SignInManager.IsSignedIn(User))
|
||||
{
|
||||
var activeSessions = RemoteControlSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name);
|
||||
var activeSessions = RemoteControlSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity?.Name);
|
||||
foreach (var session in activeSessions)
|
||||
{
|
||||
await DesktopHubContext.Clients.Client(session.DesktopConnectionId).SendAsync("Disconnect", "User logged out.");
|
||||
|
||||
@ -32,18 +32,19 @@ public class EnableAuthenticatorModel : PageModel
|
||||
_urlEncoder = urlEncoder;
|
||||
}
|
||||
|
||||
public string SharedKey { get; set; }
|
||||
public string? SharedKey { get; set; }
|
||||
|
||||
public string? AuthenticatorUri { get; set; }
|
||||
|
||||
public string AuthenticatorUri { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string[] RecoveryCodes { get; set; }
|
||||
public string[]? RecoveryCodes { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
public string? StatusMessage { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
public InputModel Input { get; set; } = new();
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
@ -51,7 +52,7 @@ public class EnableAuthenticatorModel : PageModel
|
||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "Verification Code")]
|
||||
public string Code { get; set; }
|
||||
public string Code { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGetAsync()
|
||||
@ -103,7 +104,7 @@ public class EnableAuthenticatorModel : PageModel
|
||||
if (await _userManager.CountRecoveryCodesAsync(user) == 0)
|
||||
{
|
||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
RecoveryCodes = recoveryCodes.ToArray();
|
||||
RecoveryCodes = recoveryCodes?.ToArray();
|
||||
return RedirectToPage("./ShowRecoveryCodes");
|
||||
}
|
||||
else
|
||||
@ -122,10 +123,10 @@ public class EnableAuthenticatorModel : PageModel
|
||||
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
}
|
||||
|
||||
SharedKey = FormatKey(unformattedKey);
|
||||
SharedKey = FormatKey($"{unformattedKey}");
|
||||
|
||||
var email = await _userManager.GetEmailAsync(user);
|
||||
AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
|
||||
AuthenticatorUri = GenerateQrCodeUri($"{email}", $"{unformattedKey}");
|
||||
}
|
||||
|
||||
private string FormatKey(string unformattedKey)
|
||||
|
||||
@ -23,19 +23,19 @@ public partial class IndexModel : PageModel
|
||||
_signInManager = signInManager;
|
||||
}
|
||||
|
||||
public string Username { get; set; }
|
||||
public string? Username { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
public string? StatusMessage { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
public InputModel Input { get; set; } = new();
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Phone]
|
||||
[Display(Name = "Phone number")]
|
||||
public string PhoneNumber { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
}
|
||||
|
||||
private async Task LoadAsync(RemotelyUser user)
|
||||
|
||||
@ -44,6 +44,6 @@ public static class ManageNavPages
|
||||
{
|
||||
var activePage = viewContext.ViewData["ActivePage"] as string
|
||||
?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
|
||||
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
|
||||
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ public class LocalOnlyFilter : IAuthorizationFilter
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
{
|
||||
var remoteIp = context.HttpContext.Connection.RemoteIpAddress;
|
||||
if (!IPAddress.IsLoopback(remoteIp))
|
||||
if (remoteIp is null || !IPAddress.IsLoopback(remoteIp))
|
||||
{
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
|
||||
@ -22,10 +22,10 @@ public class TwoFactorRequiredHandler : AuthorizationHandler<TwoFactorRequiredRe
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, TwoFactorRequiredRequirement requirement)
|
||||
{
|
||||
if (context.User.Identity.IsAuthenticated && _appConfig.Require2FA)
|
||||
if (context.User.Identity?.IsAuthenticated == true && _appConfig.Require2FA)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(context.User);
|
||||
if (!user.TwoFactorEnabled)
|
||||
if (user?.TwoFactorEnabled != true)
|
||||
{
|
||||
context.Fail();
|
||||
return;
|
||||
|
||||
@ -9,10 +9,10 @@
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string Message { get; set; }
|
||||
public string? Message { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string StatusClass { get; set; }
|
||||
public string? StatusClass { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnClose { get; set; }
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
private readonly List<Alert> _alerts = new();
|
||||
private bool _isOpen;
|
||||
|
||||
private string FrameClass => _isOpen ? "open" : null;
|
||||
private string? FrameClass => _isOpen ? "open" : null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -93,7 +93,8 @@
|
||||
|
||||
private void ShowAlertDetails(Alert alert)
|
||||
{
|
||||
ModalService.ShowModal($"Alert Details for {alert.Device?.DeviceName}", alert.Details.Split('\n'));
|
||||
var body = alert.Details?.Split('\n') ?? Array.Empty<string>();
|
||||
ModalService.ShowModal($"Alert Details for {alert.Device?.DeviceName}", body);
|
||||
}
|
||||
|
||||
private void ToggleOpen()
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
@code {
|
||||
|
||||
private ColorPickerModel _color;
|
||||
private ColorPickerModel _color = new();
|
||||
|
||||
[Parameter]
|
||||
public ColorPickerModel Color
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
<h6 class="mt-3">
|
||||
<i class="oi oi-chat mr-1"></i>
|
||||
<span class="badge badge-info badge-pill mr-1">
|
||||
@Session?.MissedChats
|
||||
@Session.MissedChats
|
||||
</span>
|
||||
@Session?.DeviceName
|
||||
@Session.DeviceName
|
||||
</h6>
|
||||
<div class="text-right">
|
||||
<button class="btn btn-sm btn-secondary" title="Close" @onclick="CloseChatCard" @onclick:stopPropagation>
|
||||
|
||||
@ -15,18 +15,21 @@ public partial class ChatCard : AuthComponentBase, IDisposable
|
||||
{
|
||||
private ElementReference _chatMessagesWindow;
|
||||
|
||||
private string _inputText;
|
||||
private string? _inputText;
|
||||
|
||||
[Parameter]
|
||||
public ChatSession Session { get; set; }
|
||||
[Inject]
|
||||
private IClientAppState AppState { get; set; }
|
||||
[EditorRequired]
|
||||
public required ChatSession Session { get; set; }
|
||||
|
||||
[Inject]
|
||||
private ICircuitConnection CircuitConnection { get; set; }
|
||||
private IClientAppState AppState { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IJsInterop JsInterop { get; set; }
|
||||
private ICircuitConnection CircuitConnection { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IJsInterop JsInterop { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
@ -46,15 +49,15 @@ public partial class ChatCard : AuthComponentBase, IDisposable
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
}
|
||||
|
||||
private void AppState_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
private async void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == Session.SessionId)
|
||||
{
|
||||
InvokeAsync(StateHasChanged);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private void CircuitConnection_MessageReceived(object sender, Models.CircuitEvent e)
|
||||
private async void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
|
||||
{
|
||||
if (e.EventName == Models.CircuitEventName.ChatReceived)
|
||||
{
|
||||
@ -90,7 +93,7 @@ public partial class ChatCard : AuthComponentBase, IDisposable
|
||||
session.MissedChats++;
|
||||
}
|
||||
|
||||
InvokeAsync(StateHasChanged);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
JsInterop.ScrollToEnd(_chatMessagesWindow);
|
||||
}
|
||||
@ -110,7 +113,7 @@ public partial class ChatCard : AuthComponentBase, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
await CircuitConnection.SendChat(_inputText, Session.DeviceId);
|
||||
await CircuitConnection.SendChat(_inputText, $"{Session.DeviceId}");
|
||||
|
||||
Session.ChatHistory.Add(new ChatHistoryItem()
|
||||
{
|
||||
|
||||
@ -15,10 +15,10 @@ public partial class ChatFrame : AuthComponentBase, IDisposable
|
||||
{
|
||||
|
||||
[Inject]
|
||||
private IClientAppState AppState { get; set; }
|
||||
private IClientAppState AppState { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ICircuitConnection CircuitConnection { get; set; }
|
||||
private ICircuitConnection CircuitConnection { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@ -34,7 +34,7 @@ public partial class ChatFrame : AuthComponentBase, IDisposable
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
}
|
||||
|
||||
private void CircuitConnection_MessageReceived(object sender, Models.CircuitEvent e)
|
||||
private void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
|
||||
{
|
||||
if (e.EventName == Models.CircuitEventName.ChatReceived)
|
||||
{
|
||||
@ -71,7 +71,7 @@ public partial class ChatFrame : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void AppState_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(AppState.DevicesFrameChatSessions))
|
||||
{
|
||||
|
||||
@ -31,20 +31,20 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
private DeviceGroup[] _deviceGroups = Array.Empty<DeviceGroup>();
|
||||
|
||||
[Parameter]
|
||||
public Device Device { get; set; }
|
||||
public Device Device { get; set; } = null!;
|
||||
|
||||
[CascadingParameter]
|
||||
public DevicesFrame ParentFrame { get; set; }
|
||||
public DevicesFrame ParentFrame { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private IClientAppState AppState { get; set; }
|
||||
private IClientAppState AppState { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ICircuitConnection CircuitConnection { get; set; }
|
||||
private ICircuitConnection CircuitConnection { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IDataService DataService { get; set; }
|
||||
private IDataService DataService { get; init; } = null!;
|
||||
|
||||
private bool IsExpanded => GetCardState() == DeviceCardState.Expanded;
|
||||
|
||||
@ -55,19 +55,20 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
private bool IsSelected => AppState.DevicesFrameSelectedDevices.Contains(Device.ID);
|
||||
|
||||
[Inject]
|
||||
private IJsInterop JsInterop { get; set; }
|
||||
private IJsInterop JsInterop { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IModalService ModalService { get; set; }
|
||||
private IModalService ModalService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IAgentHubSessionCache ServiceSessionCache { get; init; }
|
||||
private IAgentHubSessionCache ServiceSessionCache { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IToastService ToastService { get; set; }
|
||||
private IToastService ToastService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IUpgradeService UpgradeService { get; init; }
|
||||
private IUpgradeService UpgradeService { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppState.PropertyChanged -= AppState_PropertyChanged;
|
||||
@ -85,7 +86,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
CircuitConnection.MessageReceived += CircuitConnection_MessageReceived;
|
||||
}
|
||||
|
||||
private void AppState_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(AppState.DevicesFrameFocusedCardState) ||
|
||||
e.PropertyName == nameof(AppState.DevicesFrameFocusedDevice) ||
|
||||
@ -95,7 +96,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void CircuitConnection_MessageReceived(object sender, CircuitEvent e)
|
||||
private async void CircuitConnection_MessageReceived(object? sender, CircuitEvent e)
|
||||
{
|
||||
switch (e.EventName)
|
||||
{
|
||||
@ -106,7 +107,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
device.ID == Device?.ID)
|
||||
{
|
||||
Device = device;
|
||||
InvokeAsync(StateHasChanged);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -185,7 +186,7 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
DataService.UpdateDevice(Device.ID,
|
||||
await DataService.UpdateDevice(Device.ID,
|
||||
Device.Tags,
|
||||
Device.Alias,
|
||||
Device.DeviceGroupID,
|
||||
@ -302,7 +303,11 @@ public partial class DeviceCard : AuthComponentBase, IDisposable
|
||||
|
||||
private void ToggleIsSelected(ChangeEventArgs args)
|
||||
{
|
||||
var isSelected = (bool)args.Value;
|
||||
if (args.Value is not bool isSelected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
AppState.DevicesFrameSelectedDevices.Add(Device.ID);
|
||||
|
||||
@ -18,28 +18,28 @@ namespace Remotely.Server.Components.Devices;
|
||||
|
||||
public partial class Terminal : AuthComponentBase, IDisposable
|
||||
{
|
||||
private string _inputText;
|
||||
private string? _inputText;
|
||||
|
||||
private string _lastCompletionInput;
|
||||
private string? _lastCompletionInput;
|
||||
private int _lastCursorIndex;
|
||||
private ScriptingShell _shell;
|
||||
|
||||
private ElementReference _terminalInput;
|
||||
private string _terminalOpenClass;
|
||||
private string? _terminalOpenClass;
|
||||
private ElementReference _terminalWindow;
|
||||
|
||||
[Inject]
|
||||
private IClientAppState AppState { get; set; }
|
||||
private IClientAppState AppState { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ICircuitConnection CircuitConnection { get; set; }
|
||||
private ICircuitConnection CircuitConnection { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IDataService DataService { get; set; }
|
||||
private IDataService DataService { get; init; } = null!;
|
||||
|
||||
private string InputText
|
||||
{
|
||||
get => _inputText;
|
||||
get => _inputText ?? string.Empty;
|
||||
set
|
||||
{
|
||||
_inputText = value;
|
||||
@ -51,29 +51,29 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
}
|
||||
|
||||
[Inject]
|
||||
private IJsInterop JsInterop { get; set; }
|
||||
private IJsInterop JsInterop { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<Terminal> Logger { get; set; }
|
||||
private ILogger<Terminal> Logger { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private IModalService ModalService { get; set; }
|
||||
private IModalService ModalService { get; init; } = null!;
|
||||
|
||||
private EventCallback<SavedScript> RunQuickScript =>
|
||||
EventCallback.Factory.Create<SavedScript>(this, async script =>
|
||||
{
|
||||
var scriptRun = new ScriptRun()
|
||||
var scriptRun = new ScriptRun
|
||||
{
|
||||
OrganizationID = User.OrganizationID,
|
||||
RunAt = Time.Now,
|
||||
SavedScriptId = script.Id,
|
||||
RunOnNextConnect = false,
|
||||
Initiator = User.UserName,
|
||||
InputType = ScriptInputType.OneTimeScript
|
||||
InputType = ScriptInputType.OneTimeScript,
|
||||
Devices = DataService.GetDevices(AppState.DevicesFrameSelectedDevices)
|
||||
};
|
||||
|
||||
scriptRun.Devices = DataService.GetDevices(AppState.DevicesFrameSelectedDevices);
|
||||
|
||||
await DataService.AddScriptRun(scriptRun);
|
||||
|
||||
await CircuitConnection.RunScript(AppState.DevicesFrameSelectedDevices, script.Id, scriptRun.Id, ScriptInputType.OneTimeScript, false);
|
||||
@ -82,7 +82,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
});
|
||||
|
||||
[Inject]
|
||||
private IToastService ToastService { get; set; }
|
||||
private IToastService ToastService { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@ -130,7 +130,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void AppState_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
private void AppState_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(AppState.TerminalLines))
|
||||
{
|
||||
@ -139,7 +139,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private void CircuitConnection_MessageReceived(object sender, Models.CircuitEvent e)
|
||||
private async void CircuitConnection_MessageReceived(object? sender, Models.CircuitEvent e)
|
||||
{
|
||||
if (e.EventName == Models.CircuitEventName.PowerShellCompletions)
|
||||
{
|
||||
@ -149,7 +149,7 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
switch (intent)
|
||||
{
|
||||
case CompletionIntent.ShowAll:
|
||||
DisplayCompletions(completion.CompletionMatches);
|
||||
await DisplayCompletions(completion.CompletionMatches);
|
||||
break;
|
||||
case CompletionIntent.NextResult:
|
||||
ApplyCompletion(completion);
|
||||
@ -350,6 +350,11 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
if (User.UserOptions is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentText.Equals(User.UserOptions.CommandModeShortcutPSCore.ToLower()))
|
||||
{
|
||||
_shell = ScriptingShell.PSCore;
|
||||
|
||||
@ -14,25 +14,25 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private System.Timers.Timer _collapseTimer;
|
||||
private string _showClass;
|
||||
private System.Timers.Timer? _collapseTimer;
|
||||
private string? _showClass;
|
||||
private bool _isExpanded;
|
||||
|
||||
[Parameter]
|
||||
public string ButtonClass { get; set; }
|
||||
public string? ButtonClass { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ButtonContent { get; set; }
|
||||
public RenderFragment? ButtonContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string DropDownClass { get; set; }
|
||||
public string? DropDownClass { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string DropDownMenuClass { get; set; }
|
||||
public string? DropDownMenuClass { get; set; }
|
||||
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildListItems { get; set; }
|
||||
public RenderFragment? ChildListItems { get; set; }
|
||||
|
||||
private void ToggleShown()
|
||||
{
|
||||
|
||||
@ -8,14 +8,14 @@
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string ClassNames { get; set; }
|
||||
public string? ClassNames { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ButtonContent { get; set; }
|
||||
public RenderFragment? ButtonContent { get; set; }
|
||||
|
||||
|
||||
[Parameter]
|
||||
public Func<InputFileChangeEventArgs, Task> OnChanged { get; set; }
|
||||
public Func<InputFileChangeEventArgs, Task>? OnChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Multiple { get; set; }
|
||||
|
||||
@ -21,10 +21,10 @@
|
||||
public static string DeviceGroupsPropName => nameof(DeviceGroups);
|
||||
|
||||
[Parameter]
|
||||
public RemotelyUser EditUser { get; set; }
|
||||
public required RemotelyUser EditUser { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public DeviceGroup[] DeviceGroups { get; set; }
|
||||
public required DeviceGroup[] DeviceGroups { get; set; }
|
||||
|
||||
|
||||
private bool DoesGroupContainUser(DeviceGroup group)
|
||||
@ -34,7 +34,9 @@
|
||||
|
||||
private async Task GroupCheckChanged(ChangeEventArgs args, DeviceGroup group)
|
||||
{
|
||||
if ((bool)args.Value)
|
||||
if (!string.IsNullOrWhiteSpace(EditUser.UserName) &&
|
||||
args.Value is bool boolValue &&
|
||||
boolValue)
|
||||
{
|
||||
if (!DataService.AddUserToDeviceGroup(EditUser.OrganizationID, group.ID, EditUser.UserName, out var result))
|
||||
{
|
||||
|
||||
@ -11,13 +11,13 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" >
|
||||
@if (ModalService?.RenderBody is not null)
|
||||
@if (ModalService.RenderBody is not null)
|
||||
{
|
||||
@ModalService.RenderBody
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var line in ModalService?.Body ?? Array.Empty<string>())
|
||||
@foreach (var line in ModalService.Body ?? Array.Empty<string>())
|
||||
{
|
||||
<p>
|
||||
@line
|
||||
@ -38,8 +38,8 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string _showClass;
|
||||
private string _displayStyle;
|
||||
private string? _showClass;
|
||||
private string? _displayStyle;
|
||||
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<AlertBanner Message="@_alertMessage" OnClose="() => _alertMessage = null" />
|
||||
<AlertBanner Message="@_alertMessage" OnClose="() => _alertMessage = string.Empty" />
|
||||
</div>
|
||||
|
||||
<div class="mt-2 col-md-6 col-lg-4">
|
||||
|
||||
@ -8,13 +8,14 @@
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
public TabControl Parent { get; set; }
|
||||
public required TabControl Parent { get; init; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
[EditorRequired]
|
||||
public required string Name { get; set; }
|
||||
|
||||
private bool IsActive => Parent.ActiveTab == Name;
|
||||
|
||||
|
||||
@ -12,15 +12,15 @@
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public RenderFragment TabHeaders { get; set; }
|
||||
public RenderFragment? TabHeaders { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment TabContents { get; set; }
|
||||
public RenderFragment? TabContents { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string InitialActiveTab { get; set; }
|
||||
public string? InitialActiveTab { get; set; }
|
||||
|
||||
public string ActiveTab { get; set; }
|
||||
public string? ActiveTab { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
|
||||
@ -8,21 +8,21 @@
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
public TabControl Parent { get; set; }
|
||||
public TabControl? Parent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Action OnActivated { get; set; }
|
||||
public Action? OnActivated { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string NavigationUri { get; set; }
|
||||
public string? NavigationUri { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
private string ActiveClass => Parent.ActiveTab == Name ? "active" : "";
|
||||
private string ActiveClass => Parent?.ActiveTab == Name ? "active" : "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -46,7 +46,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
Parent.SetActiveTab(this);
|
||||
Parent?.SetActiveTab(this);
|
||||
StateHasChanged();
|
||||
OnActivated?.Invoke();
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
@inject IToastService ToastService
|
||||
|
||||
<div class="toast-harness">
|
||||
@foreach (var toast in ToastService?.Toasts)
|
||||
@foreach (var toast in ToastService.Toasts)
|
||||
{
|
||||
<div @key="toast.Guid" class="toast-message @toast.ClassString" style="animation-delay: @(toast.Expiration.TotalMilliseconds.ToString() + "ms"); @toast.StyleOverrides">
|
||||
@toast.Message
|
||||
|
||||
@ -9,35 +9,41 @@ namespace Remotely.Server.Components.TreeView;
|
||||
public partial class TreeView<T> : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public IEnumerable<T> DataSource { get; set; }
|
||||
[EditorRequired]
|
||||
public required IEnumerable<T> DataSource { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, List<T>> ChildItemSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, List<T>> ChildItemSelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> ItemHeaderSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, string> ItemHeaderSelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> KeySelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, string> KeySelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<T> ItemSelected { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string WrapperStyle { get; set; }
|
||||
public string? WrapperStyle { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ChildItemStyle { get; set; }
|
||||
public string? ChildItemStyle { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int IndentLevel { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, TreeItemType> ItemTypeSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, TreeItemType> ItemTypeSelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> ItemIconCssSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, string>? ItemIconCssSelector { get; set; }
|
||||
|
||||
public TreeViewItem<T> SelectedNode { get; set; }
|
||||
public TreeViewItem<T>? SelectedNode { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@ -9,34 +9,40 @@ namespace Remotely.Server.Components.TreeView;
|
||||
public partial class TreeViewItem<T> : ComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
public TreeView<T> ParentTree { get; set; }
|
||||
public required TreeView<T> ParentTree { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public T Source { get; set; }
|
||||
[EditorRequired]
|
||||
public required T Source { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, List<T>> ChildItemSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, List<T>> ChildItemSelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> HeaderSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, string> HeaderSelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> ItemIconCssSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, string> ItemIconCssSelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Style { get; set; }
|
||||
public string? Style { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int IndentLevel { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, TreeItemType> ItemTypeSelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, TreeItemType> ItemTypeSelector { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<T> ItemSelected { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> KeySelector { get; set; }
|
||||
[EditorRequired]
|
||||
public required Func<T, string> KeySelector { get; set; }
|
||||
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
|
||||
@ -10,9 +10,7 @@ using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Utilities;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -52,7 +50,14 @@ public class AgentHub : Hub
|
||||
{
|
||||
get
|
||||
{
|
||||
return Context.Items["Device"] as Device;
|
||||
if (Context.Items["Device"] is Device device)
|
||||
{
|
||||
return device;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Device not set.");
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
@ -64,7 +69,7 @@ public class AgentHub : Hub
|
||||
{
|
||||
if (_circuitManager.TryGetConnection(browserConnectionId, out var connection))
|
||||
{
|
||||
return connection.InvokeCircuitEvent(CircuitEventName.ChatReceived, Device.ID, Device.DeviceName, message, disconnected);
|
||||
return connection.InvokeCircuitEvent(CircuitEventName.ChatReceived, Device.ID, $"{Device.DeviceName}", message, disconnected);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -102,7 +107,7 @@ public class AgentHub : Hub
|
||||
{
|
||||
ip = ip.MapToIPv4();
|
||||
}
|
||||
device.PublicIP = ip?.ToString();
|
||||
device.PublicIP = $"{ip}";
|
||||
|
||||
if (CheckForDeviceBan(device.PublicIP))
|
||||
{
|
||||
@ -157,7 +162,7 @@ public class AgentHub : Hub
|
||||
{
|
||||
ip = ip.MapToIPv4();
|
||||
}
|
||||
device.PublicIP = ip?.ToString();
|
||||
device.PublicIP = $"{ip}";
|
||||
|
||||
if (CheckForDeviceBan(device.PublicIP))
|
||||
{
|
||||
@ -216,10 +221,10 @@ public class AgentHub : Hub
|
||||
|
||||
public string GetServerVerificationToken()
|
||||
{
|
||||
return Device.ServerVerificationToken;
|
||||
return $"{Device.ServerVerificationToken}";
|
||||
}
|
||||
|
||||
public override Task OnDisconnectedAsync(Exception exception)
|
||||
public override Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@ -28,7 +28,7 @@ namespace Remotely.Server.Hubs;
|
||||
public interface ICircuitConnection
|
||||
{
|
||||
event EventHandler<CircuitEvent>? MessageReceived;
|
||||
RemotelyUser? User { get; }
|
||||
RemotelyUser User { get; }
|
||||
|
||||
Task DeleteRemoteLogs(string deviceId);
|
||||
|
||||
|
||||
@ -2,6 +2,6 @@
|
||||
|
||||
public class ApiLogin
|
||||
{
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Password { get; set; }
|
||||
}
|
||||
|
||||
@ -4,8 +4,8 @@ namespace Remotely.Server.Models;
|
||||
|
||||
public class ModalButton
|
||||
{
|
||||
public string Class { get; set; }
|
||||
public string Text { get; set; }
|
||||
public string Class { get; init; } = string.Empty;
|
||||
public string Text { get; init; } = string.Empty;
|
||||
|
||||
public Action OnClick { get; set; }
|
||||
public required Action OnClick { get; init; }
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
public class RemoteControlRequest
|
||||
{
|
||||
public string DeviceID { get; set; }
|
||||
public string Email { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string? DeviceID { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Password { get; set; }
|
||||
}
|
||||
|
||||
@ -65,8 +65,8 @@
|
||||
</div>
|
||||
</div>
|
||||
@code {
|
||||
private string _alertMessage;
|
||||
private string _base64Icon;
|
||||
private string? _alertMessage;
|
||||
private string? _base64Icon;
|
||||
private InputModel _inputModel = new();
|
||||
|
||||
private class InputModel
|
||||
@ -74,13 +74,13 @@
|
||||
[StringLength(25)]
|
||||
[Required]
|
||||
[Display(Name = "Product Name")]
|
||||
public string ProductName { get; set; }
|
||||
public string ProductName { get; set; } = string.Empty;
|
||||
|
||||
public ColorPickerModel TitleForegroundColor { get; set; } = new();
|
||||
public ColorPickerModel TitleBackgroundColor { get; set; } = new();
|
||||
public ColorPickerModel TitleButtonColor { get; set; } = new();
|
||||
|
||||
public byte[] IconBytes { get; set; }
|
||||
public byte[] IconBytes { get; set; } = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
|
||||
@ -255,9 +255,9 @@ else
|
||||
<td>@scriptResult.TimeStamp</td>
|
||||
<td>@scriptResult.SenderUserName</td>
|
||||
<td>@scriptResult.RunTime</td>
|
||||
<td>@GetTrimmedText(scriptResult.ScriptInput, 25)</td>
|
||||
<td>@GetTrimmedText(scriptResult.StandardOutput, 25)</td>
|
||||
<td>@GetTrimmedText(scriptResult.ErrorOutput, 25)</td>
|
||||
<td>@GetTrimmedText($"{scriptResult.ScriptInput}", 25)</td>
|
||||
<td>@GetTrimmedText($"{scriptResult.StandardOutput}", 25)</td>
|
||||
<td>@GetTrimmedText($"{scriptResult.ErrorOutput}", 25)</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" @onclick="() => ShowFullScriptOutput(scriptResult)">Show Full</button>
|
||||
</td>
|
||||
|
||||
@ -13,17 +13,10 @@ namespace Remotely.Server.Pages;
|
||||
[IgnoreAntiforgeryToken]
|
||||
public class ErrorModel : PageModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
private readonly ILogger<ErrorModel> _logger;
|
||||
|
||||
public ErrorModel(ILogger<ErrorModel> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
|
||||
@ -21,7 +21,7 @@ public class GetSupportModel : PageModel
|
||||
public string? StatusMessage { get; set; }
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
public InputModel Input { get; set; } = new();
|
||||
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
@ -76,7 +76,7 @@ public class GetSupportModel : PageModel
|
||||
{
|
||||
[StringLength(150)]
|
||||
[Required]
|
||||
public required string Name { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public bool ChatResponseOk { get; set; }
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
private bool _showOnlyMyScripts = true;
|
||||
|
||||
[Parameter]
|
||||
public string ActiveTab { get; set; }
|
||||
public string? ActiveTab { get; set; }
|
||||
|
||||
public bool ShowOnlyMyScripts
|
||||
{
|
||||
@ -56,7 +56,7 @@
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_treeNodes?.Any() == true)
|
||||
if (_treeNodes.Any() == true)
|
||||
{
|
||||
return _treeNodes;
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ public class AppSettingsModel
|
||||
[Display(Name = "Max Organizations")]
|
||||
public int MaxOrganizationCount { get; set; }
|
||||
[Display(Name = "Message of the Day")]
|
||||
public string MessageOfTheDay { get; set; }
|
||||
public string? MessageOfTheDay { get; set; }
|
||||
|
||||
[Display(Name = "Redirect To HTTPS")]
|
||||
public bool RedirectToHttps { get; set; }
|
||||
@ -76,28 +76,28 @@ public class AppSettingsModel
|
||||
public bool Require2FA { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Display Name")]
|
||||
public string SmtpDisplayName { get; set; }
|
||||
public string? SmtpDisplayName { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Email")]
|
||||
[EmailAddress]
|
||||
public string SmtpEmail { get; set; }
|
||||
public string? SmtpEmail { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Host")]
|
||||
public string SmtpHost { get; set; }
|
||||
public string? SmtpHost { get; set; }
|
||||
[Display(Name = "SMTP Local Domain")]
|
||||
public string SmtpLocalDomain { get; set; }
|
||||
public string? SmtpLocalDomain { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Check Certificate Revocation")]
|
||||
public bool SmtpCheckCertificateRevocation { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Password")]
|
||||
public string SmtpPassword { get; set; }
|
||||
public string? SmtpPassword { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Port")]
|
||||
public int SmtpPort { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Username")]
|
||||
public string SmtpUserName { get; set; }
|
||||
public string? SmtpUserName { get; set; }
|
||||
|
||||
[Display(Name = "Theme")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
@ -116,72 +116,74 @@ public class AppSettingsModel
|
||||
public class ConnectionStringsModel
|
||||
{
|
||||
[Display(Name = "PostgreSQL")]
|
||||
public string PostgreSQL { get; set; }
|
||||
public string? PostgreSQL { get; set; }
|
||||
|
||||
[Display(Name = "SQLite")]
|
||||
public string SQLite { get; set; }
|
||||
public string? SQLite { get; set; }
|
||||
|
||||
[Display(Name = "SQL Server")]
|
||||
public string SQLServer { get; set; }
|
||||
public string? SQLServer { get; set; }
|
||||
}
|
||||
|
||||
public partial class ServerConfig : AuthComponentBase
|
||||
{
|
||||
private string _alertMessage;
|
||||
private string _bannedDeviceSelected;
|
||||
private string _bannedDeviceToAdd;
|
||||
private string? _alertMessage;
|
||||
private string? _bannedDeviceSelected;
|
||||
private string? _bannedDeviceToAdd;
|
||||
|
||||
private string _knownProxySelected;
|
||||
private string _knownProxyToAdd;
|
||||
private string? _knownProxySelected;
|
||||
private string? _knownProxyToAdd;
|
||||
|
||||
private bool _showMyOrgAdminsOnly = true;
|
||||
private bool _showAdminsOnly;
|
||||
|
||||
private string _trustedCorsOriginSelected;
|
||||
private string _trustedCorsOriginToAdd;
|
||||
private string? _trustedCorsOriginSelected;
|
||||
private string? _trustedCorsOriginToAdd;
|
||||
|
||||
private readonly List<RemotelyUser> _userList = new();
|
||||
|
||||
|
||||
[Inject]
|
||||
private IHubContext<AgentHub> AgentHubContext { get; set; }
|
||||
private IHubContext<AgentHub> AgentHubContext { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IConfiguration Configuration { get; set; }
|
||||
private IConfiguration Configuration { get; init; } = null!;
|
||||
|
||||
private ConnectionStringsModel ConnectionStrings { get; } = new();
|
||||
|
||||
[Inject]
|
||||
private IDataService DataService { get; set; }
|
||||
private IDataService DataService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IEmailSenderEx EmailSender { get; set; }
|
||||
private IEmailSenderEx EmailSender { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IWebHostEnvironment HostEnv { get; set; }
|
||||
private IWebHostEnvironment HostEnv { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<ServerConfig> Logger { get; set; }
|
||||
private ILogger<ServerConfig> Logger { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IAgentHubSessionCache ServiceSessionCache { get; init; }
|
||||
private IAgentHubSessionCache ServiceSessionCache { get; init; } = null!;
|
||||
|
||||
private AppSettingsModel Input { get; } = new();
|
||||
|
||||
[Inject]
|
||||
private IModalService ModalService { get; set; }
|
||||
private IModalService ModalService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IUpgradeService UpgradeService { get; init; }
|
||||
private IUpgradeService UpgradeService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ICircuitManager CircuitManager { get; set; }
|
||||
private ICircuitManager CircuitManager { get; init; } = null!;
|
||||
|
||||
private IEnumerable<string> OutdatedDevices => GetOutdatedDevices();
|
||||
|
||||
[Inject]
|
||||
private IToastService ToastService { get; set; }
|
||||
private IToastService ToastService { get; init; } = null!;
|
||||
|
||||
private int TotalDevices => DataService.GetTotalDevices();
|
||||
|
||||
private IEnumerable<RemotelyUser> UserList
|
||||
{
|
||||
get
|
||||
@ -338,6 +340,12 @@ public partial class ServerConfig : AuthComponentBase
|
||||
{
|
||||
await SaveInputToAppSettings();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(User.Email))
|
||||
{
|
||||
ToastService.ShowToast2("User email is not set.", Enums.ToastType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
var success = await EmailSender.SendEmailAsync(User.Email, "Remotely Test Email", "Congratulations! Your SMTP settings are working!", User.OrganizationID);
|
||||
if (success)
|
||||
{
|
||||
@ -359,19 +367,27 @@ public partial class ServerConfig : AuthComponentBase
|
||||
var devSettings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Development.json");
|
||||
var settings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.json");
|
||||
|
||||
if (HostEnv.IsProduction() && prodSettings.Exists)
|
||||
if (HostEnv.IsProduction()
|
||||
&& prodSettings.Exists &&
|
||||
!string.IsNullOrWhiteSpace(prodSettings.PhysicalPath))
|
||||
{
|
||||
savePath = prodSettings.PhysicalPath;
|
||||
}
|
||||
else if (HostEnv.IsStaging() && stagingSettings.Exists)
|
||||
else if (
|
||||
HostEnv.IsStaging() &&
|
||||
stagingSettings.Exists &&
|
||||
!string.IsNullOrWhiteSpace(stagingSettings.PhysicalPath))
|
||||
{
|
||||
savePath = stagingSettings.PhysicalPath;
|
||||
}
|
||||
else if (HostEnv.IsDevelopment() && devSettings.Exists)
|
||||
else if (
|
||||
HostEnv.IsDevelopment() &&
|
||||
devSettings.Exists &&
|
||||
!string.IsNullOrWhiteSpace(devSettings.PhysicalPath))
|
||||
{
|
||||
savePath = devSettings.PhysicalPath;
|
||||
}
|
||||
else if (settings.Exists)
|
||||
else if (settings.Exists && !string.IsNullOrWhiteSpace(settings.PhysicalPath))
|
||||
{
|
||||
savePath = settings.PhysicalPath;
|
||||
}
|
||||
@ -381,6 +397,10 @@ public partial class ServerConfig : AuthComponentBase
|
||||
}
|
||||
|
||||
var settingsJson = JsonSerializer.Deserialize<IDictionary<string, object>>(await System.IO.File.ReadAllTextAsync(savePath));
|
||||
if (settingsJson is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
settingsJson["ApplicationOptions"] = Input;
|
||||
settingsJson["ConnectionStrings"] = ConnectionStrings;
|
||||
|
||||
@ -393,7 +413,10 @@ public partial class ServerConfig : AuthComponentBase
|
||||
}
|
||||
private void SetIsServerAdmin(ChangeEventArgs ev, RemotelyUser user)
|
||||
{
|
||||
var isAdmin = (bool)ev.Value;
|
||||
if (ev.Value is not bool isAdmin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DataService.SetIsServerAdmin(user.Id, isAdmin, User.Id);
|
||||
ToastService.ShowToast("Server admins updated.");
|
||||
}
|
||||
@ -404,10 +427,13 @@ public partial class ServerConfig : AuthComponentBase
|
||||
{
|
||||
var outdatedDeviceNames = DataService
|
||||
.GetDevices(OutdatedDevices)
|
||||
.Select(x => x.DeviceName);
|
||||
.Select(x => $"{x.DeviceName}");
|
||||
|
||||
ModalService.ShowModal("Outdated Devices",
|
||||
(new[] { "Outdated Devices:" }).Concat(outdatedDeviceNames).ToArray());
|
||||
var body = (new[] { "Outdated Devices:" })
|
||||
.Concat(outdatedDeviceNames)
|
||||
.ToArray();
|
||||
|
||||
ModalService.ShowModal("Outdated Devices", body);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
|
||||
@code {
|
||||
private RemotelyUserOptions _options = new();
|
||||
private string _alertMessage;
|
||||
private string? _alertMessage;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@ -119,7 +119,7 @@
|
||||
_options.CommandModeShortcutWinPS = "/" + _options.CommandModeShortcutWinPS;
|
||||
}
|
||||
|
||||
DataService.UpdateUserOptions(User.UserName, _options);
|
||||
DataService.UpdateUserOptions(UserName, _options);
|
||||
|
||||
_alertMessage = "Options saved";
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using Remotely.Shared.Models;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Remotely.Server.Services;
|
||||
@ -13,9 +14,9 @@ public interface IAgentHubSessionCache
|
||||
|
||||
ICollection<Device> GetAllDevices();
|
||||
IEnumerable<string> GetConnectionIdsByDeviceIds(IEnumerable<string> deviceIds);
|
||||
bool TryGetByDeviceId(string deviceId, out Device device);
|
||||
bool TryGetConnectionId(string deviceId, out string serviceConnectionId);
|
||||
bool TryRemoveByConnectionId(string connectionId, out Device device);
|
||||
bool TryGetByDeviceId(string deviceId, [NotNullWhen(true)] out Device? device);
|
||||
bool TryGetConnectionId(string deviceId, [NotNullWhen(true)] out string? serviceConnectionId);
|
||||
bool TryRemoveByConnectionId(string connectionId, [NotNullWhen(true)] out Device? device);
|
||||
}
|
||||
|
||||
public class AgentHubSessionCache : IAgentHubSessionCache
|
||||
@ -55,23 +56,23 @@ public class AgentHubSessionCache : IAgentHubSessionCache
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetByDeviceId(string deviceId, out Device device)
|
||||
public bool TryGetByDeviceId(string deviceId, [NotNullWhen(true)] out Device? device)
|
||||
{
|
||||
if (_deviceIdToConnectionIdLookup.TryGetValue(deviceId, out var connectionId) &&
|
||||
_connectionIdToDeviceLookup.TryGetValue(connectionId, out device))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
device = Device.Empty;
|
||||
device = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetConnectionId(string deviceId, out string serviceConnectionId)
|
||||
public bool TryGetConnectionId(string deviceId, [NotNullWhen(true)] out string? serviceConnectionId)
|
||||
{
|
||||
return _deviceIdToConnectionIdLookup.TryGetValue(deviceId, out serviceConnectionId);
|
||||
}
|
||||
|
||||
public bool TryRemoveByConnectionId(string connectionId, out Device device)
|
||||
public bool TryRemoveByConnectionId(string connectionId, [NotNullWhen(true)] out Device? device)
|
||||
{
|
||||
if (_connectionIdToDeviceLookup.TryRemove(connectionId, out var lookupResult))
|
||||
{
|
||||
@ -80,7 +81,7 @@ public class AgentHubSessionCache : IAgentHubSessionCache
|
||||
return true;
|
||||
}
|
||||
|
||||
device = Device.Empty;
|
||||
device = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,21 +59,21 @@ public class ApplicationConfig : IApplicationConfig
|
||||
public string[] KnownProxies => _config.GetSection("ApplicationOptions:KnownProxies").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public int MaxConcurrentUpdates => int.TryParse(_config["ApplicationOptions:MaxConcurrentUpdates"], out var result) ? result : 10;
|
||||
public int MaxOrganizationCount => int.TryParse(_config["ApplicationOptions:MaxOrganizationCount"], out var result) ? result : 1;
|
||||
public string MessageOfTheDay => _config["ApplicationOptions:MessageOfTheDay"];
|
||||
public string MessageOfTheDay => _config["ApplicationOptions:MessageOfTheDay"] ?? string.Empty;
|
||||
public bool RedirectToHttps => bool.TryParse(_config["ApplicationOptions:RedirectToHttps"], out var result) && result;
|
||||
public bool RemoteControlNotifyUser => bool.TryParse(_config["ApplicationOptions:RemoteControlNotifyUser"], out var result) && result;
|
||||
public bool RemoteControlRequiresAuthentication => bool.TryParse(_config["ApplicationOptions:RemoteControlRequiresAuthentication"], out var result) && result;
|
||||
public int RemoteControlSessionLimit => int.TryParse(_config["ApplicationOptions:RemoteControlSessionLimit"], out var result) ? result : 3;
|
||||
public bool Require2FA => bool.TryParse(_config["ApplicationOptions:Require2FA"], out var result) && result;
|
||||
public string ServerUrl => _config["ApplicationOptions:ServerUrl"];
|
||||
public string ServerUrl => _config["ApplicationOptions:ServerUrl"] ?? string.Empty;
|
||||
public bool SmtpCheckCertificateRevocation => !bool.TryParse(_config["ApplicationOptions:SmtpCheckCertificateRevocation"], out var result) || result;
|
||||
public string SmtpDisplayName => _config["ApplicationOptions:SmtpDisplayName"];
|
||||
public string SmtpEmail => _config["ApplicationOptions:SmtpEmail"];
|
||||
public string SmtpHost => _config["ApplicationOptions:SmtpHost"];
|
||||
public string SmtpLocalDomain => _config["ApplicationOptions:SmtpLocalDomain"];
|
||||
public string SmtpPassword => _config["ApplicationOptions:SmtpPassword"];
|
||||
public string SmtpDisplayName => _config["ApplicationOptions:SmtpDisplayName"] ?? string.Empty;
|
||||
public string SmtpEmail => _config["ApplicationOptions:SmtpEmail"] ?? string.Empty;
|
||||
public string SmtpHost => _config["ApplicationOptions:SmtpHost"] ?? string.Empty;
|
||||
public string SmtpLocalDomain => _config["ApplicationOptions:SmtpLocalDomain"] ?? string.Empty;
|
||||
public string SmtpPassword => _config["ApplicationOptions:SmtpPassword"] ?? string.Empty;
|
||||
public int SmtpPort => int.TryParse(_config["ApplicationOptions:SmtpPort"], out var result) ? result : 25;
|
||||
public string SmtpUserName => _config["ApplicationOptions:SmtpUserName"];
|
||||
public string SmtpUserName => _config["ApplicationOptions:SmtpUserName"] ?? string.Empty;
|
||||
public Theme Theme => Enum.TryParse<Theme>(_config["ApplicationOptions:Theme"], out var result) ? result : Theme.Dark;
|
||||
public string[] TrustedCorsOrigins => _config.GetSection("ApplicationOptions:TrustedCorsOrigins").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public bool UseHsts => bool.TryParse(_config["ApplicationOptions:UseHsts"], out var result) && result;
|
||||
|
||||
@ -14,7 +14,7 @@ public interface IClientAppState : INotifyPropertyChanged, IInvokePropertyChange
|
||||
{
|
||||
ConcurrentList<ChatSession> DevicesFrameChatSessions { get; }
|
||||
DeviceCardState DevicesFrameFocusedCardState { get; set; }
|
||||
string DevicesFrameFocusedDevice { get; set; }
|
||||
string? DevicesFrameFocusedDevice { get; set; }
|
||||
ConcurrentList<string> DevicesFrameSelectedDevices { get; }
|
||||
ConcurrentQueue<TerminalLineItem> TerminalLines { get; }
|
||||
|
||||
@ -49,7 +49,7 @@ public class ClientAppState : ViewModelBase, IClientAppState
|
||||
set => Set(value);
|
||||
}
|
||||
|
||||
public string DevicesFrameFocusedDevice
|
||||
public string? DevicesFrameFocusedDevice
|
||||
{
|
||||
get => Get<string>();
|
||||
set => Set(value);
|
||||
|
||||
@ -352,7 +352,7 @@ public class DataService : IDataService
|
||||
|
||||
var inviteLink = new InviteLink()
|
||||
{
|
||||
InvitedUser = invite.InvitedUser.ToLower(),
|
||||
InvitedUser = invite.InvitedUser?.ToLower(),
|
||||
DateSent = DateTimeOffset.Now,
|
||||
IsAdmin = invite.IsAdmin,
|
||||
Organization = organization,
|
||||
|
||||
@ -13,8 +13,8 @@ namespace Remotely.Server.Services;
|
||||
|
||||
public interface IEmailSenderEx
|
||||
{
|
||||
Task<bool> SendEmailAsync(string email, string replyTo, string subject, string htmlMessage, string organizationID = null);
|
||||
Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null);
|
||||
Task<bool> SendEmailAsync(string email, string replyTo, string subject, string htmlMessage, string? organizationID = null);
|
||||
Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string? organizationID = null);
|
||||
}
|
||||
|
||||
public class EmailSender : IEmailSender
|
||||
@ -44,7 +44,12 @@ public class EmailSenderEx : IEmailSenderEx
|
||||
_appConfig = appConfig;
|
||||
_logger = logger;
|
||||
}
|
||||
public async Task<bool> SendEmailAsync(string toEmail, string replyTo, string subject, string htmlMessage, string organizationID = null)
|
||||
public async Task<bool> SendEmailAsync(
|
||||
string toEmail,
|
||||
string replyTo,
|
||||
string subject,
|
||||
string htmlMessage,
|
||||
string? organizationID = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -89,7 +94,7 @@ public class EmailSenderEx : IEmailSenderEx
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null)
|
||||
public Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string? organizationID = null)
|
||||
{
|
||||
return SendEmailAsync(email, _appConfig.SmtpEmail, subject, htmlMessage, organizationID);
|
||||
}
|
||||
|
||||
@ -11,24 +11,25 @@ public interface IModalService
|
||||
{
|
||||
event EventHandler ModalShown;
|
||||
List<ModalButton> Buttons { get; }
|
||||
string[] Body { get; }
|
||||
RenderFragment RenderBody { get; }
|
||||
string[]? Body { get; }
|
||||
RenderFragment? RenderBody { get; }
|
||||
string Title { get; }
|
||||
Task ShowModal(string title, string[] body, ModalButton[] buttons = null);
|
||||
Task ShowModal(string title, RenderFragment body, ModalButton[] buttons = null);
|
||||
Task ShowModal(string title, string[] body, ModalButton[]? buttons = null);
|
||||
Task ShowModal(string title, RenderFragment body, ModalButton[]? buttons = null);
|
||||
}
|
||||
|
||||
public class ModalService : IModalService
|
||||
{
|
||||
private readonly SemaphoreSlim _modalLock = new(1, 1);
|
||||
|
||||
public event EventHandler ModalShown;
|
||||
public event EventHandler? ModalShown;
|
||||
|
||||
public List<ModalButton> Buttons { get; } = new List<ModalButton>();
|
||||
public string[] Body { get; private set; }
|
||||
public RenderFragment RenderBody { get; private set; }
|
||||
public string[]? Body { get; private set; }
|
||||
public RenderFragment? RenderBody { get; private set; }
|
||||
public bool ShowInput { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public async Task ShowModal(string title, string[] body, ModalButton[] buttons = null)
|
||||
public string Title { get; private set; } = string.Empty;
|
||||
public async Task ShowModal(string title, string[] body, ModalButton[]? buttons = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -41,7 +42,7 @@ public class ModalService : IModalService
|
||||
{
|
||||
Buttons.AddRange(buttons);
|
||||
}
|
||||
ModalShown?.Invoke(this, null);
|
||||
ModalShown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -49,7 +50,7 @@ public class ModalService : IModalService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ShowModal(string title, RenderFragment body, ModalButton[] buttons = null)
|
||||
public async Task ShowModal(string title, RenderFragment body, ModalButton[]? buttons = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -62,7 +63,7 @@ public class ModalService : IModalService
|
||||
{
|
||||
Buttons.AddRange(buttons);
|
||||
}
|
||||
ModalShown?.Invoke(this, null);
|
||||
ModalShown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@ -30,7 +30,8 @@ public class OtpProvider : IOtpProvider
|
||||
|
||||
public bool OtpMatchesDevice(string otp, string deviceId)
|
||||
{
|
||||
if (_otpCache.TryGetValue(otp, out string cachedDevice) &&
|
||||
if (_otpCache.TryGetValue(otp, out var cachedItem) &&
|
||||
cachedItem is string cachedDevice &&
|
||||
cachedDevice == deviceId)
|
||||
{
|
||||
return true;
|
||||
|
||||
@ -21,8 +21,8 @@ public class ScriptScheduler : IHostedService, IDisposable
|
||||
TimeSpan.FromSeconds(30) :
|
||||
TimeSpan.FromMinutes(10);
|
||||
|
||||
private IServiceProvider _serviceProvider;
|
||||
private System.Timers.Timer _schedulerTimer;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private System.Timers.Timer? _schedulerTimer;
|
||||
|
||||
|
||||
public ScriptScheduler(IServiceProvider serviceProvider)
|
||||
@ -40,7 +40,7 @@ public class ScriptScheduler : IHostedService, IDisposable
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_schedulerTimer?.Dispose();
|
||||
_schedulerTimer = new System.Timers.Timer(_timerInterval.TotalMilliseconds);
|
||||
_schedulerTimer = new System.Timers.Timer(_timerInterval);
|
||||
_schedulerTimer.Elapsed += SchedulerTimer_Elapsed;
|
||||
_schedulerTimer.Start();
|
||||
return Task.CompletedTask;
|
||||
@ -52,7 +52,7 @@ public class ScriptScheduler : IHostedService, IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void SchedulerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
private void SchedulerTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
_ = DispatchScriptRuns();
|
||||
}
|
||||
@ -63,13 +63,14 @@ public class ScriptScheduler : IHostedService, IDisposable
|
||||
var scriptScheduleDispatcher = scope.ServiceProvider.GetRequiredService<IScriptScheduleDispatcher>();
|
||||
var logger = scope.ServiceProvider.GetRequiredService<ILogger<ScriptScheduler>>();
|
||||
|
||||
if (!await _dispatchLock.WaitAsync(0))
|
||||
{
|
||||
logger.LogWarning("Script schedule dispatcher is already running. Returning.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!await _dispatchLock.WaitAsync(0))
|
||||
{
|
||||
logger.LogWarning("Script schedule dispatcher is already running. Returning.");
|
||||
return;
|
||||
}
|
||||
|
||||
await scriptScheduleDispatcher.DispatchPendingScriptRuns();
|
||||
}
|
||||
|
||||
@ -14,25 +14,25 @@ public interface IToastService
|
||||
void ShowToast(
|
||||
string message,
|
||||
int expirationMillisecond = 3000,
|
||||
string classString = null,
|
||||
string styleOverrides = null);
|
||||
string classString = "",
|
||||
string styleOverrides = "");
|
||||
|
||||
void ShowToast2(
|
||||
string message,
|
||||
ToastType toastType = ToastType.Info,
|
||||
int expirationMillisecond = 3000,
|
||||
string styleOverrides = null);
|
||||
string styleOverrides = "");
|
||||
}
|
||||
|
||||
public class ToastService : IToastService
|
||||
{
|
||||
public event EventHandler OnToastsChanged;
|
||||
public event EventHandler? OnToastsChanged;
|
||||
public ConcurrentList<Toast> Toasts { get; } = new();
|
||||
|
||||
public void ShowToast(string message,
|
||||
int expirationMillisecond = 3000,
|
||||
string classString = null,
|
||||
string styleOverrides = null)
|
||||
string classString = "",
|
||||
string styleOverrides = "")
|
||||
{
|
||||
|
||||
if (string.IsNullOrWhiteSpace(classString))
|
||||
@ -57,7 +57,7 @@ public class ToastService : IToastService
|
||||
removeToastTimer.Elapsed += (s, e) =>
|
||||
{
|
||||
Toasts.Remove(toastModel);
|
||||
OnToastsChanged?.Invoke(this, null);
|
||||
OnToastsChanged?.Invoke(this, EventArgs.Empty);
|
||||
removeToastTimer.Dispose();
|
||||
};
|
||||
removeToastTimer.Start();
|
||||
@ -67,7 +67,7 @@ public class ToastService : IToastService
|
||||
string message,
|
||||
ToastType toastType,
|
||||
int expirationMillisecond = 3000,
|
||||
string styleOverrides = null)
|
||||
string styleOverrides = "")
|
||||
{
|
||||
var classString = toastType switch
|
||||
{
|
||||
|
||||
@ -20,7 +20,7 @@ public class UpgradeService : IUpgradeService
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<UpgradeService> _logger;
|
||||
private Version _currentVersion;
|
||||
private Version? _currentVersion;
|
||||
|
||||
public UpgradeService(IHttpClientFactory httpClientFactory, ILogger<UpgradeService> logger)
|
||||
{
|
||||
@ -59,10 +59,22 @@ public class UpgradeService : IUpgradeService
|
||||
{
|
||||
using var client = _httpClientFactory.CreateClient();
|
||||
var response = await client.GetAsync("https://github.com/immense/Remotely/releases/latest");
|
||||
var versionString = response.RequestMessage.RequestUri.ToString().Split("/").Last()[1..];
|
||||
var versionTag = $"{response.RequestMessage?.RequestUri}".Split("/").LastOrDefault();
|
||||
if (string.IsNullOrWhiteSpace(versionTag))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var versionString = versionTag[1..];
|
||||
var remoteVersion = Version.Parse(versionString);
|
||||
|
||||
var filePath = Directory.GetFiles(Directory.GetCurrentDirectory(), "Remotely_Server.dll", SearchOption.AllDirectories).First();
|
||||
var localVersion = Version.Parse(System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath).FileVersion);
|
||||
var fileVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath).FileVersion;
|
||||
if (string.IsNullOrWhiteSpace(fileVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var localVersion = Version.Parse(fileVersion);
|
||||
|
||||
if (remoteVersion > localVersion)
|
||||
{
|
||||
return true;
|
||||
|
||||
@ -16,7 +16,7 @@ public class ConnectionInfo
|
||||
}
|
||||
set
|
||||
{
|
||||
_host = value?.Trim()?.TrimEnd('/') ?? string.Empty;
|
||||
_host = value?.Trim()?.TrimEnd('/');
|
||||
}
|
||||
}
|
||||
public string? OrganizationID { get; set; }
|
||||
|
||||
@ -11,7 +11,6 @@ namespace Remotely.Shared.Models;
|
||||
|
||||
public class Device
|
||||
{
|
||||
public static Device Empty { get; } = new();
|
||||
|
||||
[Sortable]
|
||||
[Display(Name = "Agent Version")]
|
||||
|
||||
@ -11,7 +11,7 @@ public class PwshCommandCompletion
|
||||
public int CurrentMatchIndex { get; set; }
|
||||
public int ReplacementIndex { get; set; }
|
||||
public int ReplacementLength { get; set; }
|
||||
public List<PwshCompletionResult> CompletionMatches { get; set; }
|
||||
public List<PwshCompletionResult> CompletionMatches { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
@ -26,13 +26,13 @@ public class PwshCompletionResult
|
||||
ToolTip = toolTip;
|
||||
}
|
||||
|
||||
public string CompletionText { get; set; }
|
||||
public string CompletionText { get; set; } = string.Empty;
|
||||
|
||||
public string ListItemText { get; set; }
|
||||
public string ListItemText { get; set; } = string.Empty;
|
||||
|
||||
public PwshCompletionResultType ResultType { get; set; }
|
||||
|
||||
public string ToolTip { get; set; }
|
||||
public string ToolTip { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public enum PwshCompletionResultType
|
||||
|
||||
@ -7,7 +7,7 @@ public class RemotelyUserOptions
|
||||
{
|
||||
[Display(Name = "Display Name")]
|
||||
[StringLength(100)]
|
||||
public string DisplayName { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
|
||||
[Display(Name = "PS Core Shortcut")]
|
||||
[StringLength(10)]
|
||||
|
||||
@ -32,9 +32,9 @@ public class ProcessInvoker : IProcessInvoker
|
||||
};
|
||||
|
||||
var proc = Process.Start(psi);
|
||||
proc.WaitForExit();
|
||||
proc?.WaitForExit();
|
||||
|
||||
return proc.StandardOutput.ReadToEnd();
|
||||
return proc?.StandardOutput.ReadToEnd() ?? string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@ -34,7 +34,7 @@ public static class AppVersionHelper
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetFileVersion(string filePath, out string version)
|
||||
private static bool TryGetFileVersion(string? filePath, out string version)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@ -28,7 +28,7 @@ public static class ConsoleHelper
|
||||
Console.Write("Enter Response: ");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
return Console.ReadLine().Trim();
|
||||
return Console.ReadLine()?.Trim() ?? string.Empty;
|
||||
}
|
||||
|
||||
public static string ReadLine(string prompt, ConsoleColor promptColor = ConsoleColor.Cyan, string subprompt = "")
|
||||
@ -50,7 +50,7 @@ public static class ConsoleHelper
|
||||
var response = Console.ReadLine();
|
||||
Console.WriteLine();
|
||||
|
||||
return response;
|
||||
return response ?? string.Empty;
|
||||
}
|
||||
|
||||
public static bool TryParseBoolLike(string value, out bool result)
|
||||
|
||||
@ -15,7 +15,7 @@ namespace Remotely.Shared.Utilities;
|
||||
[Obsolete("Please use ILogger<T> via dependency injection.")]
|
||||
public static class Logger
|
||||
{
|
||||
private static string _logDir;
|
||||
private static string? _logDir;
|
||||
|
||||
private static string LogDir
|
||||
{
|
||||
@ -130,7 +130,7 @@ public static class Logger
|
||||
|
||||
while (exception != null)
|
||||
{
|
||||
File.AppendAllText(LogPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[{eventType}]\t[{callerName}]\t{exception?.Message}\t{exception?.StackTrace}\t{exception?.Source}{Environment.NewLine}");
|
||||
File.AppendAllText(LogPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[{eventType}]\t[{callerName}]\t{exception.Message}\t{exception.StackTrace}\t{exception.Source}{Environment.NewLine}");
|
||||
Console.WriteLine(exception.Message);
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ namespace Remotely.Shared.ViewModels;
|
||||
public class ChatHistoryItem
|
||||
{
|
||||
public ChatHistoryItemOrigin Origin { get; init; }
|
||||
public string Message { get; init; }
|
||||
public string? Message { get; init; }
|
||||
|
||||
public DateTimeOffset Timestamp { get; init; } = DateTime.Now;
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@ namespace Remotely.Shared.ViewModels;
|
||||
public class ChatSession
|
||||
{
|
||||
public ConcurrentList<ChatHistoryItem> ChatHistory { get; } = new();
|
||||
public string DeviceId { get; set; }
|
||||
public string DeviceName { get; set; }
|
||||
public string? DeviceId { get; set; }
|
||||
public string? DeviceName { get; set; }
|
||||
public string ExpandedClass => IsExpanded ? "expanded" : "";
|
||||
public bool IsExpanded { get; set; }
|
||||
|
||||
|
||||
@ -5,9 +5,9 @@ namespace Remotely.Shared.ViewModels;
|
||||
|
||||
public class InviteViewModel
|
||||
{
|
||||
public string ID { get; set; }
|
||||
public string? ID { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
public DateTimeOffset DateSent { get; set; }
|
||||
[EmailAddress]
|
||||
public string InvitedUser { get; set; }
|
||||
public string? InvitedUser { get; set; }
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
public class OrganizationUser
|
||||
{
|
||||
public string ID { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string? ID { get; set; }
|
||||
public string? UserName { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ namespace Remotely.Shared.ViewModels;
|
||||
public class TerminalLineItem
|
||||
{
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
public string Text { get; set; }
|
||||
public string ClassName { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string? Text { get; set; }
|
||||
public string? ClassName { get; set; }
|
||||
public string? Title { get; set; }
|
||||
}
|
||||
|
||||
@ -1,24 +1,32 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Remotely.Tests.LoadTester;
|
||||
|
||||
public class CommandLineParser
|
||||
{
|
||||
private static Dictionary<string, string> commandLineArgs;
|
||||
private static Dictionary<string, string>? _commandLineArgs;
|
||||
|
||||
public static Dictionary<string, string> CommandLineArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (commandLineArgs is null)
|
||||
if (_commandLineArgs is null)
|
||||
{
|
||||
commandLineArgs = new Dictionary<string, string>();
|
||||
_commandLineArgs = new Dictionary<string, string>();
|
||||
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
if (args?.Any() != true)
|
||||
{
|
||||
return _commandLineArgs;
|
||||
}
|
||||
|
||||
for (var i = 1; i < args.Length; i += 2)
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = args?[i];
|
||||
var key = args[i];
|
||||
if (key != null)
|
||||
{
|
||||
if (!key.Contains("-"))
|
||||
@ -29,14 +37,14 @@ public class CommandLineParser
|
||||
|
||||
key = key.Trim().Replace("-", "").ToLower();
|
||||
|
||||
commandLineArgs.Add(key, args[i + 1]);
|
||||
_commandLineArgs.Add(key, args[i + 1]);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
}
|
||||
}
|
||||
return commandLineArgs;
|
||||
return _commandLineArgs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,12 +17,12 @@ internal class Program
|
||||
{
|
||||
private static readonly double _heartbeatMs = TimeSpan.FromMinutes(1).TotalMilliseconds;
|
||||
private static int _agentCount;
|
||||
private static string _organizationId;
|
||||
private static string _serverurl;
|
||||
private static Mock<ICpuUtilizationSampler> _cpuSampler;
|
||||
private static Mock<ILogger<DeviceInfoGeneratorWin>> _logger;
|
||||
private static DeviceInfoGeneratorWin _deviceInfo;
|
||||
private static Stopwatch _stopwatch;
|
||||
private static string? _organizationId;
|
||||
private static string? _serverurl;
|
||||
private static Mock<ICpuUtilizationSampler>? _cpuSampler;
|
||||
private static Mock<ILogger<DeviceInfoGeneratorWin>>? _logger;
|
||||
private static DeviceInfoGeneratorWin? _deviceInfo;
|
||||
private static Stopwatch? _stopwatch;
|
||||
private static int _connectedCount;
|
||||
|
||||
private static void Main(string[] args)
|
||||
@ -96,7 +96,7 @@ internal class Program
|
||||
Console.WriteLine($"Connecting device number {i}");
|
||||
await hubConnection.StartAsync();
|
||||
|
||||
var device = await _deviceInfo.CreateDevice(deviceId, _organizationId);
|
||||
var device = await _deviceInfo!.CreateDevice(deviceId, _organizationId!);
|
||||
device.DeviceName = "TestDevice-" + Guid.NewGuid();
|
||||
|
||||
var result = await hubConnection.InvokeAsync<bool>("DeviceCameOnline", device);
|
||||
@ -116,7 +116,7 @@ internal class Program
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentInfo = await _deviceInfo.CreateDevice(device.ID, _organizationId);
|
||||
var currentInfo = await _deviceInfo.CreateDevice(device.ID, _organizationId!);
|
||||
currentInfo.DeviceName = device.DeviceName;
|
||||
await hubConnection.SendAsync("DeviceHeartbeat", currentInfo);
|
||||
}
|
||||
@ -130,7 +130,7 @@ internal class Program
|
||||
Interlocked.Increment(ref _connectedCount);
|
||||
if (_connectedCount == _agentCount)
|
||||
{
|
||||
Console.WriteLine($"Finished connecting all devices. Elapsed: {_stopwatch.Elapsed}");
|
||||
Console.WriteLine($"Finished connecting all devices. Elapsed: {_stopwatch!.Elapsed}");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@ -24,9 +24,8 @@ namespace Remotely.Tests;
|
||||
[TestClass]
|
||||
public class AgentHubTests
|
||||
{
|
||||
private TestData _testData;
|
||||
|
||||
public IDataService DataService { get; private set; }
|
||||
private TestData _testData = null!;
|
||||
private IDataService _dataService = null!;
|
||||
|
||||
[TestMethod]
|
||||
[DoNotParallelize]
|
||||
@ -42,10 +41,10 @@ public class AgentHubTests
|
||||
var serviceSessionCache = new Mock<IAgentHubSessionCache>();
|
||||
var logger = new Mock<ILogger<AgentHub>>();
|
||||
|
||||
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Org1Device1.DeviceName });
|
||||
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { $"{_testData.Org1Device1.DeviceName}" });
|
||||
|
||||
var hub = new AgentHub(
|
||||
DataService,
|
||||
_dataService,
|
||||
appConfig.Object,
|
||||
serviceSessionCache.Object,
|
||||
viewerHub.Object,
|
||||
@ -82,7 +81,7 @@ public class AgentHubTests
|
||||
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Org1Device1.ID });
|
||||
|
||||
var hub = new AgentHub(
|
||||
DataService,
|
||||
_dataService,
|
||||
appConfig.Object,
|
||||
serviceSessionCache.Object,
|
||||
viewerHub.Object,
|
||||
@ -111,18 +110,17 @@ public class AgentHubTests
|
||||
{
|
||||
_testData = new TestData();
|
||||
await _testData.Init();
|
||||
DataService = IoCActivator.ServiceProvider.GetRequiredService<IDataService>();
|
||||
_dataService = IoCActivator.ServiceProvider.GetRequiredService<IDataService>();
|
||||
}
|
||||
|
||||
private class CallerContext : HubCallerContext
|
||||
{
|
||||
public override string ConnectionId => "test-id";
|
||||
|
||||
public override string UserIdentifier => null;
|
||||
public override string? UserIdentifier => null;
|
||||
public override ClaimsPrincipal? User => null;
|
||||
|
||||
public override ClaimsPrincipal User => null;
|
||||
|
||||
public override IDictionary<object, object> Items { get; } = new Dictionary<object, object>();
|
||||
public override IDictionary<object, object?> Items { get; } = new Dictionary<object, object?>();
|
||||
|
||||
public override IFeatureCollection Features { get; } = new FeatureCollection();
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ public class CircuitConnectionTests
|
||||
var addToGroupResult = _dataService.AddUserToDeviceGroup(
|
||||
_testData.Org1Id,
|
||||
_testData.Org1Group1.ID,
|
||||
_testData.Org1User1.UserName,
|
||||
$"{_testData.Org1User1.UserName}",
|
||||
out _);
|
||||
|
||||
var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto());
|
||||
@ -199,7 +199,7 @@ public class CircuitConnectionTests
|
||||
var addToGroupResult = _dataService.AddUserToDeviceGroup(
|
||||
_testData.Org1Id,
|
||||
_testData.Org1Group1.ID,
|
||||
_testData.Org1User1.UserName,
|
||||
$"{_testData.Org1User1.UserName}",
|
||||
out _);
|
||||
|
||||
var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto());
|
||||
@ -278,7 +278,7 @@ public class CircuitConnectionTests
|
||||
var addToGroupResult = _dataService.AddUserToDeviceGroup(
|
||||
_testData.Org1Id,
|
||||
_testData.Org1Group1.ID,
|
||||
_testData.Org1User1.UserName,
|
||||
$"{_testData.Org1User1.UserName}",
|
||||
out _);
|
||||
|
||||
var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto());
|
||||
@ -346,7 +346,7 @@ public class CircuitConnectionTests
|
||||
var addToGroupResult = _dataService.AddUserToDeviceGroup(
|
||||
_testData.Org1Id,
|
||||
_testData.Org1Group1.ID,
|
||||
_testData.Org1User1.UserName,
|
||||
$"{_testData.Org1User1.UserName}",
|
||||
out _);
|
||||
|
||||
var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto());
|
||||
@ -417,7 +417,7 @@ public class CircuitConnectionTests
|
||||
var addToGroupResult = _dataService.AddUserToDeviceGroup(
|
||||
_testData.Org1Id,
|
||||
_testData.Org1Group1.ID,
|
||||
_testData.Org1User1.UserName,
|
||||
$"{_testData.Org1User1.UserName}",
|
||||
out _);
|
||||
|
||||
var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto());
|
||||
|
||||
@ -21,25 +21,25 @@ namespace Remotely.Tests;
|
||||
[TestClass]
|
||||
public class IoCActivator
|
||||
{
|
||||
public static IServiceProvider ServiceProvider { get; set; }
|
||||
private static IWebHostBuilder builder;
|
||||
public static IServiceProvider ServiceProvider { get; set; } = null!;
|
||||
private static IWebHostBuilder? _builder;
|
||||
|
||||
public static void Activate()
|
||||
{
|
||||
if (builder is null)
|
||||
if (_builder is null)
|
||||
{
|
||||
builder = WebHost.CreateDefaultBuilder()
|
||||
_builder = WebHost.CreateDefaultBuilder()
|
||||
.UseStartup<Startup>()
|
||||
.CaptureStartupErrors(true)
|
||||
.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
config.AddInMemoryCollection(new Dictionary<string, string>()
|
||||
config.AddInMemoryCollection(new Dictionary<string, string?>()
|
||||
{
|
||||
["ApplicationOptions:DBProvider"] = "InMemory"
|
||||
});
|
||||
});
|
||||
|
||||
builder.Build();
|
||||
_builder.Build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,14 +19,14 @@ namespace Remotely.Tests;
|
||||
[TestClass]
|
||||
public class ScriptScheduleDispatcherTests
|
||||
{
|
||||
private ScriptSchedule _schedule1;
|
||||
private Mock<IDataService> _dataService;
|
||||
private Mock<ICircuitConnection> _circuitConnection;
|
||||
private Mock<IAgentHubSessionCache> _serviceSessionCache;
|
||||
private Mock<ILogger<ScriptScheduleDispatcher>> _logger;
|
||||
private ScriptScheduleDispatcher _dispatcher;
|
||||
private TestData _testData;
|
||||
private SavedScript _savedScript;
|
||||
private ScriptSchedule _schedule1 = null!;
|
||||
private Mock<IDataService> _dataService = null!;
|
||||
private Mock<ICircuitConnection> _circuitConnection = null!;
|
||||
private Mock<IAgentHubSessionCache> _serviceSessionCache = null!;
|
||||
private Mock<ILogger<ScriptScheduleDispatcher>> _logger = null!;
|
||||
private ScriptScheduleDispatcher _dispatcher = null!;
|
||||
private TestData _testData = null!;
|
||||
private SavedScript _savedScript = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public async Task Init()
|
||||
@ -112,8 +112,8 @@ public class ScriptScheduleDispatcherTests
|
||||
x.Contains(_schedule1.Devices.First().ID))));
|
||||
_dataService.Verify(x => x.AddScriptRun(It.Is<ScriptRun>(x =>
|
||||
x.ScheduleId == _schedule1.Id &&
|
||||
x.Devices.Exists(d => d.ID == _testData.Org1Device1.ID) &&
|
||||
x.Devices.Exists(d => d.ID == _testData.Org1Device2.ID))));
|
||||
x.Devices!.Exists(d => d.ID == _testData.Org1Device1.ID) &&
|
||||
x.Devices!.Exists(d => d.ID == _testData.Org1Device2.ID))));
|
||||
_dataService.VerifyNoOtherCalls();
|
||||
|
||||
_circuitConnection.Verify(x => x.RunScript(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user