Added audit logging. Added admin view for audit log

This commit is contained in:
Marcel Baumgartner 2023-03-06 02:21:23 +01:00
parent d7b10aa224
commit 62cd63f56b
39 changed files with 2754 additions and 153 deletions

View file

@ -9,4 +9,5 @@ public class AuditLogEntry
public string JsonData { get; set; } = "";
public bool System { get; set; }
public string Ip { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View file

@ -8,4 +8,5 @@ public class ErrorLogEntry
public string JsonData { get; set; } = "";
public string Ip { get; set; } = "";
public string Class { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View file

@ -9,4 +9,5 @@ public class SecurityLogEntry
public string Ip { get; set; } = "";
public SecurityLogType Type { get; set; }
public string JsonData { get; set; } = "";
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

View file

@ -0,0 +1,925 @@
// <auto-generated />
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("20230305014834_AddedLoggingAndDbStuff")]
partial class AddedLoggingAndDbStuff
{
/// <inheritdoc />
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.Database", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("AaPanelId")
.HasColumnType("int");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Databases");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Default")
.HasColumnType("tinyint(1)");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("DockerImages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ConfigFiles")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallEntrypoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Startup")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StartupDetection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageTags");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("LoadingMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Class")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stacktrace")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("ErrorLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SecurityLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("HttpPort")
.HasColumnType("int");
b.Property<int>("MoonlightDaemonPort")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("SftpPort")
.HasColumnType("int");
b.Property<bool>("Ssl")
.HasColumnType("tinyint(1)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TokenId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Nodes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("NodeId")
.HasColumnType("int");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NotificationClientId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NotificationClientId");
b.ToTable("NotificationActions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationClients");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Revokes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<long>("Disk")
.HasColumnType("bigint");
b.Property<int>("DockerImageIndex")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<bool>("Installing")
.HasColumnType("tinyint(1)");
b.Property<int>("MainAllocationId")
.HasColumnType("int");
b.Property<long>("Memory")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NodeId")
.HasColumnType("int");
b.Property<string>("OverrideStartup")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<bool>("Suspended")
.HasColumnType("tinyint(1)");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Bytes")
.HasColumnType("bigint");
b.Property<bool>("Created")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerBackups");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CloudflareId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("SharedDomains");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Duration")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("SellPassId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Amount")
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<int>("Disk")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<int>("Memory")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("SubscriptionId");
b.ToTable("SubscriptionLimits");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Answer")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("IsQuestion")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSupport")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSystem")
.HasColumnType("tinyint(1)");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("RecipientId")
.HasColumnType("int");
b.Property<int?>("SenderId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("SenderId");
b.ToTable("SupportMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("Admin")
.HasColumnType("tinyint(1)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("DiscordDiscriminator")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("DiscordId")
.HasColumnType("bigint");
b.Property<string>("DiscordUsername")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("State")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("SubscriptionDuration")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.Property<DateTime?>("SubscriptionSince")
.HasColumnType("datetime(6)");
b.Property<bool>("SupportPending")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("TokenValidTime")
.HasColumnType("datetime(6)");
b.Property<bool>("TotpEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("TotpSecret")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("SubscriptionId");
b.ToTable("Users");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
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.ImageTag", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("Tags")
.HasForeignKey("ImageId");
});
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.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.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation")
.WithMany()
.HasForeignKey("MainAllocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
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("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.SubscriptionLimit", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.Subscription", null)
.WithMany("Limits")
.HasForeignKey("SubscriptionId");
b.Navigation("Image");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId");
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", "Subscription")
.WithMany()
.HasForeignKey("SubscriptionId");
b.Navigation("Subscription");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
{
b.Navigation("DockerImages");
b.Navigation("Tags");
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.Subscription", b =>
{
b.Navigation("Limits");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,66 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedLoggingAndDbStuff : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ErrorLog",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Stacktrace = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
JsonData = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Ip = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Class = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_ErrorLog", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "SecurityLog",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
Ip = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Type = table.Column<int>(type: "int", nullable: false),
JsonData = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_SecurityLog", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ErrorLog");
migrationBuilder.DropTable(
name: "SecurityLog");
}
}
}

View file

@ -0,0 +1,934 @@
// <auto-generated />
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("20230305021844_AddedDatesToLogs")]
partial class AddedDatesToLogs
{
/// <inheritdoc />
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.Database", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("AaPanelId")
.HasColumnType("int");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.ToTable("Databases");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Default")
.HasColumnType("tinyint(1)");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("DockerImages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ConfigFiles")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallDockerImage")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallEntrypoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("InstallScript")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Startup")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StartupDetection")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("StopCommand")
.IsRequired()
.HasColumnType("longtext");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("Images");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageTag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageTags");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("DefaultValue")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ImageId")
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ImageId");
b.ToTable("ImageVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("LoadingMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Class")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stacktrace")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("ErrorLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SecurityLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Fqdn")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("HttpPort")
.HasColumnType("int");
b.Property<int>("MoonlightDaemonPort")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("SftpPort")
.HasColumnType("int");
b.Property<bool>("Ssl")
.HasColumnType("tinyint(1)");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TokenId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Nodes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int?>("NodeId")
.HasColumnType("int");
b.Property<int>("Port")
.HasColumnType("int");
b.Property<int?>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Action")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NotificationClientId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NotificationClientId");
b.ToTable("NotificationActions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationClients");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Identifier")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Revokes");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<long>("Disk")
.HasColumnType("bigint");
b.Property<int>("DockerImageIndex")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<bool>("Installing")
.HasColumnType("tinyint(1)");
b.Property<int>("MainAllocationId")
.HasColumnType("int");
b.Property<long>("Memory")
.HasColumnType("bigint");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NodeId")
.HasColumnType("int");
b.Property<string>("OverrideStartup")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<bool>("Suspended")
.HasColumnType("tinyint(1)");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<long>("Bytes")
.HasColumnType("bigint");
b.Property<bool>("Created")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<Guid>("Uuid")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerBackups");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("ServerId")
.HasColumnType("int");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ServerId");
b.ToTable("ServerVariables");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("CloudflareId")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("SharedDomains");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Duration")
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("SellPassId")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SubscriptionLimit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Amount")
.HasColumnType("int");
b.Property<int>("Cpu")
.HasColumnType("int");
b.Property<int>("Disk")
.HasColumnType("int");
b.Property<int>("ImageId")
.HasColumnType("int");
b.Property<int>("Memory")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ImageId");
b.HasIndex("SubscriptionId");
b.ToTable("SubscriptionLimits");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Answer")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("IsQuestion")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSupport")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsSystem")
.HasColumnType("tinyint(1)");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("RecipientId")
.HasColumnType("int");
b.Property<int?>("SenderId")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("SenderId");
b.ToTable("SupportMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Address")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("Admin")
.HasColumnType("tinyint(1)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("DiscordDiscriminator")
.IsRequired()
.HasColumnType("longtext");
b.Property<long>("DiscordId")
.HasColumnType("bigint");
b.Property<string>("DiscordUsername")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("State")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<int>("SubscriptionDuration")
.HasColumnType("int");
b.Property<int?>("SubscriptionId")
.HasColumnType("int");
b.Property<DateTime?>("SubscriptionSince")
.HasColumnType("datetime(6)");
b.Property<bool>("SupportPending")
.HasColumnType("tinyint(1)");
b.Property<DateTime>("TokenValidTime")
.HasColumnType("datetime(6)");
b.Property<bool>("TotpEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("TotpSecret")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.HasIndex("SubscriptionId");
b.ToTable("Users");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
});
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.ImageTag", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", null)
.WithMany("Tags")
.HasForeignKey("ImageId");
});
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.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.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation")
.WithMany()
.HasForeignKey("MainAllocationId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
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("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.SubscriptionLimit", b =>
{
b.HasOne("Moonlight.App.Database.Entities.Image", "Image")
.WithMany()
.HasForeignKey("ImageId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.Subscription", null)
.WithMany("Limits")
.HasForeignKey("SubscriptionId");
b.Navigation("Image");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId");
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", "Subscription")
.WithMany()
.HasForeignKey("SubscriptionId");
b.Navigation("Subscription");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
{
b.Navigation("DockerImages");
b.Navigation("Tags");
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.Subscription", b =>
{
b.Navigation("Limits");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,52 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedDatesToLogs : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "SecurityLog",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "ErrorLog",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "AuditLog",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "SecurityLog");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "ErrorLog");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "AuditLog");
}
}
}

