Add "update all" button on server config page.

This commit is contained in:
Jared 2021-01-28 07:43:26 -08:00 committed by Jared Goodwin
parent 1e1dd129c0
commit adbab10f93
4 changed files with 71 additions and 49 deletions

View File

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

View File

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

View File

@ -10,6 +10,8 @@
@if (Model.IsServerAdmin)
{
<partial name="_StatusMessage" for="StatusMessage" />
<h5 class="mt-3">Server Info</h5>
<div class="row">
@ -24,6 +26,11 @@
<label>Devices Outdated</label>
<br />
<span class="badge badge-primary p-2">@(Model.OutdatedDevices?.Count() ?? 0)</span>
<form class="d-inline-block" method="post" asp-page-handler="UpdateAll">
<button class="btn btn-secondary btn-sm ml-1" type="submit" title="Update All">
<span class="fas fa-sync"></span>
</button>
</form>
</div>
<div class="d-inline-block text-center mr-2">
<label>Devices Total</label>
@ -46,8 +53,6 @@
<h5>Application Settings</h5>
<partial name="_StatusMessage" for="StatusMessage" />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">

View File

@ -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<AgentHub> _agentHubContext;
private readonly IConfiguration _configuration;
private readonly IDataService _dataService;
private readonly IEmailSenderEx _emailSender;
private readonly IWebHostEnvironment _hostEnv;
private readonly UserManager<RemotelyUser> _userManager;
public ServerConfigModel(IConfiguration configuration,
IWebHostEnvironment hostEnv,
IWebHostEnvironment hostEnv,
UserManager<RemotelyUser> userManager,
IDataService dataService,
IEmailSenderEx emailSender)
IEmailSenderEx emailSender,
IHubContext<AgentHub> 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<string> 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<RemotelyUser> UserManager { get; }
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<string> 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<IDictionary<string, object>>(await System.IO.File.ReadAllTextAsync(savePath));
AppSettingsInput.IceServers = Configuration.GetSection("ApplicationOptions:IceServers").Get<IceServerModel[]>();
AppSettingsInput.IceServers = _configuration.GetSection("ApplicationOptions:IceServers").Get<IceServerModel[]>();
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