Move to PostgreSQL. Start adding stuff for device permissions.

This commit is contained in:
Jared Goodwin 2020-02-25 19:37:31 -08:00
parent 350099c430
commit e2be4d2371
27 changed files with 669 additions and 1631 deletions

View File

@ -45,24 +45,29 @@
<div class="row">
<div class="col-sm-8">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@* Device Groups *@
<div class="form-group">
<label class="mb-1">Device Groups</label>
<span id="deviceGroupHelpButton" class="fas fa-question-circle pointer"></span>
<select id="deviceGroupList" multiple asp-items="Model.DeviceGroups" class="form-control all-device-groups-list mb-1">
</select>
<form asp-page-handler="CreateDeviceGroup">
<div class="form-group">
<label class="mb-1">Device Groups</label>
<span id="deviceGroupHelpButton" class="fas fa-question-circle pointer"></span>
<select id="deviceGroupList" multiple asp-items="Model.DeviceGroupSelectItems" class="form-control all-device-groups-list mb-1">
</select>
<div class="text-right mb-3">
<button id="removeDeviceGroupButton" type="button" class="btn btn-secondary">Remove</button>
</div>
<div class="text-right mb-3">
<button id="addUsersToDeviceGroupButton" type="button" class="btn btn-secondary">Users</button>
<button id="removeDeviceGroupButton" type="button" class="btn btn-secondary">Remove</button>
</div>
<div class="input-group">
<input id="deviceGroupInput" placeholder="Add new device group" class="form-control" maxlength="200" />
<span class="input-group-btn">
<button id="addDeviceGroupButton" type="button" class="btn btn-secondary">Add</button>
</span>
<div class="input-group">
<input id="deviceGroupInput" placeholder="Add new device group" asp-for="Input.DeviceGroupName" class="form-control" maxlength="200" />
<span class="input-group-btn">
<button id="addDeviceGroupButton" type="submit" class="btn btn-secondary">Add</button>
</span>
</div>
</div>
</div>
</form>
</div>
</div>
@ -150,7 +155,7 @@
<form method="post" asp-page-handler="SendInvite">
<div class="form-group">
<div class="input-group">
<input asp-for="Input.UserEmail" placeholder="Username/email to invite" type="email" required class="form-control" />
<input asp-for="Input.UserEmail" placeholder="Username/email to invite" type="email" required class="form-control" />
<div class="input-group-append">
<span class="input-group-text pr-1">Admin?</span>
</div>
@ -169,6 +174,58 @@
</div>
</div>
@* Device Permission Modals *@
@for (var i = 0; i < Model.DeviceGroups.Count; i++)
{
<div group="@Model.DeviceGroups[i].ID" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Users <span class="small text-muted">for @Model.DeviceGroups[i].Name</span></h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
</div>
<div class="modal-body">
<div class="mb-1">
Use this form to give users access to this device group. Once added, only the
users in this list will have access.
</div>
<div class="mb-3">
If no users are added, everyone will have access.
</div>
<select multiple class="form-control device-group-user-list mb-1">
@if (Model.DeviceGroups[i]?.Users?.Any() == true)
{
foreach (var user in Model.DeviceGroups[i].Users)
{
<option value="@user.Id">@user.UserName</option>
}
}
</select>
<div class="text-right mb-3">
<button type="button" class="btn btn-secondary remove-user-from-device-group-button" group="@Model.DeviceGroups[i].ID">
Remove
</button>
</div>
<div class="input-group">
<input placeholder="Add user to device group" class="form-control add-user-to-devicegroup-input" maxlength="200" />
<span class="input-group-btn">
<button type="button" class="btn btn-secondary add-user-to-devicegroup-button" group="@Model.DeviceGroups[i].ID">
Add
</button>
</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
}
<script type="module" src="~/scripts/Pages/OrganizationManagement.js">
</script>

View File

@ -24,7 +24,8 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
UserManager = userManager;
EmailSender = emailSender;
}
public List<SelectListItem> DeviceGroups { get; } = new List<SelectListItem>();
public List<SelectListItem> DeviceGroupSelectItems { get; } = new List<SelectListItem>();
public List<DeviceGroup> DeviceGroups { get; } = new List<DeviceGroup>();
[BindProperty]
public InputModel Input { get; set; } = new InputModel();
@ -117,12 +118,41 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
return Page();
}
public async Task<IActionResult> OnPostCreateDeviceGroupAsync()
{
var currentUser = await UserManager.FindByEmailAsync(User.Identity.Name);
if (!currentUser.IsAdministrator)
{
return RedirectToPage("Index");
}
if (ModelState.IsValid)
{
var deviceGroup = new DeviceGroup()
{
Name = Input.DeviceGroupName
};
var result = DataService.AddDeviceGroup(currentUser.OrganizationID, deviceGroup, out _, out var errorMessage);
if (!result)
{
PopulateViewModel();
ModelState.AddModelError("AddDeviceGroup", errorMessage);
return Page();
}
StatusMessage = "Device group created.";
return RedirectToPage();
}
PopulateViewModel();
return Page();
}
private void PopulateViewModel()
{
OrganizationName = DataService.GetOrganizationName(User.Identity.Name);
var groups = DataService.GetDeviceGroupsForUserName(User.Identity.Name);
DeviceGroups.AddRange(groups.Select(x => new SelectListItem(x.Name, x.ID)));
DeviceGroups.AddRange(DataService.GetDeviceGroupsForUserName(User.Identity.Name));
DeviceGroupSelectItems.AddRange(DeviceGroups.Select(x => new SelectListItem(x.Name, x.ID)));
Users = DataService.GetAllUsers(User.Identity.Name)
.Select(x => new OrganizationUser()
@ -147,6 +177,9 @@ namespace Remotely.Server.Areas.Identity.Pages.Account.Manage
[EmailAddress]
public string UserEmail { get; set; }
[StringLength(200)]
public string DeviceGroupName { get; set; }
}
}