View file

@ -19,31 +19,6 @@ namespace Moonlight.App.Database.Migrations
.HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Moonlight.App.Database.Entities.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Database", b =>
{
b.Property<int>("Id")
@ -220,6 +195,95 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("LoadingMessages");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("AuditLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Class")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Stacktrace")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("ErrorLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("JsonData")
.IsRequired()
.HasColumnType("longtext");
b.Property<bool>("System")
.HasColumnType("tinyint(1)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SecurityLog");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b =>
{
b.Property<int>("Id")

View file

@ -76,4 +76,16 @@ public static class Formatter
return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year} {i2s(e.Hour)}:{i2s(e.Minute)}";
}
public static string FormatDateOnly(DateTime e)
{
string i2s(int i)
{
if (i.ToString().Length < 2)
return "0" + i;
return i.ToString();
}
return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year}";
}
}

View file

@ -67,7 +67,7 @@ public class WingsServerConverter
// Settings
wingsServer.Settings.Skip_Egg_Scripts = false;
wingsServer.Settings.Suspended = false; //TODO: Implement
wingsServer.Settings.Suspended = server.Suspended;
wingsServer.Settings.Invocation = string.IsNullOrEmpty(server.OverrideStartup) ? image.Startup : server.OverrideStartup;
wingsServer.Settings.Uuid = server.Uuid;

View file

@ -27,7 +27,7 @@ public class PullController : Controller
Stream req = Request.Body;
string jwt = await new StreamReader(req).ReadToEndAsync();
var dict = OneTimeJwtService.Validate(jwt);
var dict = await OneTimeJwtService.Validate(jwt);
if (dict == null)
return NotFound();

View file

