Another pass of nullable refactoring.

This commit is contained in:
Jared Goodwin 2023-07-26 11:31:34 -07:00
parent 0f9ea4957b
commit ac6487c6c5
89 changed files with 672 additions and 465 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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; }
}

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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()

View File

@ -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);

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();

View File

@ -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";

View File

@ -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;

View File

@ -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);

View File

@ -288,7 +288,7 @@ public class OrganizationManagementController : ControllerBase
return Unauthorized();
}
if (!ModelState.IsValid)
if (!ModelState.IsValid || string.IsNullOrWhiteSpace(invite.InvitedUser))
{
return BadRequest();
}

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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);

View File

@ -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.");

View File

@ -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.");

View File

@ -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)

View File

@ -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)

View File

@ -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" : "";
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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; }

View File

@ -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()

View File

@ -20,7 +20,7 @@
@code {
private ColorPickerModel _color;
private ColorPickerModel _color = new();
[Parameter]
public ColorPickerModel Color

View File

@ -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>

View File

@ -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()
{

View File

@ -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))
{

View File

@ -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);

View File

@ -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;

View File

@ -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()
{

View File

@ -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; }

View File

@ -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))
{

View File

@ -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)
{

View File

@ -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">

View File

@ -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;

View File

@ -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()
{

View File

@ -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();
}

View File

@ -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

View File

@ -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; }
}

View File

@ -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; }

View File

@ -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
{

View File

@ -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);

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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()

View File

@ -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>

View File

@ -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;

View File

@ -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; }

View File

@ -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;
}

View File

@ -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
{

View File

@ -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";

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -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);
}

View File

@ -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
{

View File

@ -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;

View File

@ -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();
}

View File

@ -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
{

View File

@ -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;

View File

@ -16,7 +16,7 @@ public class ConnectionInfo
}
set
{
_host = value?.Trim()?.TrimEnd('/') ?? string.Empty;
_host = value?.Trim()?.TrimEnd('/');
}
}
public string? OrganizationID { get; set; }

View File

@ -11,7 +11,6 @@ namespace Remotely.Shared.Models;
public class Device
{
public static Device Empty { get; } = new();
[Sortable]
[Display(Name = "Agent Version")]

View File

@ -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

View File

@ -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)]

View File

@ -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)
{

View File

@ -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
{

View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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; }

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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());

View File

@ -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();
}
}

View File

@ -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(