View File

@ -34,7 +34,9 @@ namespace Remotely.Server.Data
public DbSet<DeviceGroup> DeviceGroups { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
public DbSet<UserDevicePermission> PermissionLinks { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
@ -55,6 +57,8 @@ namespace Remotely.Server.Data
builder.Entity<DeviceGroup>()
.HasMany(x => x.Devices)
.WithOne(x => x.DeviceGroup);
builder.Entity<DeviceGroup>()
.HasMany(x => x.PermissionLinks);
builder.Entity<Organization>()
.HasMany(x => x.DeviceGroups)
.WithOne(x => x.Organization);
@ -90,6 +94,9 @@ namespace Remotely.Server.Data
.HasOne(x => x.Organization)
.WithMany(x => x.RemotelyUsers);
builder.Entity<RemotelyUser>()
.HasMany(x => x.PermissionLinks);
builder.Entity<RemotelyUser>()
.Property(x => x.UserOptions)
.HasConversion(

View File

@ -1,575 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Remotely.Server.Data;
namespace Remotely.Server.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20191211163438_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.0");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("RemotelyUsers");
b.HasDiscriminator<string>("Discriminator").HasValue("IdentityUser");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Remotely.Shared.Models.CommandContext", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("CommandMode")
.HasColumnType("TEXT");
b.Property<string>("CommandResults")
.HasColumnType("TEXT");
b.Property<string>("CommandText")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("PSCoreResults")
.HasColumnType("TEXT");
b.Property<string>("SenderConnectionID")
.HasColumnType("TEXT");
b.Property<string>("SenderUserID")
.HasColumnType("TEXT");
b.Property<string>("TargetDeviceIDs")
.HasColumnType("TEXT");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("CommandContexts");
});
modelBuilder.Entity("Remotely.Shared.Models.Device", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("AgentVersion")
.HasColumnType("TEXT");
b.Property<string>("CurrentUser")
.HasColumnType("TEXT");
b.Property<string>("DeviceGroupID")
.HasColumnType("TEXT");
b.Property<string>("DeviceName")
.HasColumnType("TEXT");
b.Property<string>("Drives")
.HasColumnType("TEXT");
b.Property<double>("FreeMemory")
.HasColumnType("REAL");
b.Property<double>("FreeStorage")
.HasColumnType("REAL");
b.Property<bool>("Is64Bit")
.HasColumnType("INTEGER");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastOnline")
.HasColumnType("TEXT");
b.Property<int>("OSArchitecture")
.HasColumnType("INTEGER");
b.Property<string>("OSDescription")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("Platform")
.HasColumnType("TEXT");
b.Property<int>("ProcessorCount")
.HasColumnType("INTEGER");
b.Property<string>("ServerVerificationToken")
.HasColumnType("TEXT");
b.Property<string>("Tags")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<double>("TotalMemory")
.HasColumnType("REAL");
b.Property<double>("TotalStorage")
.HasColumnType("REAL");
b.HasKey("ID");
b.HasIndex("DeviceGroupID");
b.HasIndex("OrganizationID");
b.ToTable("Devices");
});
modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("DeviceGroups");
});
modelBuilder.Entity("Remotely.Shared.Models.EventLog", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<int>("EventType")
.HasColumnType("INTEGER");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("Source")
.HasColumnType("TEXT");
b.Property<string>("StackTrace")
.HasColumnType("TEXT");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("EventLogs");
});
modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<DateTime>("DateSent")
.HasColumnType("TEXT");
b.Property<string>("InvitedUser")
.HasColumnType("TEXT");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("ResetUrl")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("InviteLinks");
});
modelBuilder.Entity("Remotely.Shared.Models.Organization", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("OrganizationName")
.HasColumnType("TEXT")
.HasMaxLength(25);
b.HasKey("ID");
b.ToTable("Organizations");
});
modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("ContentType")
.HasColumnType("TEXT");
b.Property<byte[]>("FileContents")
.HasColumnType("BLOB");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("SharedFiles");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser");
b.Property<bool>("IsAdministrator")
.HasColumnType("INTEGER");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("UserOptions")
.HasColumnType("TEXT");
b.HasIndex("OrganizationID");
b.HasDiscriminator().HasValue("RemotelyUser");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Remotely.Shared.Models.CommandContext", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("CommandContexts")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.Device", b =>
{
b.HasOne("Remotely.Shared.Models.DeviceGroup", "DeviceGroup")
.WithMany("Devices")
.HasForeignKey("DeviceGroupID");
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("Devices")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("DeviceGroups")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.EventLog", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("EventLogs")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("InviteLinks")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("SharedFiles")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("RemotelyUsers")
.HasForeignKey("OrganizationID");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,579 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Remotely.Server.Data;
namespace Remotely.Server.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20191212152649_Device alias")]
partial class Devicealias
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.0");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("RemotelyUsers");
b.HasDiscriminator<string>("Discriminator").HasValue("IdentityUser");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Remotely.Shared.Models.CommandContext", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("CommandMode")
.HasColumnType("TEXT");
b.Property<string>("CommandResults")
.HasColumnType("TEXT");
b.Property<string>("CommandText")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("PSCoreResults")
.HasColumnType("TEXT");
b.Property<string>("SenderConnectionID")
.HasColumnType("TEXT");
b.Property<string>("SenderUserID")
.HasColumnType("TEXT");
b.Property<string>("TargetDeviceIDs")
.HasColumnType("TEXT");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("CommandContexts");
});
modelBuilder.Entity("Remotely.Shared.Models.Device", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("AgentVersion")
.HasColumnType("TEXT");
b.Property<string>("Alias")
.HasColumnType("TEXT")
.HasMaxLength(100);
b.Property<string>("CurrentUser")
.HasColumnType("TEXT");
b.Property<string>("DeviceGroupID")
.HasColumnType("TEXT");
b.Property<string>("DeviceName")
.HasColumnType("TEXT");
b.Property<string>("Drives")
.HasColumnType("TEXT");
b.Property<double>("FreeMemory")
.HasColumnType("REAL");
b.Property<double>("FreeStorage")
.HasColumnType("REAL");
b.Property<bool>("Is64Bit")
.HasColumnType("INTEGER");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastOnline")
.HasColumnType("TEXT");
b.Property<int>("OSArchitecture")
.HasColumnType("INTEGER");
b.Property<string>("OSDescription")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("Platform")
.HasColumnType("TEXT");
b.Property<int>("ProcessorCount")
.HasColumnType("INTEGER");
b.Property<string>("ServerVerificationToken")
.HasColumnType("TEXT");
b.Property<string>("Tags")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<double>("TotalMemory")
.HasColumnType("REAL");
b.Property<double>("TotalStorage")
.HasColumnType("REAL");
b.HasKey("ID");
b.HasIndex("DeviceGroupID");
b.HasIndex("OrganizationID");
b.ToTable("Devices");
});
modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(200);
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("DeviceGroups");
});
modelBuilder.Entity("Remotely.Shared.Models.EventLog", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<int>("EventType")
.HasColumnType("INTEGER");
b.Property<string>("Message")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("Source")
.HasColumnType("TEXT");
b.Property<string>("StackTrace")
.HasColumnType("TEXT");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("EventLogs");
});
modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<DateTime>("DateSent")
.HasColumnType("TEXT");
b.Property<string>("InvitedUser")
.HasColumnType("TEXT");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("ResetUrl")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("InviteLinks");
});
modelBuilder.Entity("Remotely.Shared.Models.Organization", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("OrganizationName")
.HasColumnType("TEXT")
.HasMaxLength(25);
b.HasKey("ID");
b.ToTable("Organizations");
});
modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
b.Property<string>("ContentType")
.HasColumnType("TEXT");
b.Property<byte[]>("FileContents")
.HasColumnType("BLOB");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("ID");
b.HasIndex("OrganizationID");
b.ToTable("SharedFiles");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser");
b.Property<bool>("IsAdministrator")
.HasColumnType("INTEGER");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
b.Property<string>("UserOptions")
.HasColumnType("TEXT");
b.HasIndex("OrganizationID");
b.HasDiscriminator().HasValue("RemotelyUser");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Remotely.Shared.Models.CommandContext", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("CommandContexts")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.Device", b =>
{
b.HasOne("Remotely.Shared.Models.DeviceGroup", "DeviceGroup")
.WithMany("Devices")
.HasForeignKey("DeviceGroupID");
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("Devices")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("DeviceGroups")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.EventLog", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("EventLogs")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("InviteLinks")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("SharedFiles")
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("RemotelyUsers")
.HasForeignKey("OrganizationID");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,23 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Remotely.Server.Migrations
{
public partial class Devicealias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Alias",
table: "Devices",
maxLength: 100,
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Alias",
table: "Devices");
}
}
}