@ -1,5 +1,7 @@
using Logging.Net;
using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Models.Misc;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
@ -7,12 +9,19 @@ namespace Moonlight.App.Http.Controllers.Api.Moonlight;
[Route("api/moonlight/resources")]
public class ResourcesController : Controller
{
private readonly SecurityLogService SecurityLogService;
public ResourcesController(SecurityLogService securityLogService)
{
SecurityLogService = securityLogService;
}
[HttpGet("images/{name}")]
public ActionResult GetImage([FromRoute] string name)
public async Task<ActionResult> GetImage([FromRoute] string name)
{
if (name.Contains(".."))
{
//TODO: Add security warn
await SecurityLogService.Log(SecurityLogType.PathTransversal, name);
return NotFound();
}

View file

@ -4,5 +4,19 @@ public enum AuditLogType
{
Login,
Register,
LoginFail
ChangePassword,
ChangePowerState,
CreateBackup,
RestoreBackup,
DeleteBackup,
DownloadBackup,
CreateServer,
ReinstallServer,
CancelSubscription,
ApplySubscriptionCode,
EnableTotp,
DisableTotp,
AddDomainRecord,
UpdateDomainRecord,
DeleteDomainRecord
}

View file

@ -2,5 +2,8 @@
public enum SecurityLogType
{
ManipulatedJwt
ManipulatedJwt,
PathTransversal,
SftpBruteForce,
LoginFail
}

View file

@ -9,7 +9,9 @@ using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories.Domains;
using Moonlight.App.Services.LogServices;
using DnsRecord = Moonlight.App.Models.Misc.DnsRecord;
namespace Moonlight.App.Services;
@ -19,14 +21,18 @@ public class DomainService
private readonly DomainRepository DomainRepository;
private readonly SharedDomainRepository SharedDomainRepository;
private readonly CloudFlareClient Client;
private readonly AuditLogService AuditLogService;
private readonly string AccountId;
public DomainService(ConfigService configService,
public DomainService(
ConfigService configService,
DomainRepository domainRepository,
SharedDomainRepository sharedDomainRepository)
SharedDomainRepository sharedDomainRepository,
AuditLogService auditLogService)
{
DomainRepository = domainRepository;
SharedDomainRepository = sharedDomainRepository;
AuditLogService = auditLogService;
var config = configService
.GetSection("Moonlight")
@ -46,10 +52,10 @@ public class DomainService
GetAvailableDomains() // This method returns all available domains which are not added as a shared domain
{
var domains = GetData(
await Client.Zones.GetAsync(new()
{
AccountId = AccountId
})
await Client.Zones.GetAsync(new()
{
AccountId = AccountId
})
);
var sharedDomains = SharedDomainRepository.Get().ToArray();
@ -82,7 +88,7 @@ public class DomainService
{
if (record.Name.EndsWith(dname))
{
result.Add(new ()
result.Add(new()
{
Name = record.Name.Replace(dname, ""),
Content = record.Content,
@ -95,7 +101,7 @@ public class DomainService
}
else if (record.Name.EndsWith(rname))
{
result.Add(new ()
result.Add(new()
{
Name = record.Name.Replace(rname, ""),
Content = record.Content,
@ -107,14 +113,14 @@ public class DomainService
});
}
}
return result.ToArray();
}
public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord)
{
var domain = EnsureData(d);
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
var dname = $".{rname}";
@ -134,7 +140,6 @@ public class DomainService
Type = dnsRecord.Type,
Data = new()
{
Service = parts[0],
Protocol = protocol,
Name = name,
@ -146,7 +151,7 @@ public class DomainService
Proxied = dnsRecord.Proxied,
Ttl = dnsRecord.Ttl,
};
GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, srv));
}
else
@ -163,32 +168,38 @@ public class DomainService
Name = name
}));
}
await AuditLogService.Log(AuditLogType.AddDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
}
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
{
var domain = EnsureData(d);
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
var dname = $".{rname}";
if (dnsRecord.Type == DnsRecordType.Srv)
{
throw new DisplayException("SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one");
throw new DisplayException(
"SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one");
}
else
{
var name = dnsRecord.Name == "" ? rname : dnsRecord.Name + dname;
GetData(await Client.Zones.DnsRecords.UpdateAsync(d.SharedDomain.CloudflareId, dnsRecord.Id, new ModifiedDnsRecord()
{
Content = dnsRecord.Content,
Proxied = dnsRecord.Proxied,
Ttl = dnsRecord.Ttl,
Name = name,
Type = dnsRecord.Type
}));
GetData(await Client.Zones.DnsRecords.UpdateAsync(d.SharedDomain.CloudflareId, dnsRecord.Id,
new ModifiedDnsRecord()
{
Content = dnsRecord.Content,
Proxied = dnsRecord.Proxied,
Ttl = dnsRecord.Ttl,
Name = name,
Type = dnsRecord.Type
}));
}
await AuditLogService.Log(AuditLogType.UpdateDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
}
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
@ -198,6 +209,8 @@ public class DomainService
GetData(
await Client.Zones.DnsRecords.DeleteAsync(domain.SharedDomain.CloudflareId, dnsRecord.Id)
);
await AuditLogService.Log(AuditLogType.DeleteDomainRecord, new[] { d.Id.ToString(), dnsRecord.Name });
}
private Domain EnsureData(Domain domain)
@ -210,12 +223,13 @@ public class DomainService
.Include(x => x.SharedDomain)
.First(x => x.Id == domain.Id);
}
private T GetData<T>(CloudFlareResult<T> result)
{
if (!result.Success)
{
string message;
try
{
message = result.Errors.First().ErrorChain.First().Message;

View file

@ -9,24 +9,26 @@ namespace Moonlight.App.Services.LogServices;
public class AuditLogService
{
private readonly AuditLogEntryRepository Repository;
private readonly IdentityService IdentityService;
private readonly IHttpContextAccessor HttpContextAccessor;
public AuditLogService(AuditLogEntryRepository repository, IdentityService identityService)
public AuditLogService(
AuditLogEntryRepository repository,
IHttpContextAccessor httpContextAccessor)
{
Repository = repository;
IdentityService = identityService;
HttpContextAccessor = httpContextAccessor;
}
public Task Log(AuditLogType type, object? data = null)
public Task Log(AuditLogType type, params object[] data)
{
var ip = IdentityService.GetIp();
var ip = GetIp();
var entry = new AuditLogEntry()
{
Ip = ip,
Type = type,
System = false,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data)
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
};
Repository.Add(entry);
@ -34,17 +36,30 @@ public class AuditLogService
return Task.CompletedTask;
}
public Task LogSystem(AuditLogType type, object? data = null)
public Task LogSystem(AuditLogType type, params object[] data)
{
var entry = new AuditLogEntry()
{
Type = type,
System = true,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data)
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
};
Repository.Add(entry);
return Task.CompletedTask;
}
private string GetIp()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
}
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
}
}

