From 78dd2796ebd18d12d84289006c53d6ef104b35d3 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 13 Sep 2023 18:26:06 -0500 Subject: [PATCH] WIP DHCP server --- .../Components/NetworkInterfacePicker.razor | 70 + LANCommander/Data/DatabaseContext.cs | 2 + LANCommander/Data/Models/DHCPLease.cs | 15 + LANCommander/LANCommander.csproj | 2 + .../20230913221051_AddDHCPLeases.Designer.cs | 1484 +++++++++++++++++ .../20230913221051_AddDHCPLeases.cs | 59 + .../DatabaseContextModelSnapshot.cs | 50 + LANCommander/Models/Settings.cs | 20 +- .../Pages/Settings/DHCP/Configuration.razor | 90 + LANCommander/Pages/Settings/DHCP/Leases.razor | 43 + .../Pages/Settings/SettingsLayout.razor | 4 + LANCommander/Program.cs | 1 + LANCommander/Services/DHCPService.cs | 157 ++ LANCommander/wwwroot/css/site.css | 27 + 14 files changed, 2023 insertions(+), 1 deletion(-) create mode 100644 LANCommander/Components/NetworkInterfacePicker.razor create mode 100644 LANCommander/Data/Models/DHCPLease.cs create mode 100644 LANCommander/Migrations/20230913221051_AddDHCPLeases.Designer.cs create mode 100644 LANCommander/Migrations/20230913221051_AddDHCPLeases.cs create mode 100644 LANCommander/Pages/Settings/DHCP/Configuration.razor create mode 100644 LANCommander/Pages/Settings/DHCP/Leases.razor create mode 100644 LANCommander/Services/DHCPService.cs diff --git a/LANCommander/Components/NetworkInterfacePicker.razor b/LANCommander/Components/NetworkInterfacePicker.razor new file mode 100644 index 00000000..ca751f8f --- /dev/null +++ b/LANCommander/Components/NetworkInterfacePicker.razor @@ -0,0 +1,70 @@ +@using System.Net.NetworkInformation + + + + @foreach (var nic in NetworkInterfaces) + { + + + + + + + + @switch (nic.NetworkInterfaceType) + { + case NetworkInterfaceType.Wireless80211: + case NetworkInterfaceType.Wwanpp: + case NetworkInterfaceType.Wwanpp2: + case NetworkInterfaceType.Wman: + + break; + + case NetworkInterfaceType.Loopback: + + break; + + default: + + break; + } + + + @nic.Name +
+ @nic.Description +
+
+ +
+ +
+
+
Media state:
+
@nic.OperationalStatus
+
+
+
Bytes sent:
+
@ByteSizeLib.ByteSize.FromBytes(nic.GetIPStatistics().BytesSent).ToString()
+
+
+
Bytes received:
+
@ByteSizeLib.ByteSize.FromBytes(nic.GetIPStatistics().BytesReceived).ToString()
+
+
+
Link speed:
+
@(nic.Speed / 1000000) (Mbps)
+
+
+
+
+ } +
+
+ +@code { + [Parameter] public string Value { get; set; } + [Parameter] public EventCallback ValueChanged { get; set; } + + IEnumerable NetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); +} diff --git a/LANCommander/Data/DatabaseContext.cs b/LANCommander/Data/DatabaseContext.cs index 7a80419d..50191159 100644 --- a/LANCommander/Data/DatabaseContext.cs +++ b/LANCommander/Data/DatabaseContext.cs @@ -123,5 +123,7 @@ namespace LANCommander.Data public DbSet? Servers { get; set; } public DbSet? ServerConsoles { get; set; } + + public DbSet? DHCPLeases { get; set; } } } \ No newline at end of file diff --git a/LANCommander/Data/Models/DHCPLease.cs b/LANCommander/Data/Models/DHCPLease.cs new file mode 100644 index 00000000..8c0aa7d6 --- /dev/null +++ b/LANCommander/Data/Models/DHCPLease.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LANCommander.Data.Models +{ + [Table("DHCPLeases")] + public class DHCPLease : BaseModel + { + [Display(Name = "MAC Address")] + public byte[] MACAddress { get; set; } + + [Display(Name = "IP Address")] + public byte[] IPAddress { get; set; } + } +} diff --git a/LANCommander/LANCommander.csproj b/LANCommander/LANCommander.csproj index c001d950..b35b9c19 100644 --- a/LANCommander/LANCommander.csproj +++ b/LANCommander/LANCommander.csproj @@ -28,6 +28,7 @@ + @@ -51,6 +52,7 @@ + diff --git a/LANCommander/Migrations/20230913221051_AddDHCPLeases.Designer.cs b/LANCommander/Migrations/20230913221051_AddDHCPLeases.Designer.cs new file mode 100644 index 00000000..df82d164 --- /dev/null +++ b/LANCommander/Migrations/20230913221051_AddDHCPLeases.Designer.cs @@ -0,0 +1,1484 @@ +// +using System; +using LANCommander.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LANCommander.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230913221051_AddDHCPLeases")] + partial class AddDHCPLeases + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true); + + modelBuilder.Entity("CategoryGame", b => + { + b.Property("CategoriesId") + .HasColumnType("TEXT"); + + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.HasKey("CategoriesId", "GamesId"); + + b.HasIndex("GamesId"); + + b.ToTable("CategoryGame"); + }); + + modelBuilder.Entity("GameDeveloper", b => + { + b.Property("DeveloperId") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.HasKey("DeveloperId", "GameId"); + + b.HasIndex("GameId"); + + b.ToTable("GameDeveloper"); + }); + + modelBuilder.Entity("GameGenre", b => + { + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.Property("GenresId") + .HasColumnType("TEXT"); + + b.HasKey("GamesId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("GameGenre"); + }); + + modelBuilder.Entity("GamePublisher", b => + { + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("PublisherId") + .HasColumnType("TEXT"); + + b.HasKey("GameId", "PublisherId"); + + b.HasIndex("PublisherId"); + + b.ToTable("GamePublisher"); + }); + + modelBuilder.Entity("GameTag", b => + { + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.Property("TagsId") + .HasColumnType("TEXT"); + + b.HasKey("GamesId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("GameTag"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Action", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Arguments") + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PrimaryAction") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("WorkingDirectory") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Actions"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Archive", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Changelog") + .HasColumnType("TEXT"); + + b.Property("CompressedSize") + .HasColumnType("INTEGER"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("LastVersionId") + .HasColumnType("TEXT"); + + b.Property("ObjectKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UncompressedSize") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("LastVersionId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Archive"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ParentId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Company", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Companies"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.DHCPLease", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("IPAddress") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("MACAddress") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("DHCPLeases"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Game", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("DirectoryName") + .HasColumnType("TEXT"); + + b.Property("IGDBId") + .HasColumnType("INTEGER"); + + b.Property("Icon") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReleasedOn") + .HasColumnType("TEXT"); + + b.Property("Singleplayer") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("ValidKeyRegex") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Games"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.GameSave", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.HasIndex("UserId"); + + b.ToTable("GameSaves"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Key", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AllocationMethod") + .HasColumnType("INTEGER"); + + b.Property("ClaimedByComputerName") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("ClaimedByIpv4Address") + .HasMaxLength(15) + .HasColumnType("TEXT"); + + b.Property("ClaimedByMacAddress") + .HasMaxLength(17) + .HasColumnType("TEXT"); + + b.Property("ClaimedByUserId") + .HasColumnType("TEXT"); + + b.Property("ClaimedOn") + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClaimedByUserId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Keys"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("GameId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("MaxPlayers") + .HasColumnType("INTEGER"); + + b.Property("MinPlayers") + .HasColumnType("INTEGER"); + + b.Property("NetworkProtocol") + .HasColumnType("INTEGER"); + + b.Property("Spectators") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("MultiplayerModes"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("LANCommander.Data.Models.SavePath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("SavePaths"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Script", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Contents") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("GameId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RequiresAdmin") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Scripts"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Arguments") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Autostart") + .HasColumnType("INTEGER"); + + b.Property("AutostartDelay") + .HasColumnType("INTEGER"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OnStartScriptPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OnStopScriptPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("UseShellExecute") + .HasColumnType("INTEGER"); + + b.Property("WorkingDirectory") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("GameId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.ServerConsole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Host") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ServerId1") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ServerId"); + + b.HasIndex("ServerId1"); + + b.HasIndex("UpdatedById"); + + b.ToTable("ServerConsoles"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("ApprovedOn") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RefreshToken") + .HasColumnType("TEXT"); + + b.Property("RefreshTokenExpiration") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("CategoryGame", b => + { + b.HasOne("LANCommander.Data.Models.Category", null) + .WithMany() + .HasForeignKey("CategoriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GamesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameDeveloper", b => + { + b.HasOne("LANCommander.Data.Models.Company", null) + .WithMany() + .HasForeignKey("DeveloperId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameGenre", b => + { + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GamesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GamePublisher", b => + { + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Company", null) + .WithMany() + .HasForeignKey("PublisherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameTag", b => + { + b.HasOne("LANCommander.Data.Models.Game", null) + .WithMany() + .HasForeignKey("GamesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Action", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Actions") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Archive", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Archives") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Archive", "LastVersion") + .WithMany() + .HasForeignKey("LastVersionId"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("LastVersion"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Category", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Category", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Parent"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Company", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.DHCPLease", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Game", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.GameSave", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("GameSaves") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.HasOne("LANCommander.Data.Models.User", "User") + .WithMany("GameSaves") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Genre", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Key", b => + { + b.HasOne("LANCommander.Data.Models.User", "ClaimedByUser") + .WithMany() + .HasForeignKey("ClaimedByUserId"); + + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Keys") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("ClaimedByUser"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.MultiplayerMode", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("MultiplayerModes") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.SavePath", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("SavePaths") + .HasForeignKey("GameId"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Script", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Scripts") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Server", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Game", "Game") + .WithMany("Servers") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Game"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.ServerConsole", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.Server", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.Server", null) + .WithMany("ServerConsoles") + .HasForeignKey("ServerId1"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Server"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Tag", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("LANCommander.Data.Models.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("LANCommander.Data.Models.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("LANCommander.Data.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Category", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Game", b => + { + b.Navigation("Actions"); + + b.Navigation("Archives"); + + b.Navigation("GameSaves"); + + b.Navigation("Keys"); + + b.Navigation("MultiplayerModes"); + + b.Navigation("SavePaths"); + + b.Navigation("Scripts"); + + b.Navigation("Servers"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.Server", b => + { + b.Navigation("ServerConsoles"); + }); + + modelBuilder.Entity("LANCommander.Data.Models.User", b => + { + b.Navigation("GameSaves"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LANCommander/Migrations/20230913221051_AddDHCPLeases.cs b/LANCommander/Migrations/20230913221051_AddDHCPLeases.cs new file mode 100644 index 00000000..5ba129c1 --- /dev/null +++ b/LANCommander/Migrations/20230913221051_AddDHCPLeases.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LANCommander.Migrations +{ + /// + public partial class AddDHCPLeases : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DHCPLeases", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + MACAddress = table.Column(type: "BLOB", nullable: false), + IPAddress = table.Column(type: "BLOB", nullable: false), + CreatedOn = table.Column(type: "TEXT", nullable: false), + CreatedById = table.Column(type: "TEXT", nullable: true), + UpdatedOn = table.Column(type: "TEXT", nullable: false), + UpdatedById = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DHCPLeases", x => x.Id); + table.ForeignKey( + name: "FK_DHCPLeases_AspNetUsers_CreatedById", + column: x => x.CreatedById, + principalTable: "AspNetUsers", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_DHCPLeases_AspNetUsers_UpdatedById", + column: x => x.UpdatedById, + principalTable: "AspNetUsers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_DHCPLeases_CreatedById", + table: "DHCPLeases", + column: "CreatedById"); + + migrationBuilder.CreateIndex( + name: "IX_DHCPLeases_UpdatedById", + table: "DHCPLeases", + column: "UpdatedById"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DHCPLeases"); + } + } +} diff --git a/LANCommander/Migrations/DatabaseContextModelSnapshot.cs b/LANCommander/Migrations/DatabaseContextModelSnapshot.cs index a10cfaaf..6dc4f98d 100644 --- a/LANCommander/Migrations/DatabaseContextModelSnapshot.cs +++ b/LANCommander/Migrations/DatabaseContextModelSnapshot.cs @@ -268,6 +268,41 @@ namespace LANCommander.Migrations b.ToTable("Companies"); }); + modelBuilder.Entity("LANCommander.Data.Models.DHCPLease", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("IPAddress") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("MACAddress") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("UpdatedById") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("DHCPLeases"); + }); + modelBuilder.Entity("LANCommander.Data.Models.Game", b => { b.Property("Id") @@ -1120,6 +1155,21 @@ namespace LANCommander.Migrations b.Navigation("UpdatedBy"); }); + modelBuilder.Entity("LANCommander.Data.Models.DHCPLease", b => + { + b.HasOne("LANCommander.Data.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("LANCommander.Data.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + modelBuilder.Entity("LANCommander.Data.Models.Game", b => { b.HasOne("LANCommander.Data.Models.User", "CreatedBy") diff --git a/LANCommander/Models/Settings.cs b/LANCommander/Models/Settings.cs index 40021999..e1184b86 100644 --- a/LANCommander/Models/Settings.cs +++ b/LANCommander/Models/Settings.cs @@ -1,4 +1,6 @@ -namespace LANCommander.Models +using DotNetProjects.DhcpServer; + +namespace LANCommander.Models { public enum LANCommanderTheme { @@ -18,6 +20,7 @@ public LANCommanderAuthenticationSettings Authentication { get; set; } = new LANCommanderAuthenticationSettings(); public LANCommanderArchiveSettings Archives { get; set; } = new LANCommanderArchiveSettings(); public LANCommanderIPXRelaySettings IPXRelay { get; set; } = new LANCommanderIPXRelaySettings(); + public LANCommanderDHCPServerSettings DHCPServer { get; set; } = new LANCommanderDHCPServerSettings(); } public class LANCommanderAuthenticationSettings @@ -44,4 +47,19 @@ public int Port { get; set; } = 213; public bool Logging { get; set; } = false; } + + public class LANCommanderDHCPServerSettings + { + public bool Enabled { get; set; } = false; + public string NetworkInterface { get; set; } + public string SubnetMask { get; set; } = "255.255.255.0"; + public string Network { get; set; } = "10.0.0.0"; + public string RangeStart { get; set; } = "10.0.0.30"; + public string RangeEnd { get; set; } = "10.0.0.254"; + public string Domain { get; set; } = "LANCommander"; + public string ServerIdentifier { get; set; } = "10.0.0.1"; + public string DefaultGateway = "10.0.0.1"; + public string[] DnsServers = new string[] { "10.0.0.1" }; + public NetworkRoute[] StaticRoutes; + } } diff --git a/LANCommander/Pages/Settings/DHCP/Configuration.razor b/LANCommander/Pages/Settings/DHCP/Configuration.razor new file mode 100644 index 00000000..524c3800 --- /dev/null +++ b/LANCommander/Pages/Settings/DHCP/Configuration.razor @@ -0,0 +1,90 @@ +@page "/Settings/DHCP" +@page "/Settings/DHCP/Configuration" +@using LANCommander.Models; +@layout SettingsLayout +@inject SettingService SettingService +@inject DHCPService DHCPService +@inject IMessageService MessageService +@attribute [Authorize(Roles = "Administrator")] + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +@code { + LANCommanderSettings Settings; + + Form Form; + + FormValidationRule IPAddressValidationRule = new FormValidationRule + { + Type = FormFieldType.Regexp, + Pattern = "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$", + Message = "Not a valid IP address" + }; + + protected override async Task OnInitializedAsync() + { + Settings = SettingService.GetSettings(); + } + + private void Save() + { + try + { + if (Form.Validate()) + { + SettingService.SaveSettings(Settings); + MessageService.Success("Settings saved!"); + + DHCPService.Init(); + } + } + catch + { + MessageService.Error("An unknown error occurred."); + } + } +} diff --git a/LANCommander/Pages/Settings/DHCP/Leases.razor b/LANCommander/Pages/Settings/DHCP/Leases.razor new file mode 100644 index 00000000..a706684a --- /dev/null +++ b/LANCommander/Pages/Settings/DHCP/Leases.razor @@ -0,0 +1,43 @@ +@page "/Settings/DHCP/Leases" +@using LANCommander.Models; +@using System.Net; +@layout SettingsLayout +@inject SettingService SettingService +@inject DHCPService DHCPService +@inject IMessageService MessageService +@attribute [Authorize(Roles = "Administrator")] + + + +
+ + + @(new IPAddress(context.IPAddress)) + + + @Convert.ToHexString(context.MACAddress) + + +
+
+ + @code { + ICollection DHCPLeases; + + bool Loading = true; + + protected override async Task OnInitializedAsync() + { + //DHCPLeases = await DHCPService.Get(); + Loading = false; + } + + async Task Release(DHCPLease lease) + { + //await DHCPService.Delete(lease); + + DHCPLeases.Remove(lease); + + StateHasChanged(); + } +} diff --git a/LANCommander/Pages/Settings/SettingsLayout.razor b/LANCommander/Pages/Settings/SettingsLayout.razor index 6386acdf..4aa23385 100644 --- a/LANCommander/Pages/Settings/SettingsLayout.razor +++ b/LANCommander/Pages/Settings/SettingsLayout.razor @@ -10,6 +10,10 @@ Authentication Archives IPX Relay + + Configuration + Leases + Tools diff --git a/LANCommander/Program.cs b/LANCommander/Program.cs index 15e56970..342a166b 100644 --- a/LANCommander/Program.cs +++ b/LANCommander/Program.cs @@ -127,6 +127,7 @@ builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); if (settings.Beacon) { diff --git a/LANCommander/Services/DHCPService.cs b/LANCommander/Services/DHCPService.cs new file mode 100644 index 00000000..50933080 --- /dev/null +++ b/LANCommander/Services/DHCPService.cs @@ -0,0 +1,157 @@ +using DotNetProjects.DhcpServer; +using LANCommander.Data; +using LANCommander.Data.Models; +using NetworkPrimitives.Ipv4; +using System.Collections.Concurrent; +using System.Net; +using System.Net.NetworkInformation; + +namespace LANCommander.Services +{ + public class DHCPService + { + private DHCPServer Server { get; set; } + + private Dictionary Leases { get; set; } = new Dictionary(); + + private string NetworkInterfaceId { get; set; } + private IPAddress SubnetMask { get; set; } + private IPAddress Network { get; set; } + private IPAddress RangeStart { get; set; } + private IPAddress RangeEnd { get; set; } + private string Domain { get; set; } + private IPAddress ServerIdentifier { get; set; } + private IPAddress DefaultGateway { get; set; } + private IEnumerable DnsServers { get; set; } + + public DHCPService() + { + Init(); + } + + public void Init() + { + var settings = SettingService.GetSettings(); + + NetworkInterfaceId = settings.DHCPServer.NetworkInterface; + SubnetMask = IPAddress.Parse(settings.DHCPServer.SubnetMask); + Network = IPAddress.Parse(settings.DHCPServer.Network); + RangeStart = IPAddress.Parse(settings.DHCPServer.RangeStart); + RangeEnd = IPAddress.Parse(settings.DHCPServer.RangeEnd); + Domain = settings.DHCPServer.Domain; + ServerIdentifier = IPAddress.Parse(settings.DHCPServer.ServerIdentifier); + DefaultGateway = IPAddress.Parse(settings.DHCPServer.DefaultGateway); + DnsServers = settings.DHCPServer.DnsServers.Select(IPAddress.Parse); + + // Load stored leases + // Leases = new ConcurrentBag(await Get()); + + if (Server != null) + Server.Dispose(); + + if (settings.DHCPServer.Enabled) + Start(); + } + + public void Start() + { + Server = new DHCPServer(); + + Server.ServerName = "LANCommander"; + Server.OnDataReceived += OnRequest; + Server.BroadcastAddress = IPAddress.Broadcast; + + Server.SendDhcpAnswerNetworkInterface = GetNetworkInterfaces().FirstOrDefault(nic => nic.Id == NetworkInterfaceId); + Server.Start(); + } + + private void OnRequest(DHCPRequest request) + { + var type = request.GetMsgType(); + var mac = GetMacAddress(request.GetChaddr()); + + IPAddress ip; + + if (!Leases.TryGetValue(mac, out ip)) + { + ip = GetNextAddress(); + + Leases[mac] = ip; + } + + var reply = new DHCPReplyOptions + { + SubnetMask = SubnetMask, + DomainName = Domain, + ServerIdentifier = ServerIdentifier, + RouterIP = DefaultGateway, + DomainNameServers = DnsServers.ToArray() + }; + + switch (type) + { + case DHCPMsgType.DHCPDISCOVER: + request.SendDHCPReply(DHCPMsgType.DHCPOFFER, ip, reply); + break; + + case DHCPMsgType.DHCPREQUEST: + request.SendDHCPReply(DHCPMsgType.DHCPACK, ip, reply); + break; + } + } + + private IPAddress GetNextAddress() + { + var subnet = Ipv4Subnet.Parse($"{Network}/{GetBitmask(SubnetMask)}"); + + foreach (var address in subnet.GetUsableAddresses()) + { + var ip = address.ToIpAddress(); + + if (ip.Address >= RangeStart.Address && ip.Address <= RangeEnd.Address && !Leases.Values.Any(l => l.Address == ip.Address)) + { + return ip; + } + } + + return null; + } + + private ushort GetBitmask(IPAddress subnet) + { + var bytes = subnet.GetAddressBytes(); + + ushort bitmask = 0; + + foreach (var b in bytes) + { + for (int i = 7; i >= 0; i--) + { + if ((b & (1 << i)) != 0) + bitmask++; + else + break; + } + } + + return bitmask; + } + + private string GetMacAddress(byte[] bytes) + { + return String.Join(':', bytes.Select(b => b.ToString("X2"))); + } + + public IEnumerable GetNetworkInterfaces() + { + return NetworkInterface.GetAllNetworkInterfaces(); + } + + public NetworkInterface GetActiveNetworkInterface() + { + var settings = SettingService.GetSettings(); + + return GetNetworkInterfaces().FirstOrDefault(nic => nic.Id == settings.DHCPServer.NetworkInterface); + } + } +} diff --git a/LANCommander/wwwroot/css/site.css b/LANCommander/wwwroot/css/site.css index 65ade6d8..176435a6 100644 --- a/LANCommander/wwwroot/css/site.css +++ b/LANCommander/wwwroot/css/site.css @@ -173,6 +173,33 @@ background: rgba(255, 255, 255, 0.03); } +.network-interfaces.ant-radio-group { + width: 100%; +} + +.network-interfaces .ant-collapse-header .anticon { + font-size: 32px; + margin-right: 8px; +} + +.vertical-table { + display: table; +} + +.vertical-table-row { + display: table-row; +} + +.vertical-table-th { + display: table-cell; + font-weight: bold; + padding-right: 16px; +} + +.vertical-table-td { + display: table-cell; +} + @media screen and (min-width: 768px) { .mobile-menu { display: none;