From 6b7dc2ad052607c1e002d222ac0af264b7043b81 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Sat, 24 Jun 2023 02:35:01 +0200 Subject: [PATCH] Added custom layout options for the server list --- Moonlight/App/Database/Entities/User.cs | 2 + ...12_AddedServerListLayoutToUser.Designer.cs | 1077 +++++++++++++++++ ...30623235512_AddedServerListLayoutToUser.cs | 29 + .../Migrations/DataContextModelSnapshot.cs | 4 + Moonlight/App/Models/Misc/ServerGroup.cs | 7 + Moonlight/Pages/_Layout.cshtml | 2 + Moonlight/Shared/Views/Servers/Index.razor | 462 ++++++- Moonlight/wwwroot/assets/js/moonlight.js | 51 +- 8 files changed, 1575 insertions(+), 59 deletions(-) create mode 100644 Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs create mode 100644 Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.cs create mode 100644 Moonlight/App/Models/Misc/ServerGroup.cs diff --git a/Moonlight/App/Database/Entities/User.cs b/Moonlight/App/Database/Entities/User.cs index 854cdd63..d9c79b27 100644 --- a/Moonlight/App/Database/Entities/User.cs +++ b/Moonlight/App/Database/Entities/User.cs @@ -23,6 +23,8 @@ public class User public string State { get; set; } = ""; public string Country { get; set; } = ""; + + public string ServerListLayoutJson { get; set; } = ""; // States diff --git a/Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs b/Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs new file mode 100644 index 00000000..cc09283e --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.Designer.cs @@ -0,0 +1,1077 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230623235512_AddedServerListLayoutToUser")] + partial class AddedServerListLayoutToUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AuditLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Class") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Stacktrace") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("ErrorLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SecurityLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubscriptionDuration") + .HasColumnType("int"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.Navigation("CurrentSubscription"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.cs b/Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.cs new file mode 100644 index 00000000..10a12c7b --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230623235512_AddedServerListLayoutToUser.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddedServerListLayoutToUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ServerListLayoutJson", + table: "Users", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ServerListLayoutJson", + table: "Users"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs index f4aaf207..ab91e59b 100644 --- a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs +++ b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs @@ -780,6 +780,10 @@ namespace Moonlight.App.Database.Migrations b.Property("Rating") .HasColumnType("int"); + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + b.Property("State") .IsRequired() .HasColumnType("longtext"); diff --git a/Moonlight/App/Models/Misc/ServerGroup.cs b/Moonlight/App/Models/Misc/ServerGroup.cs new file mode 100644 index 00000000..07694ad8 --- /dev/null +++ b/Moonlight/App/Models/Misc/ServerGroup.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Models.Misc; + +public class ServerGroup +{ + public string Name { get; set; } = ""; + public List Servers { get; set; } = new(); +} \ No newline at end of file diff --git a/Moonlight/Pages/_Layout.cshtml b/Moonlight/Pages/_Layout.cshtml index a4f60ffb..9cb8fdee 100644 --- a/Moonlight/Pages/_Layout.cshtml +++ b/Moonlight/Pages/_Layout.cshtml @@ -103,6 +103,8 @@ + + diff --git a/Moonlight/Shared/Views/Servers/Index.razor b/Moonlight/Shared/Views/Servers/Index.razor index dcc2405d..9ee4040c 100644 --- a/Moonlight/Shared/Views/Servers/Index.razor +++ b/Moonlight/Shared/Views/Servers/Index.razor @@ -1,13 +1,414 @@ @page "/servers" -@using Moonlight.App.Repositories.Servers @using Microsoft.EntityFrameworkCore @using Moonlight.App.Database.Entities +@using Moonlight.App.Helpers +@using Moonlight.App.Models.Misc +@using Moonlight.App.Repositories @using Moonlight.App.Services +@using Newtonsoft.Json -@inject ServerRepository ServerRepository +@inject Repository ServerRepository +@inject Repository UserRepository +@inject SmartTranslateService SmartTranslateService @inject IServiceScopeFactory ServiceScopeFactory +@inject IJSRuntime JsRuntime - + +
+
+
+ Beta + @if (EditMode) + { +
+ + + + +
+ } + else + { + + + } +
+
+@foreach (var group in ServerGroups) +{ + @* +
+
+
+ @if (EditMode) + { + + } + else + { + @(group.Name) + } +
+ @if (EditMode) + { +
+ + +
+ } +
+
+
+ @foreach (var id in group.Servers) + { + var server = AllServers.First(x => x.Id.ToString() == id); + +
+
+
+
+

@(server.Name)

+
+ @if (EditMode) + { +
+ + + +
+ } +
+
+ @if (EditMode) + { + Hidden in edit mode + } + else + { + } +
+
+
+ } +
+
+
*@ +
+
+

+ +

+
+
+
+ @foreach (var id in group.Servers) + { + var server = AllServers.First(x => x.Id.ToString() == id); + +
+ +
+ +
+ @if (EditMode) + { + Hidden in edit mode + } + else + { + + @(Math.Round(server.Memory / 1024D, 2)) GB / @(Math.Round(server.Disk / 1024D, 2)) GB / @(server.Node.Name) - @(server.Image.Name) + +
+ @(server.Node.Fqdn):@(server.MainAllocation.Port) +
+
+ @if (StatusCache.ContainsKey(server)) + { + var status = StatusCache[server]; + + switch (status) + { + case "offline": + + Offline + + break; + case "stopping": + + Stopping + + break; + case "starting": + + Starting + + break; + case "running": + + Running + + break; + case "failed": + + Failed + + break; + default: + + Offline + + break; + } + } + else + { + + Loading + + } +
+ } +
+
+ +
+ } +
+
+
+
+
+} +
+
+ +@code +{ + [CascadingParameter] + public User User { get; set; } + + private Server[] AllServers; + private LazyLoader LazyLoader; + private readonly Dictionary StatusCache = new(); + + private List ServerGroups = new(); + private bool EditMode = false; + + private async Task Load(LazyLoader arg) + { + AllServers = ServerRepository + .Get() + .Include(x => x.Owner) + .Include(x => x.MainAllocation) + .Include(x => x.Node) + .Include(x => x.Image) + .Where(x => x.Owner.Id == User.Id) + .OrderBy(x => x.Name) + .ToArray(); + + + if (string.IsNullOrEmpty(User.ServerListLayoutJson)) + { + ServerGroups.Add(new() + { + Name = "", + Servers = AllServers.Select(x => x.Id.ToString()).ToList() + }); + } + else + { + ServerGroups = (JsonConvert.DeserializeObject( + User.ServerListLayoutJson) ?? Array.Empty()).ToList(); + } + + foreach (var server in AllServers) + { + Task.Run(async () => + { + try + { + using var scope = ServiceScopeFactory.CreateScope(); + var serverService = scope.ServiceProvider.GetRequiredService(); + + AddStatus(server, (await serverService.GetDetails(server)).State); + } + catch (Exception e) + { + AddStatus(server, "failed"); + } + }); + } + } + + private async Task AddGroup() + { + ServerGroups.Insert(0, new() + { + Name = "New group" + }); + + await InvokeAsync(StateHasChanged); + await JsRuntime.InvokeVoidAsync("moonlight.serverList.init"); + } + + private async Task RemoveGroup(ServerGroup group) + { + ServerGroups.Remove(group); + await EnsureAllServersInGroups(); + + await InvokeAsync(StateHasChanged); + + await JsRuntime.InvokeVoidAsync("moonlight.serverList.init"); + } + + private async Task SetEditMode(bool toggle) + { + EditMode = toggle; + await InvokeAsync(StateHasChanged); + + if (EditMode) + { + await EnsureAllServersInGroups(); + await InvokeAsync(StateHasChanged); + + await JsRuntime.InvokeVoidAsync("moonlight.serverList.init"); + } + else + { + var json = JsonConvert.SerializeObject(await GetGroupsFromClient()); + User.ServerListLayoutJson = json; + UserRepository.Update(User); + + await LazyLoader.Reload(); + } + } + + private async Task GetGroupsFromClient() + { + var serverGroups = await JsRuntime.InvokeAsync("moonlight.serverList.getData"); + + // Check user data to prevent users from doing stupid stuff + foreach (var serverGroup in serverGroups) + { + if (serverGroup.Name.Length > 30) + { + Logger.Verbose("Server list group lenght too long"); + return Array.Empty(); + } + + if (serverGroup.Servers.Any(x => AllServers.All(y => y.Id.ToString() != x))) + { + Logger.Verbose("User tried to add a server in his server list which he has no access to"); + return Array.Empty(); + } + } + + return serverGroups; + } + + private Task EnsureAllServersInGroups() + { + var presentInGroup = new List(); + + foreach (var group in ServerGroups) + { + foreach (var id in group.Servers) + presentInGroup.Add(AllServers.First(x => x.Id.ToString() == id)); + } + + var serversMissing = new List(); + + foreach (var server in AllServers) + { + if (presentInGroup.All(x => x.Id != server.Id)) + serversMissing.Add(server); + } + + if (serversMissing.Any()) + { + var defaultGroup = ServerGroups.FirstOrDefault(x => x.Name == ""); + + if (defaultGroup == null) + { + defaultGroup = new ServerGroup() + { + Name = "" + }; + + ServerGroups.Add(defaultGroup); + } + + foreach (var server in serversMissing) + defaultGroup.Servers.Add(server.Id.ToString()); + } + + return Task.CompletedTask; + } + + private void AddStatus(Server server, string status) + { + lock (StatusCache) + { + StatusCache.Add(server, status); + InvokeAsync(StateHasChanged); + } + } +} + +@* @if (AllServers.Any()) { if (UseSortedServerView) @@ -207,57 +608,4 @@ else -
- -@code -{ - [CascadingParameter] - public User User { get; set; } - - private Server[] AllServers; - private readonly Dictionary StatusCache = new(); - - private bool UseSortedServerView = false; - - private Task Load(LazyLoader arg) - { - AllServers = ServerRepository - .Get() - .Include(x => x.Owner) - .Include(x => x.MainAllocation) - .Include(x => x.Node) - .Include(x => x.Image) - .Where(x => x.Owner.Id == User.Id) - .OrderBy(x => x.Name) - .ToArray(); - - foreach (var server in AllServers) - { - Task.Run(async () => - { - try - { - using var scope = ServiceScopeFactory.CreateScope(); - var serverService = scope.ServiceProvider.GetRequiredService(); - - AddStatus(server, (await serverService.GetDetails(server)).State); - } - catch (Exception e) - { - AddStatus(server, "failed"); - } - }); - } - - return Task.CompletedTask; - } - - private void AddStatus(Server server, string status) - { - lock (StatusCache) - { - StatusCache.Add(server, status); - InvokeAsync(StateHasChanged); - } - } -} \ No newline at end of file +*@ \ No newline at end of file diff --git a/Moonlight/wwwroot/assets/js/moonlight.js b/Moonlight/wwwroot/assets/js/moonlight.js index 3d129ec8..d096446d 100644 --- a/Moonlight/wwwroot/assets/js/moonlight.js +++ b/Moonlight/wwwroot/assets/js/moonlight.js @@ -354,8 +354,6 @@ moonlight.keyListener.listener = (event) => { // filter here what key events should be sent to moonlight - - console.log(event); if(event.code === "KeyS" && event.ctrlKey) { @@ -370,5 +368,54 @@ { window.removeEventListener('keydown', moonlight.keyListener.listener); } + }, + serverList: { + init: function () + { + if(moonlight.serverList.Swappable) + { + moonlight.serverList.Swappable.destroy(); + } + + let containers = document.querySelectorAll(".draggable-zone"); + + if (containers.length !== 0) + { + moonlight.serverList.Swappable = new Draggable.Sortable(containers, { + draggable: ".draggable", + handle: ".draggable .draggable-handle", + mirror: { + //appendTo: selector, + appendTo: "body", + constrainDimensions: true + } + }); + } + }, + getData: function () + { + let groups = new Array(); + + let groupElements = document.querySelectorAll('[ml-server-group]'); + + groupElements.forEach(groupElement => { + let group = new Object(); + group.name = groupElement.attributes.getNamedItem("ml-server-group").value; + + let servers = new Array(); + let serverElements = groupElement.querySelectorAll("[ml-server-id]"); + + serverElements.forEach(serverElement => { + let id = serverElement.attributes.getNamedItem("ml-server-id").value; + + servers.push(id); + }); + + group.servers = servers; + groups.push(group); + }); + + return groups; + } } }; \ No newline at end of file