View file

@ -10,17 +10,17 @@ namespace Moonlight.App.Services.LogServices;
public class ErrorLogService
{
private readonly ErrorLogEntryRepository Repository;
private readonly IdentityService IdentityService;
private readonly IHttpContextAccessor HttpContextAccessor;
public ErrorLogService(ErrorLogEntryRepository repository, IdentityService identityService)
public ErrorLogService(ErrorLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
{
Repository = repository;
IdentityService = identityService;
HttpContextAccessor = httpContextAccessor;
}
public Task Log(Exception exception, params object[] objects)
{
var ip = IdentityService.GetIp();
var ip = GetIp();
var entry = new ErrorLogEntry()
{
@ -74,4 +74,17 @@ public class ErrorLogService
return fullName;
}
private string GetIp()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
}
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
}
}

View file

@ -9,24 +9,24 @@ namespace Moonlight.App.Services.LogServices;
public class SecurityLogService
{
private readonly SecurityLogEntryRepository Repository;
private readonly IdentityService IdentityService;
private readonly IHttpContextAccessor HttpContextAccessor;
public SecurityLogService(SecurityLogEntryRepository repository, IdentityService identityService)
public SecurityLogService(SecurityLogEntryRepository repository, IHttpContextAccessor httpContextAccessor)
{
Repository = repository;
IdentityService = identityService;
HttpContextAccessor = httpContextAccessor;
}
public Task Log(SecurityLogType type, object? data = null)
public Task Log(SecurityLogType type, params object[] data)
{
var ip = IdentityService.GetIp();
var ip = GetIp();
var entry = new SecurityLogEntry()
{
Ip = ip,
Type = type,
System = false,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data)
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
};
Repository.Add(entry);
@ -34,17 +34,30 @@ public class SecurityLogService
return Task.CompletedTask;
}
public Task LogSystem(SecurityLogType type, object? data = null)
public Task LogSystem(SecurityLogType type, params object[] data)
{
var entry = new SecurityLogEntry()
{
Type = type,
System = true,
JsonData = data == null ? "" : JsonConvert.SerializeObject(data)
JsonData = data.Length == 0 ? "" : JsonConvert.SerializeObject(data)
};
Repository.Add(entry);
return Task.CompletedTask;
}
private string GetIp()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
}
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
}
}

View file

@ -6,6 +6,6 @@ public class MessageService : MessageSender
{
public MessageService()
{
Debug = true;
Debug = false;
}
}

View file

