mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
* Add Azure Pipelines yml. * Remove Server Installer. * Update Release Build.yml for Azure Pipelines * Update Release Build.yml * Update Release Build.yml for Azure Pipelines * Update Release Build.yml for Azure Pipelines * Update Release Build.yml for Azure Pipelines * Update Release Build.yml for Azure Pipelines * Update signtool.exe * Update Release Build.yml * Update Publish.ps1 * Update pipeline and Dockerfile. * Move docker files. * Update Dockerfile * Update Dockerfile * Update Dockerfile * Update Dockerfile * Move files. * Update Dockerfile * Update Dockerfile * Create RewritableStream. * Finish implementation of rewritable stream and embedded data searcher. * Remove RelayCode. * Get branding from default org if orgId is missing. * Update README.md * Remove AppConstants.ServerUrl. Fix main module file path. * Update submodule. * Extract embedded data in Program.cs. * Add logging. Update submodule. * Remove size block. BinaryWriter prefixes size. * Remove unused async * Update Immense.RemoteControl * Update Release Build.yml for Azure Pipelines * Update Release Build.yml for Azure Pipelines * Use UpgradeService for determining out of date clients.
372 lines
13 KiB
C#
372 lines
13 KiB
C#
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.Components.Rendering;
|
|
using Microsoft.AspNetCore.Components.Web;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
using Remotely.Server.Components;
|
|
using Remotely.Server.Components.ModalContents;
|
|
using Remotely.Server.Services;
|
|
using Remotely.Shared.Models;
|
|
using Remotely.Shared.ViewModels;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.Encodings.Web;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Remotely.Server.Pages
|
|
{
|
|
public partial class ManageOrganization : AuthComponentBase
|
|
{
|
|
private readonly List<DeviceGroup> _deviceGroups = new();
|
|
private readonly List<InviteLink> _invites = new();
|
|
private readonly List<RemotelyUser> _orgUsers = new();
|
|
private bool _inviteAsAdmin;
|
|
private string _inviteEmail;
|
|
private string _newDeviceGroupName;
|
|
private Organization _organization;
|
|
private string _selectedDeviceGroupId;
|
|
|
|
[Inject]
|
|
private IDataService DataService { get; set; }
|
|
|
|
[Inject]
|
|
private IEmailSenderEx EmailSender { get; set; }
|
|
|
|
[Inject]
|
|
private IJsInterop JsInterop { get; set; }
|
|
|
|
[Inject]
|
|
private IModalService ModalService { get; set; }
|
|
|
|
[Inject]
|
|
private NavigationManager NavManager { get; set; }
|
|
|
|
[Inject]
|
|
private IToastService ToastService { get; set; }
|
|
[Inject]
|
|
private UserManager<RemotelyUser> UserManager { get; set; }
|
|
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await base.OnInitializedAsync();
|
|
|
|
await RefreshData();
|
|
}
|
|
|
|
private void CreateNewDeviceGroup()
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(_newDeviceGroupName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var deviceGroup = new DeviceGroup()
|
|
{
|
|
Name = _newDeviceGroupName
|
|
};
|
|
|
|
var result = DataService.AddDeviceGroup(User.OrganizationID, deviceGroup, out _, out var errorMessage);
|
|
if (!result)
|
|
{
|
|
ToastService.ShowToast(errorMessage, classString: "bg-danger");
|
|
return;
|
|
}
|
|
|
|
ToastService.ShowToast("Device group created.");
|
|
_deviceGroups.Add(deviceGroup);
|
|
_newDeviceGroupName = string.Empty;
|
|
}
|
|
|
|
private void DefaultOrgCheckChanged(ChangeEventArgs args)
|
|
{
|
|
if (!User.IsServerAdmin)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var isDefault = (bool)args.Value;
|
|
DataService.SetIsDefaultOrganization(_organization.ID, isDefault);
|
|
ToastService.ShowToast("Default organization set.");
|
|
}
|
|
|
|
private async Task DeleteInvite(InviteLink invite)
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var result = await JsInterop.Confirm("Are you sure you want to delete this invitation?");
|
|
if (!result)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DataService.DeleteInvite(User.OrganizationID, invite.ID);
|
|
_invites.RemoveAll(x => x.ID == invite.ID);
|
|
ToastService.ShowToast("Invitation deleted.");
|
|
}
|
|
|
|
private async Task DeleteSelectedDeviceGroup()
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(_selectedDeviceGroupId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var result = await JsInterop.Confirm("Are you sure you want to delete this device group?");
|
|
if (!result)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DataService.DeleteDeviceGroup(User.OrganizationID, _selectedDeviceGroupId);
|
|
_deviceGroups.RemoveAll(x => x.ID == _selectedDeviceGroupId);
|
|
_selectedDeviceGroupId = string.Empty;
|
|
}
|
|
|
|
private async Task DeleteUser(RemotelyUser user)
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (User.Id == user.Id)
|
|
{
|
|
ToastService.ShowToast("You can't delete yourself.", classString: "bg-warning");
|
|
return;
|
|
}
|
|
|
|
var result = await JsInterop.Confirm("Are you sure you want to delete this user?");
|
|
if (!result)
|
|
{
|
|
return;
|
|
}
|
|
|
|
await DataService.DeleteUser(User.OrganizationID, user.Id);
|
|
_orgUsers.RemoveAll(x => x.Id == user.Id);
|
|
ToastService.ShowToast("User deleted.");
|
|
}
|
|
|
|
private async Task EditDeviceGroups(RemotelyUser user)
|
|
{
|
|
void editDeviceGroupsModal(RenderTreeBuilder builder)
|
|
{
|
|
var deviceGroups = DataService.GetDeviceGroupsForOrganization(user.OrganizationID);
|
|
|
|
builder.OpenComponent<EditDeviceGroup>(0);
|
|
builder.AddAttribute(1, EditDeviceGroup.EditUserPropName, user);
|
|
builder.AddAttribute(2, EditDeviceGroup.DeviceGroupsPropName, deviceGroups);
|
|
builder.CloseComponent();
|
|
}
|
|
await ModalService.ShowModal("Device Groups", editDeviceGroupsModal);
|
|
}
|
|
|
|
private async Task EvaluateInviteInputKeypress(KeyboardEventArgs args)
|
|
{
|
|
if (args.Key.Equals("Enter", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
await SendInvite();
|
|
}
|
|
}
|
|
|
|
private void EvaluateNewDeviceGroupKeyPress(KeyboardEventArgs args)
|
|
{
|
|
if (args.Key.Equals("Enter", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
CreateNewDeviceGroup();
|
|
}
|
|
}
|
|
private void OrganizationNameChanged(ChangeEventArgs args)
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var newName = (string)args.Value;
|
|
if (string.IsNullOrWhiteSpace(newName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (newName.Length > 25)
|
|
{
|
|
ToastService.ShowToast("Must be 25 characters or less.",
|
|
classString: "bg-warning");
|
|
return;
|
|
}
|
|
|
|
DataService.UpdateOrganizationName(_organization.ID, newName);
|
|
_organization.OrganizationName = newName;
|
|
ToastService.ShowToast("Organization name changed.");
|
|
}
|
|
|
|
private async Task RefreshData()
|
|
{
|
|
_organization = await DataService.GetOrganizationByUserName(Username);
|
|
|
|
_orgUsers.Clear();
|
|
_invites.Clear();
|
|
_deviceGroups.Clear();
|
|
|
|
_invites.AddRange(DataService.GetAllInviteLinks(User.OrganizationID).OrderBy(x => x.InvitedUser));
|
|
_deviceGroups.AddRange(DataService.GetDeviceGroups(Username).OrderBy(x => x.Name));
|
|
var orgUsers = await DataService.GetAllUsersInOrganization(User.OrganizationID);
|
|
_orgUsers.AddRange(orgUsers.OrderBy(x => x.UserName));
|
|
}
|
|
private async Task ResetPassword(RemotelyUser user)
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var code = await UserManager.GeneratePasswordResetTokenAsync(user);
|
|
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
|
var resetUrl = $"{NavManager.BaseUri}Identity/Account/ResetPassword?code={code}";
|
|
|
|
await ModalService.ShowModal("Password Reset", builder =>
|
|
{
|
|
builder.AddMarkupContent(0, $@"<div class=""mb-3"">Password Reset URL:</div>
|
|
<input readonly value=""{resetUrl}"" class=""form-control"" />
|
|
<div class=""mt-3"">NOTE: Give this URL to the user. They must be logged out completely for it to work.</div>");
|
|
});
|
|
}
|
|
|
|
private async Task SendInvite()
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!DataService.DoesUserExist(_inviteEmail))
|
|
{
|
|
var result = await DataService.CreateUser(_inviteEmail, _inviteAsAdmin, User.OrganizationID);
|
|
if (result)
|
|
{
|
|
var user = await DataService.GetUserAsync(_inviteEmail);
|
|
|
|
await UserManager.ConfirmEmailAsync(user, await UserManager.GenerateEmailConfirmationTokenAsync(user));
|
|
|
|
_orgUsers.Add(user);
|
|
|
|
_inviteAsAdmin = false;
|
|
_inviteEmail = string.Empty;
|
|
ToastService.ShowToast("User account created.");
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ToastService.ShowToast("Create user failed.", classString: "bg-danger");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
var invite = new InviteViewModel()
|
|
{
|
|
InvitedUser = _inviteEmail,
|
|
IsAdmin = _inviteAsAdmin
|
|
};
|
|
var newInvite = DataService.AddInvite(User.OrganizationID, invite);
|
|
|
|
var inviteURL = $"{NavManager.BaseUri}Invite?id={newInvite.ID}";
|
|
var emailResult = await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
|
|
$@"<img src='{NavManager.BaseUri}images/Remotely_Logo.png'/>
|
|
<br><br>
|
|
Hello!
|
|
<br><br>
|
|
You've been invited to join an organization in Remotely.
|
|
<br><br>
|
|
You can join the organization by <a href='{HtmlEncoder.Default.Encode(inviteURL)}'>clicking here</a>.",
|
|
User.OrganizationID);
|
|
if (emailResult)
|
|
{
|
|
ToastService.ShowToast("Invitation sent.");
|
|
|
|
_inviteAsAdmin = false;
|
|
_inviteEmail = string.Empty;
|
|
_invites.Add(newInvite);
|
|
}
|
|
else
|
|
{
|
|
ToastService.ShowToast("Error sending invititation email.", classString: "bg-danger");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetUserIsAdmin(ChangeEventArgs args, RemotelyUser orgUser)
|
|
{
|
|
if (!User.IsAdministrator)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var isAdmin = (bool)args.Value;
|
|
DataService.ChangeUserIsAdmin(User.OrganizationID, orgUser.Id, isAdmin);
|
|
ToastService.ShowToast("Administrator value set.");
|
|
}
|
|
|
|
private void ShowDefaultOrgHelp()
|
|
{
|
|
ModalService.ShowModal("Default Organization", new[]
|
|
{
|
|
@"This option is only available for server administrators. When selected,
|
|
it sets this organization as the default for the server. If the organization can't
|
|
be determined in the quick support apps, they will use the default organization's branding."
|
|
});
|
|
}
|
|
|
|
private void ShowDeviceGroupHelp()
|
|
{
|
|
ModalService.ShowModal("Device Groups", new[]
|
|
{
|
|
"Device groups can be used to restrict user permissions and to filter computers on " +
|
|
"the main page.",
|
|
"Everyone will have access to devices that are not in a group. Only " +
|
|
"administrators and users in a device group will have access to devices in that group."
|
|
});
|
|
}
|
|
|
|
private void ShowInvitesHelp()
|
|
{
|
|
ModalService.ShowModal("Invitations", new[]
|
|
{
|
|
"All pending invitations will be shown here and can be revoked by deleting them.",
|
|
|
|
"If a user does not exist, sending an invite will create their account and add them to the current organization. " +
|
|
"A password reset URL can be generated from the user table.",
|
|
|
|
"The Admin checkbox determines if the new user will have administrator privileges in this organization."
|
|
});
|
|
}
|
|
|
|
private void ShowUsersHelp()
|
|
{
|
|
ModalService.ShowModal("Users", new[]
|
|
{
|
|
"All users for the organization are managed here",
|
|
"Administrators will have access to this management screen as well as all computers."
|
|
});
|
|
}
|
|
}
|
|
}
|