View File

@ -1,109 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Remotely.Server.Migrations
{
public partial class APITokensandDeviceUpdates : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "FreeMemory",
table: "Devices",
newName: "UsedMemory");
migrationBuilder.RenameColumn(
name: "FreeStorage",
table: "Devices",
newName: "UsedStorage");
migrationBuilder.AddColumn<double>(
name: "CpuUtilization",
table: "Devices",
nullable: false,
defaultValue: 0.0);
migrationBuilder.CreateTable(
name: "ApiTokens",
columns: table => new
{
ID = table.Column<string>(nullable: false),
LastUsed = table.Column<DateTime>(nullable: true),
Name = table.Column<string>(maxLength: 200, nullable: true),
OrganizationID = table.Column<string>(nullable: true),
Secret = table.Column<string>(nullable: true),
Token = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiTokens", x => x.ID);
table.ForeignKey(
name: "FK_ApiTokens_Organizations_OrganizationID",
column: x => x.OrganizationID,
principalTable: "Organizations",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_RemotelyUsers_UserName",
table: "RemotelyUsers",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_Devices_DeviceName",
table: "Devices",
column: "DeviceName");
migrationBuilder.CreateIndex(
name: "IX_ApiTokens_OrganizationID",
table: "ApiTokens",
column: "OrganizationID");
migrationBuilder.CreateIndex(
name: "IX_ApiTokens_Token",
table: "ApiTokens",
column: "Token");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApiTokens");
migrationBuilder.DropIndex(
name: "IX_RemotelyUsers_UserName",
table: "RemotelyUsers");
migrationBuilder.DropIndex(
name: "IX_Devices_DeviceName",
table: "Devices");
migrationBuilder.DropColumn(
name: "CpuUtilization",
table: "Devices");
migrationBuilder.DropColumn(
name: "UsedMemory",
table: "Devices");
migrationBuilder.DropColumn(
name: "UsedStorage",
table: "Devices");
migrationBuilder.AddColumn<double>(
name: "FreeMemory",
table: "Devices",
type: "REAL",
nullable: false,
defaultValue: 0.0);
migrationBuilder.AddColumn<double>(
name: "FreeStorage",
table: "Devices",
type: "REAL",
nullable: false,
defaultValue: 0.0);
}
}
}