@ -1,9 +1,12 @@
using System.Text;
using JWT.Algorithms;
using JWT.Builder;
using JWT.Exceptions;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Services;
@ -11,11 +14,15 @@ public class OneTimeJwtService
{
private readonly ConfigService ConfigService;
private readonly RevokeRepository RevokeRepository;
private readonly SecurityLogService SecurityLogService;
public OneTimeJwtService(ConfigService configService, RevokeRepository revokeRepository)
public OneTimeJwtService(ConfigService configService,
RevokeRepository revokeRepository,
SecurityLogService securityLogService)
{
ConfigService = configService;
RevokeRepository = revokeRepository;
SecurityLogService = securityLogService;
}
public string Generate(Action<Dictionary<string, string>> options, TimeSpan? validTime = null)
@ -51,7 +58,7 @@ public class OneTimeJwtService
return builder.Encode();
}
public Dictionary<string, string>? Validate(string token)
public async Task<Dictionary<string, string>?> Validate(string token)
{
string secret = ConfigService
.GetSection("Moonlight")
@ -59,15 +66,18 @@ public class OneTimeJwtService
.GetValue<string>("Token");
string json;
try
{
json = JwtBuilder.Create()
.WithAlgorithm(new HMACSHA256Algorithm())
.WithSecret(secret)
.Decode(token);
//TODO: Error handling, report signature errors
}
catch (SignatureVerificationException)
{
await SecurityLogService.LogSystem(SecurityLogType.ManipulatedJwt, token);
return null;
}
catch (Exception e)
{
@ -97,9 +107,9 @@ public class OneTimeJwtService
return opt;
}
public void Revoke(string token)
public async Task Revoke(string token)
{
var values = Validate(token);
var values = await Validate(token);
RevokeRepository.Add(new()
{

View file

@ -6,11 +6,13 @@ using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Files;
using Moonlight.App.Models.Files.Accesses;
using Moonlight.App.Models.Misc;
using Moonlight.App.Models.Wings;
using Moonlight.App.Models.Wings.Requests;
using Moonlight.App.Models.Wings.Resources;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Services;
@ -25,6 +27,9 @@ public class ServerService
private readonly UserService UserService;
private readonly ConfigService ConfigService;
private readonly WingsJwtHelper WingsJwtHelper;
private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly ErrorLogService ErrorLogService;
private readonly string AppUrl;
public ServerService(
@ -36,7 +41,10 @@ public class ServerService
MessageService messageService,
UserService userService,
ConfigService configService,
WingsJwtHelper wingsJwtHelper)
WingsJwtHelper wingsJwtHelper,
SecurityLogService securityLogService,
AuditLogService auditLogService,
ErrorLogService errorLogService)
{
ServerRepository = serverRepository;
WingsApiHelper = wingsApiHelper;
@ -47,6 +55,9 @@ public class ServerService
UserService = userService;
ConfigService = configService;
WingsJwtHelper = wingsJwtHelper;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
ErrorLogService = errorLogService;
AppUrl = ConfigService.GetSection("Moonlight").GetValue<string>("AppUrl");
}
@ -84,6 +95,8 @@ public class ServerService
{
Action = rawSignal
});
await AuditLogService.Log(AuditLogType.ChangePowerState, new[] { server.Uuid.ToString(), rawSignal });
}
public async Task<ServerBackup> CreateBackup(Server server)
@ -112,6 +125,9 @@ public class ServerService
Ignore = ""
});
await AuditLogService.Log(AuditLogType.CreateBackup,
new[] { serverData.Uuid.ToString(), backup.Uuid.ToString() });
return backup;
}
@ -146,6 +162,9 @@ public class ServerService
{
Adapter = "wings"
});
await AuditLogService.Log(AuditLogType.RestoreBackup,
new[] { s.Uuid.ToString(), serverBackup.Uuid.ToString() });
}
public async Task DeleteBackup(Server server, ServerBackup serverBackup)
@ -165,9 +184,12 @@ public class ServerService
ServerRepository.Update(serverData);
await MessageService.Emit("wings.backups.delete", backup);
await AuditLogService.Log(AuditLogType.DeleteBackup,
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
}
public Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
public async Task<string> DownloadBackup(Server s, ServerBackup serverBackup)
{
Server server = EnsureNodeData(s);
@ -176,10 +198,11 @@ public class ServerService
claims.Add("server_uuid", server.Uuid.ToString());
claims.Add("backup_uuid", serverBackup.Uuid.ToString());
});
await AuditLogService.Log(AuditLogType.DownloadBackup,
new[] { serverBackup.Uuid.ToString(), serverBackup.Uuid.ToString() });
return Task.FromResult(
$"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}"
);
return $"https://{server.Node.Fqdn}:{server.Node.HttpPort}/download/backup?token={token}";
}
public Task<IFileAccess> CreateFileAccess(Server s, User user) // We need the user to create the launch url
@ -197,7 +220,8 @@ public class ServerService
);
}
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null, Action<Server>? modifyDetails = null)
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null,
Action<Server>? modifyDetails = null)
{
var user = UserRepository
.Get()
@ -267,8 +291,8 @@ public class ServerService
Value = imageVariable.DefaultValue
});
}
if(modifyDetails != null)
if (modifyDetails != null)
modifyDetails.Invoke(server);
var newServerData = ServerRepository.Add(server);
@ -281,16 +305,17 @@ public class ServerService
StartOnCompletion = false
});
await AuditLogService.Log(AuditLogType.CreateServer, newServerData.Uuid.ToString());
return newServerData;
}
catch (Exception e)
{
Logger.Error("Error creating server on wings. Deleting db model");
Logger.Error(e);
await ErrorLogService.Log(e, new[] { newServerData.Uuid.ToString(), node.Id.ToString() });
ServerRepository.Delete(newServerData);
throw new Exception("Error creating server on wings");
throw new DisplayException("Error creating server on wings");
}
}
@ -299,14 +324,19 @@ public class ServerService
Server server = EnsureNodeData(s);
await WingsApiHelper.Post(server.Node, $"api/servers/{server.Uuid}/reinstall", null);
await AuditLogService.Log(AuditLogType.ReinstallServer, server.Uuid.ToString());
}
public async Task<Server> SftpServerLogin(int serverId, int id, string password)
{
var server = ServerRepository.Get().FirstOrDefault(x => x.Id == serverId);
if (server == null) //TODO: Logging
if (server == null)
{
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, serverId);
throw new Exception("Server not found");
}
var user = await UserService.SftpLogin(id, password);
@ -316,6 +346,7 @@ public class ServerService
}
else
{
//TODO: Decide if logging
throw new Exception("User and owner id do not match");
}
}

View file

@ -4,7 +4,9 @@ using JWT.Builder;
using JWT.Exceptions;
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using UAParser;
namespace Moonlight.App.Services.Sessions;
@ -13,6 +15,8 @@ public class IdentityService
{
private readonly UserRepository UserRepository;
private readonly CookieService CookieService;
private readonly SecurityLogService SecurityLogService;
private readonly ErrorLogService ErrorLogService;
private readonly IHttpContextAccessor HttpContextAccessor;
private readonly string Secret;
@ -22,11 +26,15 @@ public class IdentityService
CookieService cookieService,
UserRepository userRepository,
IHttpContextAccessor httpContextAccessor,
ConfigService configService)
ConfigService configService,
SecurityLogService securityLogService,
ErrorLogService errorLogService)
{
CookieService = cookieService;
UserRepository = userRepository;
HttpContextAccessor = httpContextAccessor;
SecurityLogService = securityLogService;
ErrorLogService = errorLogService;
Secret = configService
.GetSection("Moonlight")
@ -81,14 +89,12 @@ public class IdentityService
}
catch (SignatureVerificationException)
{
//TODO: Heavy warn and write it to the logs
Logger.Warn("Someone tried to modify his data: " + token);
await SecurityLogService.Log(SecurityLogType.ManipulatedJwt, token);
return null;
}
catch (Exception e)
{
Logger.Warn("Error parsing and validating token");
Logger.Warn(e);
await ErrorLogService.Log(e);
return null;
}
@ -117,19 +123,14 @@ public class IdentityService
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(iat).DateTime;
if (issuedAt < user.TokenValidTime.ToUniversalTime())
{
//TODO: Remove at publish
//Logger.Debug($"Old token found: {issuedAt.ToShortDateString()} {issuedAt.ToShortTimeString()} Current valid token time {userData.TokenValidTime.ToUniversalTime().ToShortDateString()} {userData.TokenValidTime.ToUniversalTime().ToShortTimeString()}");
return null;
}
UserCache = user;
return UserCache;
}
catch (Exception e)
{
Logger.Warn("Error loading user");
Logger.Warn(e);
await ErrorLogService.Log(e);
return null;
}
}

