diff --git a/README.md b/README.md index 45b9710d..bde76611 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ The following steps will configure your Windows 10 machine for building the Remo * If the site will be public-facing, configure your bindings in IIS. * An SSL certificate for HTTPS is recommended. You can install one for free using Let's Encrypt. * Resources: https://letsencrypt.org/, https://certifytheweb.com/ -* Documentation for hosting in IIS can be found here: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/index?view=aspnetcore-3.1 +* Documentation for hosting in IIS can be found here: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis * There is no default account. You must create the first one via the Register page, which will create an account that is both a server and organization admin. ## Hosting a Server (Ubuntu) @@ -83,13 +83,13 @@ The following steps will configure your Windows 10 machine for building the Remo * More information: https://letsencrypt.org/, https://certbot.eff.org/ * Alternatively, you can build from source (using RuntimeIdentifier "linux-x64" for the server) and copy the server files to the site folder. * Change values in appsettings.json for your environment. Make a copy named `appsettings.Production.json` (see Configuration section below). -* Documentation for hosting behind Nginx can be found here: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-3.1 +* Documentation for hosting behind Nginx can be found here: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx * There is no default account. You must create the first one via the Register page, which will create an account that is both a server and organization admin. ## Hosting Scenarios There are countless ways to host an ASP.NET Core app, and I can't document or automate all of them. For hosting scenarios aside from the above two, please refer to Microsoft's documentation. -- https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/?view=aspnetcore-3.1 +- https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/ ## Admin Accounts The first account created will be an admin for both the server and the organization that's created for the account. @@ -115,7 +115,7 @@ Likewise, `appsettings.Development.json` can be used while developing in Visual Whenever there's a reference to `appsettings.json` in this document, it refers to whichever file is currently being used. -For more information on configuring ASP.NET Core, see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1. +For more information on configuring ASP.NET Core, see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/. * AllowApiLogin: Whether to allow logging in via the API controller. API access tokens are recommended over this approach. * BannedDevices: An array of device IDs, names, or IP addresses to ban. When they try to connect, an uninstall command will immediately be sent back. @@ -155,7 +155,7 @@ You can change database by changing `DBProvider` in `ApplicationOptions` to `SQL * On Windows Servers, the above logs can also be written to the Windows Event Log. * This is enabled in appsettings.json by setting EnableWindowsEventLog to true. * You can configure logging levels and other settings in appsetttings.json. - * More information: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1 + * More information: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/ ## Remote Control Client Requirements * Windows: Only the latest version of Windows 10 is tested. Windows 7 and 8.1 should work, though performance will be reduced on Windows 7. diff --git a/Remotely.sln b/Remotely.sln index 110ed05f..d416facd 100644 --- a/Remotely.sln +++ b/Remotely.sln @@ -33,12 +33,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.Linux", "Desktop.Linux\Desktop.Linux.csproj", "{FF7FD66A-B3E2-4D39-A457-A00C94EDE9E6}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Folder", "Solution Folder", "{CF0B7B5D-5FAD-42C9-8803-123D2013EC13}" - ProjectSection(SolutionItems) = preProject - LICENSE = LICENSE - README.md = README.md - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Desktop.Win", "Desktop.Win\Desktop.Win.csproj", "{6B726FC4-A907-4813-BF38-3342E02AA8D2}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agent.Installer.Win", "Agent.Installer.Win\Agent.Installer.Win.csproj", "{A3D0368C-0850-4614-B5B5-41B9D5135AA9}" @@ -52,6 +46,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2176596E-12DA-4766-96E1-4D23EA7DBEC8}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + LICENSE = LICENSE + README.md = README.md EndProjectSection EndProject Global diff --git a/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml b/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml index ed9cc7b2..42a98dc8 100644 --- a/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml +++ b/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml @@ -10,6 +10,8 @@ @if (Model.IsServerAdmin) { + +
Server Info
@@ -24,6 +26,11 @@
@(Model.OutdatedDevices?.Count() ?? 0) +
+ +
@@ -46,8 +53,6 @@
Application Settings
- -
diff --git a/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml.cs b/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml.cs index 4cbc3109..5d4fbef3 100644 --- a/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/Manage/ServerConfig.cshtml.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Remotely.Server.Hubs; @@ -20,17 +21,26 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage { public class ServerConfigModel : PageModel { + private readonly IHubContext _agentHubContext; + private readonly IConfiguration _configuration; + private readonly IDataService _dataService; + private readonly IEmailSenderEx _emailSender; + private readonly IWebHostEnvironment _hostEnv; + private readonly UserManager _userManager; + public ServerConfigModel(IConfiguration configuration, - IWebHostEnvironment hostEnv, + IWebHostEnvironment hostEnv, UserManager userManager, IDataService dataService, - IEmailSenderEx emailSender) + IEmailSenderEx emailSender, + IHubContext agentHubContext) { - Configuration = configuration; - HostEnv = hostEnv; - UserManager = userManager; - DataService = dataService; - EmailSender = emailSender; + _configuration = configuration; + _hostEnv = hostEnv; + _userManager = userManager; + _dataService = dataService; + _emailSender = emailSender; + _agentHubContext = agentHubContext; } public enum DBProviders @@ -46,9 +56,8 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage [BindProperty] public ConnectionStringsModel ConnectionStrings { get; set; } = new ConnectionStringsModel(); - public bool IsServerAdmin { get; set; } public string Environment { get; set; } - + public bool IsServerAdmin { get; set; } public IEnumerable OutdatedDevices { get; set; } [BindProperty] @@ -57,39 +66,29 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage [TempData] public string StatusMessage { get; set; } - - private IConfiguration Configuration { get; } - private IDataService DataService { get; } - private IEmailSenderEx EmailSender { get; } - private IWebHostEnvironment HostEnv { get; } - private UserManager UserManager { get; } - public async Task OnGet() { - IsServerAdmin = (await UserManager.GetUserAsync(User)).IsServerAdmin; + IsServerAdmin = (await _userManager.GetUserAsync(User)).IsServerAdmin; if (!IsServerAdmin) { return Unauthorized(); } - var highestVersion = AgentHub.ServiceConnections.Values.Max(x => Version.TryParse(x.AgentVersion, out var result) ? result : default); - OutdatedDevices = AgentHub.ServiceConnections.Values - .Where(x => Version.TryParse(x.AgentVersion, out var result) ? result != highestVersion : false) - .Select(x => x.ID); + OutdatedDevices = GetOutdatedDevices(); - Environment = HostEnv.EnvironmentName; + Environment = _hostEnv.EnvironmentName; - Configuration.Bind("ApplicationOptions", AppSettingsInput); - Configuration.Bind("ConnectionStrings", ConnectionStrings); - ServerAdmins = DataService.GetServerAdmins(); + _configuration.Bind("ApplicationOptions", AppSettingsInput); + _configuration.Bind("ConnectionStrings", ConnectionStrings); + ServerAdmins = _dataService.GetServerAdmins(); return Page(); } public async Task OnPostSaveAndTestSmtpAsync() { var result = await OnPostSaveAsync(); - var user = DataService.GetUserByName(User.Identity.Name); - var success = await EmailSender.SendEmailAsync(user.Email, "Remotely Test Email", "Congratulations! Your SMTP settings are working!", user.OrganizationID); + var user = _dataService.GetUserByName(User.Identity.Name); + var success = await _emailSender.SendEmailAsync(user.Email, "Remotely Test Email", "Congratulations! Your SMTP settings are working!", user.OrganizationID); if (success) { StatusMessage = "Test email sent. Check your inbox (including spam folder)."; @@ -102,7 +101,7 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage } public async Task OnPostSaveAsync() { - IsServerAdmin = (await UserManager.GetUserAsync(User)).IsServerAdmin; + IsServerAdmin = (await _userManager.GetUserAsync(User)).IsServerAdmin; if (!IsServerAdmin) { return Unauthorized(); @@ -114,7 +113,7 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage } var configReloaded = false; - Configuration.GetReloadToken().RegisterChangeCallback((e) => + _configuration.GetReloadToken().RegisterChangeCallback((e) => { configReloaded = true; }, null); @@ -131,17 +130,39 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage return RedirectToPage(); } + public async Task OnPostUpdateAllAsync() + { + var outdatedDevices = GetOutdatedDevices(); + if (!outdatedDevices.Any()) + { + StatusMessage = "No agents need updating."; + return RedirectToPage(); + } + var agentConnections = AgentHub.ServiceConnections.Where(x => outdatedDevices.Contains(x.Value.ID)); + await _agentHubContext.Clients.Clients(agentConnections.Select(x => x.Key)).SendAsync("ReinstallAgent"); + StatusMessage = $"Update command sent to {agentConnections.Count()} agents."; + return RedirectToPage(); + } + + private IEnumerable GetOutdatedDevices() + { + var highestVersion = AgentHub.ServiceConnections.Values.Max(x => Version.TryParse(x.AgentVersion, out var result) ? result : default); + return AgentHub.ServiceConnections.Values + .Where(x => Version.TryParse(x.AgentVersion, out var result) ? result != highestVersion : false) + .Select(x => x.ID); + } + private async Task SaveAppSettings() { string savePath; - var prodSettings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Production.json"); - var stagingSettings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Staging.json"); - var settings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.json"); - if (HostEnv.IsProduction() && prodSettings.Exists) + var prodSettings = _hostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Production.json"); + var stagingSettings = _hostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Staging.json"); + var settings = _hostEnv.ContentRootFileProvider.GetFileInfo("appsettings.json"); + if (_hostEnv.IsProduction() && prodSettings.Exists) { savePath = prodSettings.PhysicalPath; } - else if (HostEnv.IsStaging() && stagingSettings.Exists) + else if (_hostEnv.IsStaging() && stagingSettings.Exists) { savePath = stagingSettings.PhysicalPath; } @@ -155,13 +176,13 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage } var settingsJson = JsonSerializer.Deserialize>(await System.IO.File.ReadAllTextAsync(savePath)); - AppSettingsInput.IceServers = Configuration.GetSection("ApplicationOptions:IceServers").Get(); + AppSettingsInput.IceServers = _configuration.GetSection("ApplicationOptions:IceServers").Get(); settingsJson["ApplicationOptions"] = AppSettingsInput; settingsJson["ConnectionStrings"] = ConnectionStrings; await System.IO.File.WriteAllTextAsync(savePath, JsonSerializer.Serialize(settingsJson, new JsonSerializerOptions() { WriteIndented = true })); - await DataService.UpdateServerAdmins(ServerAdmins, User.Identity.Name); + await _dataService.UpdateServerAdmins(ServerAdmins, User.Identity.Name); } public class AppSettingsModel