View File

@ -4,35 +4,38 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Remotely.Server.Data;
namespace Remotely.Server.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20200218144304_API Tokens and Device Updates")]
partial class APITokensandDeviceUpdates
[Migration("20200226021333_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.1");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.HasKey("Id");
@ -48,17 +51,18 @@ namespace Remotely.Server.Migrations
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("Id");
@ -70,57 +74,57 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.HasKey("Id");
@ -141,17 +145,18 @@ namespace Remotely.Server.Migrations
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("Id");
@ -163,19 +168,19 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
@ -187,10 +192,10 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
@ -202,18 +207,18 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
@ -223,23 +228,23 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime?>("LastUsed")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(200)")
.HasMaxLength(200);
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Secret")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Token")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("ID");
@ -253,34 +258,34 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.CommandContext", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("CommandMode")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("CommandResults")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("CommandText")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("PSCoreResults")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("SenderConnectionID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("SenderUserID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("TargetDeviceIDs")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.HasKey("ID");
@ -292,72 +297,72 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.Device", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("AgentVersion")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Alias")
.HasColumnType("TEXT")
.HasColumnType("character varying(100)")
.HasMaxLength(100);
b.Property<double>("CpuUtilization")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<string>("CurrentUser")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("DeviceGroupID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("DeviceName")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Drives")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("Is64Bit")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<DateTime>("LastOnline")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.Property<int>("OSArchitecture")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("OSDescription")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Platform")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<int>("ProcessorCount")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("ServerVerificationToken")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Tags")
.HasColumnType("TEXT")
.HasColumnType("character varying(200)")
.HasMaxLength(200);
b.Property<double>("TotalMemory")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<double>("TotalStorage")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<double>("UsedMemory")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<double>("UsedStorage")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.HasKey("ID");
@ -373,14 +378,14 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(200)")
.HasMaxLength(200);
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("ID");
@ -392,25 +397,25 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.EventLog", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<int>("EventType")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("Message")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Source")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("StackTrace")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.HasKey("ID");
@ -422,22 +427,22 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("DateSent")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.Property<string>("InvitedUser")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ResetUrl")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("ID");
@ -449,10 +454,10 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.Organization", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationName")
.HasColumnType("TEXT")
.HasColumnType("character varying(25)")
.HasMaxLength(25);
b.HasKey("ID");
@ -463,22 +468,22 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ContentType")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<byte[]>("FileContents")
.HasColumnType("BLOB");
.HasColumnType("bytea");
b.Property<string>("FileName")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.HasKey("ID");
@ -487,18 +492,43 @@ namespace Remotely.Server.Migrations
b.ToTable("SharedFiles");
});
modelBuilder.Entity("Remotely.Shared.Models.UserDevicePermission", b =>
{
b.Property<string>("ID")
.HasColumnType("text");
b.Property<string>("DeviceGroupID")
.HasColumnType("text");
b.Property<string>("UserID")
.HasColumnType("text");
b.HasKey("ID");
b.HasIndex("DeviceGroupID");
b.HasIndex("UserID");
b.ToTable("PermissionLinks");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser");
b.Property<string>("DeviceGroupID")
.HasColumnType("text");
b.Property<bool>("IsAdministrator")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("UserOptions")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasIndex("DeviceGroupID");
b.HasIndex("OrganizationID");
@ -611,8 +641,23 @@ namespace Remotely.Server.Migrations
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.UserDevicePermission", b =>
{
b.HasOne("Remotely.Shared.Models.DeviceGroup", "DeviceGroup")
.WithMany("PermissionLinks")
.HasForeignKey("DeviceGroupID");
b.HasOne("Remotely.Shared.Models.RemotelyUser", "User")
.WithMany("PermissionLinks")
.HasForeignKey("UserID");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasOne("Remotely.Shared.Models.DeviceGroup", null)
.WithMany("Users")
.HasForeignKey("DeviceGroupID");
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("RemotelyUsers")
.HasForeignKey("OrganizationID");

View File

@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Remotely.Server.Migrations
{
@ -38,7 +39,7 @@ namespace Remotely.Server.Migrations
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
@ -54,6 +55,28 @@ namespace Remotely.Server.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ApiTokens",
columns: table => new
{
ID = table.Column<string>(nullable: false),
LastUsed = table.Column<DateTime>(nullable: true),
Name = table.Column<string>(maxLength: 200, nullable: true),
OrganizationID = table.Column<string>(nullable: true),
Secret = table.Column<string>(nullable: true),
Token = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiTokens", x => x.ID);
table.ForeignKey(
name: "FK_ApiTokens_Organizations_OrganizationID",
column: x => x.OrganizationID,
principalTable: "Organizations",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "CommandContexts",
columns: table => new
@ -144,41 +167,6 @@ namespace Remotely.Server.Migrations
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "RemotelyUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false),
Discriminator = table.Column<string>(nullable: false),
UserOptions = table.Column<string>(nullable: true),
OrganizationID = table.Column<string>(nullable: true),
IsAdministrator = table.Column<bool>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RemotelyUsers", x => x.Id);
table.ForeignKey(
name: "FK_RemotelyUsers_Organizations_OrganizationID",
column: x => x.OrganizationID,
principalTable: "Organizations",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "SharedFiles",
columns: table => new
@ -207,12 +195,14 @@ namespace Remotely.Server.Migrations
{
ID = table.Column<string>(nullable: false),
AgentVersion = table.Column<string>(nullable: true),
Alias = table.Column<string>(maxLength: 100, nullable: true),
CpuUtilization = table.Column<double>(nullable: false),
CurrentUser = table.Column<string>(nullable: true),
DeviceName = table.Column<string>(nullable: true),
DeviceGroupID = table.Column<string>(nullable: true),
DeviceName = table.Column<string>(nullable: true),
Drives = table.Column<string>(nullable: true),
FreeMemory = table.Column<double>(nullable: false),
FreeStorage = table.Column<double>(nullable: false),
UsedMemory = table.Column<double>(nullable: false),
UsedStorage = table.Column<double>(nullable: false),
Is64Bit = table.Column<bool>(nullable: false),
IsOnline = table.Column<bool>(nullable: false),
LastOnline = table.Column<DateTime>(nullable: false),
@ -243,12 +233,54 @@ namespace Remotely.Server.Migrations
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "RemotelyUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false),
Discriminator = table.Column<string>(nullable: false),
UserOptions = table.Column<string>(nullable: true),
OrganizationID = table.Column<string>(nullable: true),
IsAdministrator = table.Column<bool>(nullable: true),
DeviceGroupID = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RemotelyUsers", x => x.Id);
table.ForeignKey(
name: "FK_RemotelyUsers_DeviceGroups_DeviceGroupID",
column: x => x.DeviceGroupID,
principalTable: "DeviceGroups",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_RemotelyUsers_Organizations_OrganizationID",
column: x => x.OrganizationID,
principalTable: "Organizations",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
@ -328,6 +360,41 @@ namespace Remotely.Server.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "PermissionLinks",
columns: table => new
{
ID = table.Column<string>(nullable: false),
UserID = table.Column<string>(nullable: true),
DeviceGroupID = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PermissionLinks", x => x.ID);
table.ForeignKey(
name: "FK_PermissionLinks_DeviceGroups_DeviceGroupID",
column: x => x.DeviceGroupID,
principalTable: "DeviceGroups",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_PermissionLinks_RemotelyUsers_UserID",
column: x => x.UserID,
principalTable: "RemotelyUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_ApiTokens_OrganizationID",
table: "ApiTokens",
column: "OrganizationID");
migrationBuilder.CreateIndex(
name: "IX_ApiTokens_Token",
table: "ApiTokens",
column: "Token");
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
@ -369,6 +436,11 @@ namespace Remotely.Server.Migrations
table: "Devices",
column: "DeviceGroupID");
migrationBuilder.CreateIndex(
name: "IX_Devices_DeviceName",
table: "Devices",
column: "DeviceName");
migrationBuilder.CreateIndex(
name: "IX_Devices_OrganizationID",
table: "Devices",
@ -384,6 +456,16 @@ namespace Remotely.Server.Migrations
table: "InviteLinks",
column: "OrganizationID");
migrationBuilder.CreateIndex(
name: "IX_PermissionLinks_DeviceGroupID",
table: "PermissionLinks",
column: "DeviceGroupID");
migrationBuilder.CreateIndex(
name: "IX_PermissionLinks_UserID",
table: "PermissionLinks",
column: "UserID");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "RemotelyUsers",
@ -395,11 +477,21 @@ namespace Remotely.Server.Migrations
column: "NormalizedUserName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_RemotelyUsers_DeviceGroupID",
table: "RemotelyUsers",
column: "DeviceGroupID");
migrationBuilder.CreateIndex(
name: "IX_RemotelyUsers_OrganizationID",
table: "RemotelyUsers",
column: "OrganizationID");
migrationBuilder.CreateIndex(
name: "IX_RemotelyUsers_UserName",
table: "RemotelyUsers",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_SharedFiles_OrganizationID",
table: "SharedFiles",
@ -408,6 +500,9 @@ namespace Remotely.Server.Migrations
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApiTokens");
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
@ -435,6 +530,9 @@ namespace Remotely.Server.Migrations
migrationBuilder.DropTable(
name: "InviteLinks");
migrationBuilder.DropTable(
name: "PermissionLinks");
migrationBuilder.DropTable(
name: "SharedFiles");

View File

@ -3,6 +3,7 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Remotely.Server.Data;
namespace Remotely.Server.Migrations
@ -14,23 +15,25 @@ namespace Remotely.Server.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.1");
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.2")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.HasKey("Id");
@ -46,17 +49,18 @@ namespace Remotely.Server.Migrations
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("Id");
@ -68,57 +72,57 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Discriminator")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.HasKey("Id");
@ -139,17 +143,18 @@ namespace Remotely.Server.Migrations
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
.HasColumnType("integer")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("Id");
@ -161,19 +166,19 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
@ -185,10 +190,10 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
@ -200,18 +205,18 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(128)")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("UserId", "LoginProvider", "Name");
@ -221,23 +226,23 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.ApiToken", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime?>("LastUsed")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(200)")
.HasMaxLength(200);
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Secret")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Token")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("ID");
@ -251,34 +256,34 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.CommandContext", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("CommandMode")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("CommandResults")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("CommandText")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("PSCoreResults")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("SenderConnectionID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("SenderUserID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("TargetDeviceIDs")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.HasKey("ID");
@ -290,72 +295,72 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.Device", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("AgentVersion")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Alias")
.HasColumnType("TEXT")
.HasColumnType("character varying(100)")
.HasMaxLength(100);
b.Property<double>("CpuUtilization")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<string>("CurrentUser")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("DeviceGroupID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("DeviceName")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Drives")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("Is64Bit")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<bool>("IsOnline")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<DateTime>("LastOnline")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.Property<int>("OSArchitecture")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("OSDescription")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Platform")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<int>("ProcessorCount")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("ServerVerificationToken")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Tags")
.HasColumnType("TEXT")
.HasColumnType("character varying(200)")
.HasMaxLength(200);
b.Property<double>("TotalMemory")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<double>("TotalStorage")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<double>("UsedMemory")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.Property<double>("UsedStorage")
.HasColumnType("REAL");
.HasColumnType("double precision");
b.HasKey("ID");
@ -371,14 +376,14 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.DeviceGroup", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnType("character varying(200)")
.HasMaxLength(200);
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("ID");
@ -390,25 +395,25 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.EventLog", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<int>("EventType")
.HasColumnType("INTEGER");
.HasColumnType("integer");
b.Property<string>("Message")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("Source")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("StackTrace")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("TimeStamp")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.HasKey("ID");
@ -420,22 +425,22 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.InviteLink", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("DateSent")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.Property<string>("InvitedUser")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ResetUrl")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasKey("ID");
@ -447,10 +452,10 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.Organization", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationName")
.HasColumnType("TEXT")
.HasColumnType("character varying(25)")
.HasMaxLength(25);
b.HasKey("ID");
@ -461,22 +466,22 @@ namespace Remotely.Server.Migrations
modelBuilder.Entity("Remotely.Shared.Models.SharedFile", b =>
{
b.Property<string>("ID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("ContentType")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<byte[]>("FileContents")
.HasColumnType("BLOB");
.HasColumnType("bytea");
b.Property<string>("FileName")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
.HasColumnType("timestamp without time zone");
b.HasKey("ID");
@ -485,18 +490,43 @@ namespace Remotely.Server.Migrations
b.ToTable("SharedFiles");
});
modelBuilder.Entity("Remotely.Shared.Models.UserDevicePermission", b =>
{
b.Property<string>("ID")
.HasColumnType("text");
b.Property<string>("DeviceGroupID")
.HasColumnType("text");
b.Property<string>("UserID")
.HasColumnType("text");
b.HasKey("ID");
b.HasIndex("DeviceGroupID");
b.HasIndex("UserID");
b.ToTable("PermissionLinks");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser");
b.Property<string>("DeviceGroupID")
.HasColumnType("text");
b.Property<bool>("IsAdministrator")
.HasColumnType("INTEGER");
.HasColumnType("boolean");
b.Property<string>("OrganizationID")
.HasColumnType("TEXT");
.HasColumnType("text");
b.Property<string>("UserOptions")
.HasColumnType("TEXT");
.HasColumnType("text");
b.HasIndex("DeviceGroupID");
b.HasIndex("OrganizationID");
@ -609,8 +639,23 @@ namespace Remotely.Server.Migrations
.HasForeignKey("OrganizationID");
});
modelBuilder.Entity("Remotely.Shared.Models.UserDevicePermission", b =>
{
b.HasOne("Remotely.Shared.Models.DeviceGroup", "DeviceGroup")
.WithMany("PermissionLinks")
.HasForeignKey("DeviceGroupID");
b.HasOne("Remotely.Shared.Models.RemotelyUser", "User")
.WithMany("PermissionLinks")
.HasForeignKey("UserID");
});
modelBuilder.Entity("Remotely.Shared.Models.RemotelyUser", b =>
{
b.HasOne("Remotely.Shared.Models.DeviceGroup", null)
.WithMany("Users")
.HasForeignKey("DeviceGroupID");
b.HasOne("Remotely.Shared.Models.Organization", "Organization")
.WithMany("RemotelyUsers")
.HasForeignKey("OrganizationID");

View File

@ -10,6 +10,7 @@
<Platforms>AnyCPU;x86;x64</Platforms>
<RootNamespace>Remotely.Server</RootNamespace>
<AssemblyName>Remotely_Server</AssemblyName>
<UserSecretsId>36e32491-91a5-42e6-a466-819233f0c593</UserSecretsId>
</PropertyGroup>
<ItemGroup>

View File

@ -267,7 +267,7 @@ namespace Remotely.Server.Services
x.ID == deviceGroupId &&
x.OrganizationID == orgID);
deviceGroup.Devices.ForEach(x =>
deviceGroup.Devices?.ForEach(x =>
{
x.DeviceGroup = null;
});

View File

@ -30,6 +30,8 @@ using Microsoft.Extensions.Hosting;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.OpenApi.Models;
using Remotely.Server.Auth;
using Microsoft.Data.SqlClient;
using Npgsql;
namespace Remotely.Server
{
@ -69,9 +71,17 @@ namespace Remotely.Server
}
else if (dbProvider == "postgresql")
{
var connectionBuilder = new NpgsqlConnectionStringBuilder(Configuration.GetConnectionString("PostgreSQL"));
// Password needs to be set in User Secrets in dev environment.
// See https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.1
if (IsDev)
{
connectionBuilder.Password = Configuration["PostgresPassword"];
}
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(
Configuration.GetConnectionString("PostgreSQL")));
options.UseNpgsql(connectionBuilder.ConnectionString));
}
services.AddIdentity<RemotelyUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)

View File

@ -2,7 +2,7 @@
"ConnectionStrings": {
"SQLite": "DataSource=Server.db",
"SQLServer": "Server=(localdb)\\mssqllocaldb;Database=Remotely-Server-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true",
"PostgreSQL": "Host=localhost;Database=;Username=;Password="
"PostgreSQL": "Host=localhost;Database=Remotely;Username=postgres;"
},
"Logging": {
"LogLevel": {
@ -13,7 +13,7 @@
"AllowApiLogin": false,
"MaxOrganizationCount": 1,
"DataRetentionInDays": 90,
"DBProvider": "SQLite",
"DBProvider": "PostgreSQL",
"DefaultPrompt": "~>",
"EnableWindowsEventLog": false,
"KnownProxies": [],
@ -21,7 +21,7 @@
"RedirectToHttps": false,
"RemoteControlSessionLimit": 1,
"RemoteControlRequiresAuthentication": true,
"Require2FA": false,
"Require2FA": false,
"SmtpHost": "",
"SmtpPort": 25,
"SmtpUserName": "",
@ -32,6 +32,6 @@
"TrustedCorsOrigins": [],
"Theme": "Light",
"UseHsts": false,
"UseWebRtc": true
"UseWebRtc": true
}
}

View File

@ -13,6 +13,19 @@ document.getElementById("invitesHelpButton").addEventListener("click", (ev) => {
document.getElementById("deviceGroupHelpButton").addEventListener("click", (ev) => {
ShowModal("Device Groups", `Device groups can be used to organize and filter computers on the grid.`);
});
document.getElementById("addUsersToDeviceGroupButton").addEventListener("click", (ev) => {
var selectList = document.getElementById("deviceGroupList");
if (selectList.selectedOptions.length == 0) {
return;
}
if (selectList.selectedOptions.length > 1) {
ShowModal("Device Group Users", "You can only edit users for 1 device group at a time.");
return;
}
var groupID = selectList.selectedOptions[0].value;
var modalDiv = document.querySelector(`.modal[group='${groupID}']`);
$(modalDiv).modal("show");
});
document.getElementById("removeDeviceGroupButton").addEventListener("click", (ev) => {
var selectList = document.getElementById("deviceGroupList");
for (var i = 0; i < selectList.selectedOptions.length; i++) {
@ -45,33 +58,20 @@ document.getElementById("deviceGroupInput").addEventListener("keypress", (e) =>
document.getElementById("addDeviceGroupButton").click();
}
});
document.getElementById("addDeviceGroupButton").addEventListener("click", () => {
var input = document.getElementById("deviceGroupInput");
if (input.checkValidity() && input.value.length > 0) {
var xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status == 200) {
document.querySelectorAll(`.all-device-groups-list`).forEach((list) => {
var newOption = new Option(input.value, xhr.responseText);
list.options.add(newOption);
});
input.value = "";
}
else if (xhr.status == 400) {
ShowModal("Invalid Request", xhr.responseText);
}
else {
showError(xhr);
}
};
xhr.onerror = () => {
showError(xhr);
};
xhr.open("post", location.origin + "/api/OrganizationManagement/DeviceGroup");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify({ Name: input.value }));
document.querySelectorAll(".remove-user-from-device-group-button").forEach((x) => {
var groupID = x.getAttribute("group");
var selectList = document.querySelector(`.modal[group='${groupID}'] select.device-group-user-list`);
for (var i = 0; i < selectList.selectedOptions.length; i++) {
// TODO: XHR to remove users and remove from list.
}
});
document.querySelectorAll(".add-user-to-devicegroup-button").forEach((x) => {
var groupID = x.getAttribute("group");
var modal = document.querySelector(`.modal[group='${groupID}']`);
var selectList = modal.querySelector(`select.device-group-user-list`);
var userInput = modal.querySelector(`input.add-user-to-devicegroup-input`);
// TODO: XHR to add user to group and add to select list.
});
document.getElementById("organizationNameInput").addEventListener("input", (ev) => {
var addon = ev.currentTarget.parentElement.querySelector(".fa");
addon.classList.remove("fa-check-circle");

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,21 @@ document.getElementById("deviceGroupHelpButton").addEventListener("click", (ev)
});
document.getElementById("addUsersToDeviceGroupButton").addEventListener("click", (ev) => {
var selectList = document.getElementById("deviceGroupList") as HTMLSelectElement;
if (selectList.selectedOptions.length == 0) {
return;
}
if (selectList.selectedOptions.length > 1) {
ShowModal("Device Group Users", "You can only edit users for 1 device group at a time.");
return;
}
var groupID = selectList.selectedOptions[0].value;
var modalDiv = document.querySelector(`.modal[group='${groupID}']`) as HTMLDivElement;
$(modalDiv).modal("show");
});
document.getElementById("removeDeviceGroupButton").addEventListener("click", (ev) => {
var selectList = document.getElementById("deviceGroupList") as HTMLSelectElement;
for (var i = 0; i < selectList.selectedOptions.length; i++) {
@ -51,34 +66,22 @@ document.getElementById("deviceGroupInput").addEventListener("keypress", (e) =>
document.getElementById("addDeviceGroupButton").click();
}
})
document.getElementById("addDeviceGroupButton").addEventListener("click", () => {
var input = document.getElementById("deviceGroupInput") as HTMLInputElement;
if (input.checkValidity() && input.value.length > 0) {
var xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status == 200) {
document.querySelectorAll(`.all-device-groups-list`).forEach((list: HTMLSelectElement) => {
var newOption = new Option(input.value, xhr.responseText);
list.options.add(newOption);
})
input.value = "";
}
else if (xhr.status == 400) {
ShowModal("Invalid Request", xhr.responseText);
}
else {
showError(xhr);
}
}
xhr.onerror = () => {
showError(xhr);
}
xhr.open("post", location.origin + "/api/OrganizationManagement/DeviceGroup");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify({ Name: input.value }));
document.querySelectorAll(".remove-user-from-device-group-button").forEach((x:HTMLElement) => {
var groupID = x.getAttribute("group");
var selectList = document.querySelector(`.modal[group='${groupID}'] select.device-group-user-list`) as HTMLSelectElement;
for (var i = 0; i < selectList.selectedOptions.length; i++) {
// TODO: XHR to remove users and remove from list.
}
})
});
document.querySelectorAll(".add-user-to-devicegroup-button").forEach((x: HTMLElement) => {
var groupID = x.getAttribute("group");
var modal = document.querySelector(`.modal[group='${groupID}']`);
var selectList = modal.querySelector(`select.device-group-user-list`) as HTMLSelectElement;
var userInput = modal.querySelector(`input.add-user-to-devicegroup-input`);
// TODO: XHR to add user to group and add to select list.
});
document.getElementById("organizationNameInput").addEventListener("input", (ev) => {
var addon = (ev.currentTarget as HTMLInputElement).parentElement.querySelector(".fa");

View File

@ -17,7 +17,7 @@ namespace Remotely.Shared.Models
public string Name { get; set; }
[JsonIgnore]
public virtual Organization Organization { get; set; }
public Organization Organization { get; set; }
public string OrganizationID { get; set; }
public string Secret { get; set; }

View File

@ -19,7 +19,7 @@ namespace Remotely.Shared.Models
public ICollection<GenericCommandResult> CommandResults { get; set; } = new List<GenericCommandResult>();
public DateTime TimeStamp { get; set; } = DateTime.Now;
[JsonIgnore]
public virtual Organization Organization { get; set; }
public Organization Organization { get; set; }
public string OrganizationID { get; set; }
}
}

View File

@ -19,7 +19,7 @@ namespace Remotely.Shared.Models
public string Alias { get; set; }
public double CpuUtilization { get; set; }
public string CurrentUser { get; set; }
public virtual DeviceGroup DeviceGroup { get; set; }
public DeviceGroup DeviceGroup { get; set; }
public string DeviceGroupID { get; set; }
public string DeviceName { get; set; }
public List<Drive> Drives { get; set; }
@ -37,7 +37,7 @@ namespace Remotely.Shared.Models
public DateTime LastOnline { get; set; }
[JsonIgnore]
public virtual Organization Organization { get; set; }
public Organization Organization { get; set; }
public string OrganizationID { get; set; }
public Architecture OSArchitecture { get; set; }

View File

@ -14,11 +14,15 @@ namespace Remotely.Shared.Models
[Key]
public string ID { get; set; } = Guid.NewGuid().ToString();
public virtual List<Device> Devices { get; set; }
public List<Device> Devices { get; set; }
public List<RemotelyUser> Users { get; set; }
[JsonIgnore]
public Organization Organization { get; set; }
public string OrganizationID { get; set; }
public List<UserDevicePermission> PermissionLinks { get; set; }
}
}

View File

@ -17,7 +17,7 @@ namespace Remotely.Shared.Models
public string OrganizationID { get; set; }
public DateTime TimeStamp { get; set; } = DateTime.Now;
[JsonIgnore]
public virtual Organization Organization { get; set; }
public Organization Organization { get; set; }
}
public enum EventType
{

View File

@ -14,7 +14,7 @@ namespace Remotely.Shared.Models
public bool IsAdmin { get; set; }
public DateTime DateSent { get; set; }
[JsonIgnore]
public virtual Organization Organization { get; set; }
public Organization Organization { get; set; }
public string OrganizationID { get; set; }
public string ResetUrl { get; set; }
}

View File

@ -7,24 +7,24 @@ namespace Remotely.Shared.Models
{
public class Organization
{
public virtual ICollection<ApiToken> ApiTokens { get; set; }
public ICollection<ApiToken> ApiTokens { get; set; }
public virtual ICollection<CommandContext> CommandContexts { get; set; }
public ICollection<CommandContext> CommandContexts { get; set; }
public virtual ICollection<DeviceGroup> DeviceGroups { get; set; }
public ICollection<DeviceGroup> DeviceGroups { get; set; }
public virtual ICollection<Device> Devices { get; set; }
public ICollection<Device> Devices { get; set; }
public virtual ICollection<EventLog> EventLogs { get; set; }
public ICollection<EventLog> EventLogs { get; set; }
[Key]
public string ID { get; set; } = Guid.NewGuid().ToString();
public virtual ICollection<InviteLink> InviteLinks { get; set; }
public ICollection<InviteLink> InviteLinks { get; set; }
[StringLength(25)]
public string OrganizationName { get; set; }
public virtual ICollection<RemotelyUser> RemotelyUsers { get; set; }
public virtual ICollection<SharedFile> SharedFiles { get; set; }
public ICollection<RemotelyUser> RemotelyUsers { get; set; }
public ICollection<SharedFile> SharedFiles { get; set; }
}
}

View File

@ -18,9 +18,11 @@ namespace Remotely.Shared.Models
public RemotelyUserOptions UserOptions { get; set; }
[JsonIgnore]
public virtual Organization Organization { get; set; }
public Organization Organization { get; set; }
public string OrganizationID { get; set; }
public bool IsAdministrator { get; set; } = true;
public List<UserDevicePermission> PermissionLinks { get; set; }
}
}

View File

@ -13,7 +13,7 @@ namespace Remotely.Shared.Models
public string ContentType { get; set; }
public byte[] FileContents { get; set; }
public DateTime Timestamp { get; set; } = DateTime.Now;
public virtual Organization Organization { get; set; }
public Organization Organization { get; set; }
public string OrganizationID { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace Remotely.Shared.Models
{
public class UserDevicePermission
{
[Key]
public string ID { get; set; } = Guid.NewGuid().ToString();
public string UserID { get; set; }
public RemotelyUser User { get; set; }
public string DeviceGroupID { get; set; }
public DeviceGroup DeviceGroup { get; set; }
}
}