View file

@ -1,8 +1,10 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Subscriptions;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services;
@ -14,18 +16,21 @@ public class SubscriptionService
private readonly IdentityService IdentityService;
private readonly ConfigService ConfigService;
private readonly OneTimeJwtService OneTimeJwtService;
private readonly AuditLogService AuditLogService;
public SubscriptionService(SubscriptionRepository subscriptionRepository,
UserRepository userRepository,
IdentityService identityService,
ConfigService configService,
OneTimeJwtService oneTimeJwtService)
OneTimeJwtService oneTimeJwtService,
AuditLogService auditLogService)
{
SubscriptionRepository = subscriptionRepository;
UserRepository = userRepository;
IdentityService = identityService;
ConfigService = configService;
OneTimeJwtService = oneTimeJwtService;
AuditLogService = auditLogService;
}
public async Task<Subscription?> Get()
@ -50,6 +55,8 @@ public class SubscriptionService
var user = await IdentityService.Get();
user!.Subscription = null;
UserRepository.Update(user!);
await AuditLogService.Log(AuditLogType.CancelSubscription, new[] { user.Email });
}
public Task<Subscription[]> GetAvailable()
{
@ -91,7 +98,7 @@ public class SubscriptionService
public async Task ApplyCode(string code)
{
var user = (await IdentityService.Get())!;
var values = OneTimeJwtService.Validate(code);
var values = await OneTimeJwtService.Validate(code);
if (values == null)
throw new DisplayException("Invalid subscription code");
@ -114,6 +121,8 @@ public class SubscriptionService
UserRepository.Update(user);
OneTimeJwtService.Revoke(code);
await OneTimeJwtService.Revoke(code);
await AuditLogService.Log(AuditLogType.ApplySubscriptionCode, new[] { user.Email, subscription.Id.ToString() });
}
}

View file

@ -1,4 +1,6 @@
using Moonlight.App.Repositories;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Sessions;
using OtpNet;
@ -8,11 +10,16 @@ public class TotpService
{
private readonly IdentityService IdentityService;
private readonly UserRepository UserRepository;
private readonly AuditLogService AuditLogService;
public TotpService(IdentityService identityService, UserRepository userRepository)
public TotpService(
IdentityService identityService,
UserRepository userRepository,
AuditLogService auditLogService)
{
IdentityService = identityService;
UserRepository = userRepository;
AuditLogService = auditLogService;
}
public Task<bool> Verify(string secret, string code)
@ -38,21 +45,25 @@ public class TotpService
public async Task Enable()
{
var user = await IdentityService.Get();
var user = (await IdentityService.Get())!;
user.TotpEnabled = true;
user.TotpSecret = GenerateSecret();
UserRepository.Update(user);
await AuditLogService.Log(AuditLogType.EnableTotp, user.Email);
}
public async Task Disable()
{
var user = await IdentityService.Get();
var user = (await IdentityService.Get())!;
user.TotpEnabled = false;
UserRepository.Update(user);
await AuditLogService.Log(AuditLogType.DisableTotp, user.Email);
}
private string GenerateSecret()

View file

@ -1,10 +1,10 @@
using JWT.Algorithms;
using JWT.Builder;
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices;
namespace Moonlight.App.Services;
@ -12,20 +12,24 @@ public class UserService
{
private readonly UserRepository UserRepository;
private readonly TotpService TotpService;
private readonly ConfigService ConfigService;
private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService;
private readonly string JwtSecret;
public UserService(
UserRepository userRepository,
TotpService totpService,
ConfigService configService)
ConfigService configService,
SecurityLogService securityLogService,
AuditLogService auditLogService)
{
UserRepository = userRepository;
TotpService = totpService;
ConfigService = configService;
SecurityLogService = securityLogService;
AuditLogService = auditLogService;
JwtSecret = ConfigService
JwtSecret = configService
.GetSection("Moonlight")
.GetSection("Security")
.GetValue<string>("Token");
@ -67,11 +71,13 @@ public class UserService
//var mail = new WelcomeMail(user);
//await MailService.Send(mail, user);
await AuditLogService.Log(AuditLogType.Register, user.Email);
return await GenerateToken(user);
}
public Task<bool> CheckTotp(string email, string password)
public async Task<bool> CheckTotp(string email, string password)
{
var user = UserRepository.Get()
.FirstOrDefault(
@ -80,18 +86,16 @@ public class UserService
if (user == null)
{
Logger.Debug("User not found");
//AuditLogService.Log("login:fail", $"Invalid email: {email}. Password: {password}");
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
throw new DisplayException("Email and password combination not found");
}
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
{
return Task.FromResult(user.TotpEnabled);
return user.TotpEnabled;
}
//AuditLogService.Log("login:fail", $"Invalid email: {email}. Password: {password}");
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
throw new DisplayException("Email and password combination not found");;
}
@ -111,29 +115,27 @@ public class UserService
if (string.IsNullOrEmpty(totpCode))
throw new DisplayException("2FA code must be provided");
var totpCodeValid = await TotpService.Verify(user.TotpSecret, totpCode);
var totpCodeValid = await TotpService.Verify(user!.TotpSecret, totpCode);
if (totpCodeValid)
{
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in");
await AuditLogService.Log(AuditLogType.Login, email);
return await GenerateToken(user);
}
else
{
//AuditLogService.Log("login:fail", $"Invalid totp code: {totpCode}");
await SecurityLogService.Log(SecurityLogType.LoginFail, new[] { email, password });
throw new DisplayException("2FA code invalid");
}
}
else
{
//AuditLogService.Log("login:success", $"{user.Email} has successfully logged in");
await AuditLogService.Log(AuditLogType.Login, email);
return await GenerateToken(user!);
}
}
public Task ChangePassword(User user, string password)
public async Task ChangePassword(User user, string password)
{
user.Password = BCrypt.Net.BCrypt.HashPassword(password);
user.TokenValidTime = DateTime.Now;
@ -141,26 +143,27 @@ public class UserService
//var mail = new NewPasswordMail(user);
//await MailService.Send(mail, user);
//AuditLogService.Log("password:change", "The password has been set to a new one");
return Task.CompletedTask;
await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
}
public Task<User> SftpLogin(int id, string password)
public async Task<User> SftpLogin(int id, string password)
{
var user = UserRepository.Get().FirstOrDefault(x => x.Id == id);
if (user == null)
throw new Exception("Unknown user");
{
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, id);
throw new Exception("Invalid username");
}
if (BCrypt.Net.BCrypt.Verify(password, user.Password))
{
//TODO: Maybe log
return Task.FromResult(user);
await AuditLogService.LogSystem(AuditLogType.Login, user.Email);
return user;
}
//TODO: Log
await SecurityLogService.LogSystem(SecurityLogType.SftpBruteForce, new[] { id.ToString(), password });
throw new Exception("Invalid userid or password");
}

View file

@ -10,6 +10,7 @@ using Moonlight.App.Repositories.Servers;
using Moonlight.App.Repositories.Subscriptions;
using Moonlight.App.Services;
using Moonlight.App.Services.Interop;
using Moonlight.App.Services.LogServices;
using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.OAuth2;
using Moonlight.App.Services.Sessions;
@ -80,7 +81,10 @@ namespace Moonlight
builder.Services.AddScoped<GoogleOAuth2Service>();
builder.Services.AddScoped<DiscordOAuth2Service>();
// Loggers
builder.Services.AddScoped<SecurityLogService>();
builder.Services.AddScoped<AuditLogService>();
builder.Services.AddScoped<ErrorLogService>();
// Support
builder.Services.AddSingleton<SupportServerService>();

View file

@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@inject UserRepository UserRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (User == null)
{
<TL>Password change for</TL> @(Data[0])
}
else
{
<TL>Password change for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private User? User;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
await InvokeAsync(StateHasChanged);
}
}
}

View file

@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories.Servers
@inject ServerRepository ServerRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (Server == null)
{
<TL>Change power state for</TL> @(Data[0]) <TL>to</TL> @(Data[1])
}
else
{
<TL>Change power state for</TL> <a href="/admin/servers/edit/@(Server.Id)">@(Server.Name)</a> <TL>to</TL> @(Data[1])
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private Server? Server;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == Guid.Parse(Data[0]));
await InvokeAsync(StateHasChanged);
}
}
}

View file

@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@inject UserRepository UserRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (User == null)
{
<TL>New login for</TL> @(Data[0])
}
else
{
<TL>New login for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private User? User;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
await InvokeAsync(StateHasChanged);
}
}
}

View file

@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities.LogsEntries
@using Moonlight.App.Helpers
@using Moonlight.App.Repositories
@using Newtonsoft.Json
@using Moonlight.App.Database.Entities
@inject UserRepository UserRepository
<div class="timeline-item">
<div class="timeline-line w-40px"></div>
<div class="timeline-icon symbol symbol-circle symbol-40px">
<div class="symbol-label bg-light">
<i class="bx bx-md bx-log-in"></i>
</div>
</div>
<div class="timeline-content mb-10 mt-n2">
<div class="overflow-auto pe-3">
<div class="fs-5 fw-semibold mb-2">
@if (User == null)
{
<TL>Register for</TL> @(Data[0])
}
else
{
<TL>Register for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
}
</div>
<div class="d-flex align-items-center mt-1 fs-6">
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public AuditLogEntry Entry { get; set; }
private User? User;
private string[] Data;
protected override void OnInitialized()
{
Data = JsonConvert.DeserializeObject<string[]>(Entry.JsonData)!;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0]);
await InvokeAsync(StateHasChanged);
}
}
}

View file

@ -1,9 +1,10 @@
@using Logging.Net
@using Moonlight.App.Services.LogServices
@using Moonlight.App.Services.Sessions
@inherits ErrorBoundary
@inject IdentityService IdentityService
@inject ErrorLogService ErrorLogService
@if (CurrentException is null)
{
@ -52,13 +53,7 @@ else
{
receivedExceptions.Add(exception);
var user = await IdentityService.Get();
var id = user == null ? -1 : user.Id;
Logger.Error($"[{id}] An unhanded exception occured:");
Logger.Error(exception);
//TODO: Create error report
await ErrorLogService.Log(exception);
await base.OnErrorAsync(exception);
}

View file

@ -13,6 +13,8 @@
{
protected override async Task OnErrorAsync(Exception exception)
{
Logger.Debug(exception);
if (exception is DisplayException displayException)
{
await AlertService.Error(

View file

@ -0,0 +1,25 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/system">
Overview
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/system/auditlog">
AuditLog
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View file

@ -93,7 +93,7 @@ else
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/general">
<a class="menu-link" href="/admin/system">
<span class="menu-icon">
<i class="bx bx-chip"></i>
</span>

View file

@ -44,11 +44,11 @@
}
}
</select>
<WorkerButton
<WButton
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary"></WorkerButton>
CssClasses="btn-primary"></WButton>
</LazyLoader>
</div>
</div>

View file

@ -0,0 +1,106 @@
@page "/admin/system/auditlog"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Repositories.LogEntries
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities.LogsEntries
@using BlazorTable
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.Shared.Components.AuditLogEntrys
@inject AuditLogEntryRepository AuditLogEntryRepository
<OnlyAdmin>
<AdminSystemNavigation Index="1"/>
<div class="card">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<span class="me-3 lh-0">
<i class="bx bx-md bx-calendar"></i>
</span>
<h3 class="fw-bold m-0 text-gray-800">@(Formatter.FormatDateOnly(DateTime))</h3>
</div>
<div class="card-toolbar m-0">
<ul class="nav nav-tabs nav-line-tabs nav-stretch fs-6 border-0 fw-bold">
<li class="nav-item">
<button @onclick="Left" class="nav-link justify-content-center text-active-gray-800">
<i class="bx bx-md bx-left-arrow"></i>
</button>
</li>
<li class="nav-item">
<button @onclick="Right" class="nav-link justify-content-center text-active-gray-800">
<i class="bx bx-md bx-right-arrow"></i>
</button>
</li>
</ul>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (AuditLogEntries.Any())
{
<div class="timeline">
@foreach (var entry in AuditLogEntries)
{
switch (entry.Type)
{
case AuditLogType.Login:
<AuditLogEntryLogin Entry="entry"/>
break;
case AuditLogType.Register:
<AuditLogEntryRegister Entry="entry"/>
break;
case AuditLogType.ChangePassword:
<AuditLogEntryChangePassword Entry="entry"/>
break;
case AuditLogType.ChangePowerState:
<AuditLogEntryChangePowerState Entry="entry"/>
break;
}
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No records found for this day</TL>
</div>
}
</LazyLoader>
</div>
</div>
</OnlyAdmin>
@code
{
private AuditLogEntry[] AuditLogEntries;
private LazyLoader LazyLoader;
private DateTime DateTime = DateTime.Today;
private Task Load(LazyLoader arg)
{
AuditLogEntries = AuditLogEntryRepository
.Get()
.Where(x => x.CreatedAt.Date == DateTime.Date)
.OrderByDescending(x => x.Id)
.ToArray();
return Task.CompletedTask;
}
private async Task Left()
{
DateTime = DateTime.AddDays(1);
await LazyLoader.Reload();
}
private async Task Right()
{
DateTime = DateTime.AddDays(-1);
await LazyLoader.Reload();
}
}

View file

@ -0,0 +1,9 @@
@page "/admin/system"
@using Moonlight.Shared.Components.Navigations
<OnlyAdmin>
<AdminSystemNavigation Index="0" />
</OnlyAdmin>

View file

@ -31,6 +31,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQWxlcnRzXFNldHVwQ29tcGxldGVkQWxlcnQucmF6b3I=
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryLogin.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQXVkaXRMb2dFbnRyeXNcQXVkaXRMb2dFbnRyeUxvZ2luLnJhem9y
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Auth/Login.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcQXV0aFxMb2dpbi5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
@ -67,6 +71,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcRm9ybXNcV0J1dHRvbi5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcTmF2aWdhdGlvbnNcQWRtaW5TeXN0ZW1OYXZpZ2F0aW9uLnJhem9y
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXENvbXBvbmVudHNcTmF2aWdhdGlvbnNcUHJvZmlsZU5hdmlnYXRpb24ucmF6b3I=
build_metadata.AdditionalFiles.CssScope =
@ -227,6 +235,18 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN1cHBvcnRcVmlldy5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Admin/Sys/AuditLog.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN5c1xBdWRpdExvZy5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Admin/Sys/Index.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN5c1xJbmRleC5yYXpvcg==
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Admin/Sys/Logging.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXEFkbWluXFN5c1xMb2dnaW5nLnJhem9y
build_metadata.AdditionalFiles.CssScope =
[C:/Users/marce/GitHub/Moonlight-Panel/Moonlight/Moonlight/Shared/Views/Domains.razor]
build_metadata.AdditionalFiles.TargetPath = U2hhcmVkXFZpZXdzXERvbWFpbnMucmF6b3I=
build_metadata.AdditionalFiles.CssScope =

View file

@ -257,3 +257,13 @@ Cancel Subscription;Cancel Subscription
Active until;Active until
We will send you a notification upon subscription expiration;We will send you a notification upon subscription expiration
This token has been already used;This token has been already used
New login for;New login for
No records found for this day;No records found for this day
Change;Change
Changing;Changing
Minecraft version;Minecraft version
Build version;Build version
Server installation is currently running;Server installation is currently running
Selected;Selected
Move deleted;Move deleted
Delete selected;Delete selected