Merge pull request #46 from Moonlight-Panel/PleskIntegration

Plesk integration
This commit is contained in:
Marcel Baumgartner 2023-04-06 18:54:44 +02:00 committed by GitHub
commit 9188323594
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 4290 additions and 74 deletions

View file

@ -39,6 +39,8 @@ public class DataContext : DbContext
public DbSet<NotificationAction> NotificationActions { get; set; } public DbSet<NotificationAction> NotificationActions { get; set; }
public DbSet<DdosAttack> DdosAttacks { get; set; } public DbSet<DdosAttack> DdosAttacks { get; set; }
public DbSet<Subscription> Subscriptions { get; set; } public DbSet<Subscription> Subscriptions { get; set; }
public DbSet<PleskServer> PleskServers { get; set; }
public DbSet<Website> Websites { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {

View file

@ -0,0 +1,9 @@
namespace Moonlight.App.Database.Entities;
public class PleskServer
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string ApiUrl { get; set; } = "";
public string ApiKey { get; set; } = "";
}

View file

@ -0,0 +1,12 @@
namespace Moonlight.App.Database.Entities;
public class Website
{
public int Id { get; set; }
public string BaseDomain { get; set; } = "";
public int PleskId { get; set; }
public PleskServer PleskServer { get; set; }
public User Owner { get; set; }
public string FtpLogin { get; set; } = "";
public string FtpPassword { get; set; } = "";
}

View file

@ -0,0 +1,946 @@
// <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("20230404181522_AddedPleskAndWebsiteModels")]
partial class AddedPleskAndWebsiteModels
{
/// <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.DdosAttack", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<long>("Data")
.HasColumnType("bigint");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NodeId")
.HasColumnType("int");
b.Property<bool>("Ongoing")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("NodeId");
b.ToTable("DdosAttacks");
});
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<int>("Allocations")
.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<string>("TagsJson")
.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<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
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.PleskServer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ApiKey")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ApiUrl")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PleskServers");
});
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<bool>("IsCleanupException")
.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<string>("LimitsJson")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
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<int?>("CurrentSubscriptionId")
.HasColumnType("int");
b.Property<long>("DiscordId")
.HasColumnType("bigint");
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<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("CurrentSubscriptionId");
b.ToTable("Users");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("BaseDomain")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<int>("PleskId")
.HasColumnType("int");
b.Property<int>("PleskServerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("PleskServerId");
b.ToTable("Websites");
});
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.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.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", "CurrentSubscription")
.WithMany()
.HasForeignKey("CurrentSubscriptionId");
b.Navigation("CurrentSubscription");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.PleskServer", "PleskServer")
.WithMany()
.HasForeignKey("PleskServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("PleskServer");
});
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");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,84 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedPleskAndWebsiteModels : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PleskServers",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ApiUrl = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ApiKey = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_PleskServers", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "Websites",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
BaseDomain = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
PleskId = table.Column<int>(type: "int", nullable: false),
PleskServerId = table.Column<int>(type: "int", nullable: false),
OwnerId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Websites", x => x.Id);
table.ForeignKey(
name: "FK_Websites_PleskServers_PleskServerId",
column: x => x.PleskServerId,
principalTable: "PleskServers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Websites_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Websites_OwnerId",
table: "Websites",
column: "OwnerId");
migrationBuilder.CreateIndex(
name: "IX_Websites_PleskServerId",
table: "Websites",
column: "PleskServerId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Websites");
migrationBuilder.DropTable(
name: "PleskServers");
}
}
}

View file

@ -0,0 +1,954 @@
// <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("20230405162507_UpdatedWebsiteModel")]
partial class UpdatedWebsiteModel
{
/// <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.DdosAttack", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<long>("Data")
.HasColumnType("bigint");
b.Property<string>("Ip")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("NodeId")
.HasColumnType("int");
b.Property<bool>("Ongoing")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.HasIndex("NodeId");
b.ToTable("DdosAttacks");
});
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<int>("Allocations")
.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<string>("TagsJson")
.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<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
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.PleskServer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ApiKey")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ApiUrl")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PleskServers");
});
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<bool>("IsCleanupException")
.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<string>("LimitsJson")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
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<int?>("CurrentSubscriptionId")
.HasColumnType("int");
b.Property<long>("DiscordId")
.HasColumnType("bigint");
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<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("CurrentSubscriptionId");
b.ToTable("Users");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("BaseDomain")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FtpLogin")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FtpPassword")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<int>("PleskId")
.HasColumnType("int");
b.Property<int>("PleskServerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("PleskServerId");
b.ToTable("Websites");
});
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.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.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", "CurrentSubscription")
.WithMany()
.HasForeignKey("CurrentSubscriptionId");
b.Navigation("CurrentSubscription");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.PleskServer", "PleskServer")
.WithMany()
.HasForeignKey("PleskServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("PleskServer");
});
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");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class UpdatedWebsiteModel : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "FtpLogin",
table: "Websites",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AddColumn<string>(
name: "FtpPassword",
table: "Websites",
type: "longtext",
nullable: false)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "FtpLogin",
table: "Websites");
migrationBuilder.DropColumn(
name: "FtpPassword",
table: "Websites");
}
}
}

View file

@ -395,6 +395,29 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("NotificationClients"); b.ToTable("NotificationClients");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.PleskServer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ApiKey")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ApiUrl")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PleskServers");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -697,6 +720,42 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("Users"); b.ToTable("Users");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("BaseDomain")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FtpLogin")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FtpPassword")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<int>("PleskId")
.HasColumnType("int");
b.Property<int>("PleskServerId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("OwnerId");
b.HasIndex("PleskServerId");
b.ToTable("Websites");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
{ {
b.HasOne("Moonlight.App.Database.Entities.Node", "Node") b.HasOne("Moonlight.App.Database.Entities.Node", "Node")
@ -847,6 +906,25 @@ namespace Moonlight.App.Database.Migrations
b.Navigation("CurrentSubscription"); b.Navigation("CurrentSubscription");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.PleskServer", "PleskServer")
.WithMany()
.HasForeignKey("PleskServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner");
b.Navigation("PleskServer");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
{ {
b.Navigation("DockerImages"); b.Navigation("DockerImages");

View file

@ -0,0 +1,32 @@
using System.Runtime.Serialization;
namespace Moonlight.App.Exceptions;
[Serializable]
public class DaemonException : Exception
{
public int StatusCode { private get; set; }
public DaemonException()
{
}
public DaemonException(string message, int statusCode) : base(message)
{
StatusCode = statusCode;
}
public DaemonException(string message) : base(message)
{
}
public DaemonException(string message, Exception inner) : base(message, inner)
{
}
protected DaemonException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}

View file

@ -0,0 +1,32 @@
using System.Runtime.Serialization;
namespace Moonlight.App.Exceptions;
[Serializable]
public class PleskException : Exception
{
public int StatusCode { private get; set; }
public PleskException()
{
}
public PleskException(string message, int statusCode) : base(message)
{
StatusCode = statusCode;
}
public PleskException(string message) : base(message)
{
}
public PleskException(string message, Exception inner) : base(message, inner)
{
}
protected PleskException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}

View file

@ -37,7 +37,7 @@ public class DaemonApiHelper
{ {
if (response.StatusCode != 0) if (response.StatusCode != 0)
{ {
throw new WingsException( throw new DaemonException(
$"An error occured: ({response.StatusCode}) {response.Content}", $"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode (int)response.StatusCode
); );

View file

@ -1,6 +1,7 @@
using System.Net; using System.Net;
using System.Text; using System.Text;
using FluentFTP; using FluentFTP;
using Moonlight.App.Exceptions;
namespace Moonlight.App.Helpers.Files; namespace Moonlight.App.Helpers.Files;
@ -78,10 +79,10 @@ public class FtpFileAccess : FileAccess
{ {
await EnsureConnect(); await EnsureConnect();
var s = new MemoryStream(); var s = new MemoryStream(8 * 1024 * 1204); //TODO: Add config option
await Client.DownloadStream(s, CurrentPath.TrimEnd('/') + "/" + fileData.Name); await Client.DownloadStream(s, CurrentPath.TrimEnd('/') + "/" + fileData.Name);
var data = s.ToArray(); var data = s.ToArray();
s.Dispose(); await s.DisposeAsync();
var str = Encoding.UTF8.GetString(data); var str = Encoding.UTF8.GetString(data);
return str; return str;
} }
@ -90,11 +91,11 @@ public class FtpFileAccess : FileAccess
{ {
await EnsureConnect(); await EnsureConnect();
var s = new MemoryStream(); var s = new MemoryStream(8 * 1024 * 1204); //TODO: Add config option
s.Write(Encoding.UTF8.GetBytes(content)); s.Write(Encoding.UTF8.GetBytes(content));
s.Position = 0; s.Position = 0;
await Client.UploadStream(s, CurrentPath.TrimEnd('/') + "/" + fileData.Name, FtpRemoteExists.Overwrite); await Client.UploadStream(s, CurrentPath.TrimEnd('/') + "/" + fileData.Name, FtpRemoteExists.Overwrite);
s.Dispose(); await s.DisposeAsync();
} }
public override async Task Upload(string name, Stream dataStream, Action<int>? progressUpdated = null) public override async Task Upload(string name, Stream dataStream, Action<int>? progressUpdated = null)
@ -103,10 +104,9 @@ public class FtpFileAccess : FileAccess
IProgress<FtpProgress> progress = new Progress<FtpProgress>(x => IProgress<FtpProgress> progress = new Progress<FtpProgress>(x =>
{ {
progressUpdated((int) x.Progress); progressUpdated?.Invoke((int)x.Progress);
}); });
await Client.UploadStream(dataStream, CurrentPath.TrimEnd('/') + "/" + name, FtpRemoteExists.Overwrite, false, progress); await Client.UploadStream(dataStream, CurrentPath.TrimEnd('/') + "/" + name, FtpRemoteExists.Overwrite, false, progress);
dataStream.Dispose();
} }
public override async Task MkDir(string name) public override async Task MkDir(string name)
@ -121,10 +121,8 @@ public class FtpFileAccess : FileAccess
return Task.FromResult(CurrentPath); return Task.FromResult(CurrentPath);
} }
public override async Task<string> DownloadUrl(FileData fileData) public override Task<string> DownloadUrl(FileData fileData)
{ {
await EnsureConnect();
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -132,8 +130,12 @@ public class FtpFileAccess : FileAccess
{ {
await EnsureConnect(); await EnsureConnect();
var s = new MemoryStream(); var s = new MemoryStream(8 * 1024 * 1204); //TODO: Add config option
await Client.DownloadStream(s, CurrentPath.TrimEnd('/') + "/" + fileData.Name); var downloaded = await Client.DownloadStream(s, CurrentPath.TrimEnd('/') + "/" + fileData.Name);
if (!downloaded)
throw new DisplayException("Unable to download file");
return s; return s;
} }
@ -157,17 +159,13 @@ public class FtpFileAccess : FileAccess
await Client.MoveDirectory(CurrentPath.TrimEnd('/') + "/" + fileData.Name, newPath); await Client.MoveDirectory(CurrentPath.TrimEnd('/') + "/" + fileData.Name, newPath);
} }
public override async Task Compress(params FileData[] files) public override Task Compress(params FileData[] files)
{ {
await EnsureConnect();
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override async Task Decompress(FileData fileData) public override Task Decompress(FileData fileData)
{ {
await EnsureConnect();
throw new NotImplementedException(); throw new NotImplementedException();
} }

View file

@ -0,0 +1,220 @@
using System.Text;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Newtonsoft.Json;
using RestSharp;
namespace Moonlight.App.Helpers;
public class PleskApiHelper
{
private readonly RestClient Client;
public PleskApiHelper()
{
Client = new();
}
public async Task<T> Get<T>(PleskServer server, string resource)
{
var request = CreateRequest(server, resource);
request.Method = Method.Get;
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new PleskException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
return JsonConvert.DeserializeObject<T>(response.Content!)!;
}
public async Task<string> GetRaw(PleskServer server, string resource)
{
var request = CreateRequest(server, resource);
request.Method = Method.Get;
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new PleskException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
return response.Content!;
}
public async Task<T> Post<T>(PleskServer server, string resource, object? body)
{
var request = CreateRequest(server, resource);
request.Method = Method.Post;
request.AddParameter("text/plain",
JsonConvert.SerializeObject(body),
ParameterType.RequestBody
);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new PleskException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
return JsonConvert.DeserializeObject<T>(response.Content!)!;
}
public async Task Post(PleskServer server, string resource, object? body)
{
var request = CreateRequest(server, resource);
request.Method = Method.Post;
if(body != null)
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new PleskException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
public async Task PostRaw(PleskServer server, string resource, object body)
{
var request = CreateRequest(server, resource);
request.Method = Method.Post;
request.AddParameter("text/plain", body, ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new PleskException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
public async Task Delete(PleskServer server, string resource, object? body)
{
var request = CreateRequest(server, resource);
request.Method = Method.Delete;
if(body != null)
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new PleskException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
public async Task Put(PleskServer server, string resource, object? body)
{
var request = CreateRequest(server, resource);
request.Method = Method.Put;
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new PleskException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
private RestRequest CreateRequest(PleskServer pleskServer, string resource)
{
var url = $"{pleskServer.ApiUrl}/" + resource;
var request = new RestRequest(url);
var ba = Convert.ToBase64String(Encoding.UTF8.GetBytes(pleskServer.ApiKey));
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization", "Basic " + ba);
return request;
}
}

View file

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
namespace Moonlight.App.Models.Forms;
public class DatabaseDataModel
{
[Required(ErrorMessage = "You need to enter a name")]
[MinLength(8, ErrorMessage = "The name should be at least 8 characters long")]
[MaxLength(32, ErrorMessage = "The database name should be maximal 32 characters")]
[RegularExpression(@"^[a-z0-9]+$", ErrorMessage = "The name should only contain of lower case characters and numbers")]
public string Name { get; set; } = "";
[Required(ErrorMessage = "You need to enter a password")]
[MinLength(8, ErrorMessage = "The password should be at least 8 characters long")]
[MaxLength(32, ErrorMessage = "The password name should be maximal 32 characters")]
public string Password { get; set; } = "";
}

View file

@ -0,0 +1,16 @@
using System.ComponentModel.DataAnnotations;
namespace Moonlight.App.Models.Forms;
public class PleskServerDataModel
{
[Required(ErrorMessage = "You have to enter a name")]
[MaxLength(32, ErrorMessage = "The name should not be longer than 32 characters")]
public string Name { get; set; }
[Required(ErrorMessage = "You need to enter an api url")]
public string ApiUrl { get; set; }
[Required(ErrorMessage = "You need to enter an api key")]
public string ApiKey { get; set; }
}

View file

@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
using Moonlight.App.Database.Entities;
namespace Moonlight.App.Models.Forms;
public class WebsiteAdminDataModel
{
[Required(ErrorMessage = "You need a domain")]
[RegularExpression(@"([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+", ErrorMessage = "You need to enter a valid domain")]
public string BaseDomain { get; set; } = "";
[Required(ErrorMessage = "You need to specify a owner")]
public User User { get; set; }
}

View file

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Moonlight.App.Models.Forms;
public class WebsiteDataModel
{
[Required(ErrorMessage = "You need a domain")]
[RegularExpression(@"([a-z0-9|-]+\.)*[a-z0-9|-]+\.[a-z]+", ErrorMessage = "You need to enter a valid domain")]
public string BaseDomain { get; set; } = "";
}

View file

@ -0,0 +1,10 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Requests;
public class CliCall
{
[JsonProperty("params")] public List<string> Params { get; set; } = new();
[JsonProperty("env")] public Dictionary<string, string> Env { get; set; } = new();
}

View file

@ -0,0 +1,23 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Requests;
public class CreateDatabase
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("parent_domain")] public ParentDomainModel ParentDomain { get; set; } = new();
[JsonProperty("server_id")]
public int ServerId { get; set; }
public class ParentDomainModel
{
[JsonProperty("name")]
public string Name { get; set; }
}
}

View file

@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Requests;
public class CreateDatabaseUser
{
[JsonProperty("login")]
public string Login { get; set; }
[JsonProperty("password")]
public string Password { get; set; }
[JsonProperty("database_id")]
public int DatabaseId { get; set; }
}

View file

@ -0,0 +1,45 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Requests;
public class CreateDomain
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("hosting_type")]
public string HostingType { get; set; }
[JsonProperty("hosting_settings")]
public HostingSettingsModel HostingSettings { get; set; }
[JsonProperty("owner_client")]
public OwnerClientModel OwnerClient { get; set; }
[JsonProperty("plan")]
public PlanModel Plan { get; set; }
public partial class HostingSettingsModel
{
[JsonProperty("ftp_login")]
public string FtpLogin { get; set; }
[JsonProperty("ftp_password")]
public string FtpPassword { get; set; }
}
public partial class OwnerClientModel
{
[JsonProperty("id")]
public long Id { get; set; }
}
public partial class PlanModel
{
[JsonProperty("name")]
public string Name { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Resources;
public class CliResult
{
[JsonProperty("code")]
public int Code { get; set; }
[JsonProperty("stdout")] public string Stdout { get; set; } = "";
[JsonProperty("stderr")] public string Stderr { get; set; } = "";
}

View file

@ -0,0 +1,45 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Resources;
public class Client
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("created")]
public DateTimeOffset Created { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("company")]
public string Company { get; set; }
[JsonProperty("login")]
public string Login { get; set; }
[JsonProperty("status")]
public long Status { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("locale")]
public string Locale { get; set; }
[JsonProperty("guid")]
public Guid Guid { get; set; }
[JsonProperty("owner_login")]
public string OwnerLogin { get; set; }
[JsonProperty("external_id")]
public string ExternalId { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}

View file

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Resources;
public class CreateResult
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("guid")]
public Guid Guid { get; set; }
}

View file

@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Resources;
public class Database
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}

View file

@ -0,0 +1,30 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Resources;
public class DatabaseServer
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("host")]
public string Host { get; set; }
[JsonProperty("port")]
public int Port { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("db_count")]
public int DbCount { get; set; }
[JsonProperty("is_default")]
public bool IsDefault { get; set; }
[JsonProperty("is_local")]
public bool IsLocal { get; set; }
}

View file

@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Resources;
public class DatabaseUser
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("login")]
public string Login { get; set; }
[JsonProperty("database_id")]
public int DatabaseId { get; set; }
}

View file

@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace Moonlight.App.Models.Plesk.Resources;
public class ServerStatus
{
[JsonProperty("platform")]
public string Platform { get; set; }
[JsonProperty("hostname")]
public string Hostname { get; set; }
[JsonProperty("guid")]
public Guid Guid { get; set; }
[JsonProperty("panel_version")]
public string PanelVersion { get; set; }
}

View file

@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database;
using Moonlight.App.Database.Entities;
namespace Moonlight.App.Repositories;
public class PleskServerRepository : IDisposable
{
private readonly DataContext DataContext;
public PleskServerRepository(DataContext dataContext)
{
DataContext = dataContext;
}
public DbSet<PleskServer> Get()
{
return DataContext.PleskServers;
}
public PleskServer Add(PleskServer pleskServer)
{
var x = DataContext.PleskServers.Add(pleskServer);
DataContext.SaveChanges();
return x.Entity;
}
public void Update(PleskServer pleskServer)
{
DataContext.PleskServers.Update(pleskServer);
DataContext.SaveChanges();
}
public void Delete(PleskServer pleskServer)
{
DataContext.PleskServers.Remove(pleskServer);
DataContext.SaveChanges();
}
public void Dispose()
{
DataContext.Dispose();
}
}

View file

@ -0,0 +1,44 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database;
using Moonlight.App.Database.Entities;
namespace Moonlight.App.Repositories;
public class WebsiteRepository : IDisposable
{
private readonly DataContext DataContext;
public WebsiteRepository(DataContext dataContext)
{
DataContext = dataContext;
}
public DbSet<Website> Get()
{
return DataContext.Websites;
}
public Website Add(Website website)
{
var x = DataContext.Websites.Add(website);
DataContext.SaveChanges();
return x.Entity;
}
public void Update(Website website)
{
DataContext.Websites.Update(website);
DataContext.SaveChanges();
}
public void Delete(Website website)
{
DataContext.Websites.Remove(website);
DataContext.SaveChanges();
}
public void Dispose()
{
DataContext.Dispose();
}
}

View file

@ -41,4 +41,22 @@ public class NodeService
{ {
return await DaemonApiHelper.Get<ContainerStats>(node, "stats/container"); return await DaemonApiHelper.Get<ContainerStats>(node, "stats/container");
} }
public async Task<bool> IsHostUp(Node node)
{
try
{
//TODO: Implement status caching
var data = await GetStatus(node);
if (data != null)
return true;
}
catch (Exception)
{
// ignored
}
return false;
}
} }

View file

@ -29,6 +29,7 @@ public class ServerService
private readonly SecurityLogService SecurityLogService; private readonly SecurityLogService SecurityLogService;
private readonly AuditLogService AuditLogService; private readonly AuditLogService AuditLogService;
private readonly ErrorLogService ErrorLogService; private readonly ErrorLogService ErrorLogService;
private readonly NodeService NodeService;
public ServerService( public ServerService(
ServerRepository serverRepository, ServerRepository serverRepository,
@ -42,7 +43,8 @@ public class ServerService
WingsJwtHelper wingsJwtHelper, WingsJwtHelper wingsJwtHelper,
SecurityLogService securityLogService, SecurityLogService securityLogService,
AuditLogService auditLogService, AuditLogService auditLogService,
ErrorLogService errorLogService) ErrorLogService errorLogService,
NodeService nodeService)
{ {
ServerRepository = serverRepository; ServerRepository = serverRepository;
WingsApiHelper = wingsApiHelper; WingsApiHelper = wingsApiHelper;
@ -56,6 +58,7 @@ public class ServerService
SecurityLogService = securityLogService; SecurityLogService = securityLogService;
AuditLogService = auditLogService; AuditLogService = auditLogService;
ErrorLogService = errorLogService; ErrorLogService = errorLogService;
NodeService = nodeService;
} }
private Server EnsureNodeData(Server s) private Server EnsureNodeData(Server s)
@ -252,16 +255,14 @@ public class ServerService
Node node; Node node;
if (n == null) if (n == null)
{
node = NodeRepository.Get().Include(x => x.Allocations).First(); //TODO: Smart deploy
}
else
{ {
node = NodeRepository node = NodeRepository
.Get() .Get()
.Include(x => x.Allocations) .Include(x => x.Allocations)
.First(x => x.Id == n.Id); .First(x => x.Id == n.Id);
} }
else
node = n;
NodeAllocation freeAllo; NodeAllocation freeAllo;
@ -395,4 +396,11 @@ public class ServerService
ServerRepository.Delete(s); ServerRepository.Delete(s);
} }
public async Task<bool> IsHostUp(Server s)
{
var server = EnsureNodeData(s);
return await NodeService.IsHostUp(server.Node);
}
} }

View file

@ -0,0 +1,383 @@
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Helpers.Files;
using Moonlight.App.Models.Plesk.Requests;
using Moonlight.App.Models.Plesk.Resources;
using Moonlight.App.Repositories;
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
namespace Moonlight.App.Services;
public class WebsiteService
{
private readonly WebsiteRepository WebsiteRepository;
private readonly PleskServerRepository PleskServerRepository;
private readonly PleskApiHelper PleskApiHelper;
private readonly UserRepository UserRepository;
public WebsiteService(WebsiteRepository websiteRepository, PleskApiHelper pleskApiHelper, PleskServerRepository pleskServerRepository, UserRepository userRepository)
{
WebsiteRepository = websiteRepository;
PleskApiHelper = pleskApiHelper;
PleskServerRepository = pleskServerRepository;
UserRepository = userRepository;
}
public async Task<Website> Create(string baseDomain, User owner, PleskServer? ps = null)
{
if (WebsiteRepository.Get().Any(x => x.BaseDomain == baseDomain))
throw new DisplayException("A website with this domain does already exist");
var pleskServer = ps ?? PleskServerRepository.Get().First();
var ftpLogin = baseDomain;
var ftpPassword = StringHelper.GenerateString(16);
var w = new Website()
{
PleskServer = pleskServer,
Owner = owner,
BaseDomain = baseDomain,
PleskId = 0,
FtpPassword = ftpPassword,
FtpLogin = ftpLogin
};
var website = WebsiteRepository.Add(w);
try
{
var id = await GetAdminAccount(pleskServer);
var result = await PleskApiHelper.Post<CreateResult>(pleskServer, "domains", new CreateDomain()
{
Description = $"moonlight website {website.Id}",
Name = baseDomain,
HostingType = "virtual",
Plan = new()
{
Name = "Unlimited"
},
HostingSettings = new()
{
FtpLogin = ftpLogin,
FtpPassword = ftpPassword
},
OwnerClient = new()
{
Id = id
}
});
website.PleskId = result.Id;
WebsiteRepository.Update(website);
}
catch (Exception e)
{
WebsiteRepository.Delete(website);
throw;
}
return website;
}
public async Task Delete(Website w)
{
var website = EnsureData(w);
await PleskApiHelper.Delete(website.PleskServer, $"domains/{w.PleskId}", null);
WebsiteRepository.Delete(website);
}
public async Task<bool> IsHostUp(PleskServer pleskServer)
{
try
{
var res = await PleskApiHelper.Get<ServerStatus>(pleskServer, "server");
if (res != null)
return true;
}
catch (Exception e)
{
// ignored
}
return false;
}
public async Task<bool> IsHostUp(Website w)
{
var website = EnsureData(w);
try
{
var res = await PleskApiHelper.Get<ServerStatus>(website.PleskServer, "server");
if (res != null)
return true;
}
catch (Exception)
{
// ignored
}
return false;
}
#region Get host
public async Task<string> GetHost(PleskServer pleskServer)
{
return (await PleskApiHelper.Get<ServerStatus>(pleskServer, "server")).Hostname;
}
public async Task<string> GetHost(Website w)
{
var website = EnsureData(w);
return await GetHost(website.PleskServer);
}
#endregion
private async Task<int> GetAdminAccount(PleskServer pleskServer)
{
var users = await PleskApiHelper.Get<Client[]>(pleskServer, "clients");
var user = users.FirstOrDefault(x => x.Type == "admin");
if (user == null)
throw new DisplayException("No admin account in plesk found");
return user.Id;
}
#region SSL
public async Task<string[]> GetSslCertificates(Website w)
{
var website = EnsureData(w);
var certs = new List<string>();
var data = await ExecuteCli(website.PleskServer, "certificate", p =>
{
p.Add("-l");
p.Add("-domain");
p.Add(w.BaseDomain);
});
string[] lines = data.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
if (line.Contains("Lets Encrypt"))
{
string[] parts = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if(parts.Length > 6)
certs.Add($"{parts[4]} {parts[5]} {parts[6]}");
}
else if (line.Contains("Listing of SSL/TLS certificates repository was successful"))
{
// This line indicates the end of the certificate listing, so we can stop parsing
break;
}
}
return certs.ToArray();
}
public async Task CreateSslCertificate(Website w)
{
var website = EnsureData(w);
await ExecuteCli(website.PleskServer, "extension", p =>
{
p.Add("--exec");
p.Add("letsencrypt");
p.Add("cli.php");
p.Add("-d");
p.Add(website.BaseDomain);
p.Add("-m");
p.Add(website.Owner.Email);
});
}
public async Task DeleteSslCertificate(Website w, string name)
{
var website = EnsureData(w);
try
{
await ExecuteCli(website.PleskServer, "site", p =>
{
p.Add("-u");
p.Add(website.BaseDomain);
p.Add("-ssl");
p.Add("false");
});
try
{
await ExecuteCli(website.PleskServer, "certificate", p =>
{
p.Add("--remove");
p.Add(name);
p.Add("-domain");
p.Add(website.BaseDomain);
});
}
catch (Exception e)
{
Logger.Warn("Error removing ssl certificate");
Logger.Warn(e);
throw new DisplayException("An unknown error occured while removing ssl certificate");
}
}
catch (DisplayException)
{
// Redirect all display exception to soft error handler
throw;
}
catch (Exception e)
{
Logger.Warn("Error disabling ssl certificate");
Logger.Warn(e);
throw new DisplayException("An unknown error occured while disabling ssl certificate");
}
}
#endregion
#region Databases
public async Task<Models.Plesk.Resources.Database[]> GetDatabases(Website w)
{
var website = EnsureData(w);
var dbs = await PleskApiHelper.Get<Models.Plesk.Resources.Database[]>(
website.PleskServer,
$"databases?domain={w.BaseDomain}"
);
return dbs;
}
public async Task CreateDatabase(Website w, string name, string password)
{
var website = EnsureData(w);
var server = await GetDefaultDatabaseServer(website);
if (server == null)
throw new DisplayException("No database server marked as default found");
var dbReq = new CreateDatabase()
{
Name = name,
Type = "mysql",
ParentDomain = new()
{
Name = website.BaseDomain
},
ServerId = server.Id
};
var db = await PleskApiHelper.Post<Models.Plesk.Resources.Database>(website.PleskServer, "databases", dbReq);
if (db == null)
throw new DisplayException("Unable to create database via api");
var dbUserReq = new CreateDatabaseUser()
{
DatabaseId = db.Id,
Login = name,
Password = password
};
await PleskApiHelper.Post(website.PleskServer, "dbusers", dbUserReq);
}
public async Task DeleteDatabase(Website w, Models.Plesk.Resources.Database database)
{
var website = EnsureData(w);
var dbUsers = await PleskApiHelper.Get<DatabaseUser[]>(
website.PleskServer,
$"dbusers?dbId={database.Id}"
);
foreach (var dbUser in dbUsers)
{
await PleskApiHelper.Delete(website.PleskServer, $"dbusers/{dbUser.Id}", null);
}
await PleskApiHelper.Delete(website.PleskServer, $"databases/{database.Id}", null);
}
public async Task<DatabaseServer?> GetDefaultDatabaseServer(PleskServer pleskServer)
{
var dbServers = await PleskApiHelper.Get<DatabaseServer[]>(pleskServer, "dbservers");
return dbServers.FirstOrDefault(x => x.IsDefault);
}
public async Task<DatabaseServer?> GetDefaultDatabaseServer(Website w)
{
var website = EnsureData(w);
return await GetDefaultDatabaseServer(website.PleskServer);
}
#endregion
public async Task<FileAccess> CreateFileAccess(Website w)
{
var website = EnsureData(w);
var host = await GetHost(website.PleskServer);
return new FtpFileAccess(host, 21, website.FtpLogin, website.FtpPassword);
}
private async Task<string> ExecuteCli(
PleskServer server,
string cli, Action<List<string>>? parameters = null,
Action<Dictionary<string, string>>? variables = null
)
{
var p = new List<string>();
var v = new Dictionary<string, string>();
parameters?.Invoke(p);
variables?.Invoke(v);
var req = new CliCall()
{
Env = v,
Params = p
};
var res = await PleskApiHelper.Post<CliResult>(server, $"cli/{cli}/call", req);
return res.Stdout;
}
private Website EnsureData(Website website)
{
if (website.PleskServer == null || website.Owner == null)
return WebsiteRepository
.Get()
.Include(x => x.PleskServer)
.Include(x => x.Owner)
.First(x => x.Id == website.Id);
return website;
}
}

View file

@ -60,8 +60,10 @@ namespace Moonlight
builder.Services.AddScoped<NotificationRepository>(); builder.Services.AddScoped<NotificationRepository>();
builder.Services.AddScoped<DdosAttackRepository>(); builder.Services.AddScoped<DdosAttackRepository>();
builder.Services.AddScoped<SubscriptionRepository>(); builder.Services.AddScoped<SubscriptionRepository>();
builder.Services.AddScoped<PleskServerRepository>();
builder.Services.AddScoped<WebsiteRepository>();
builder.Services.AddScoped<LoadingMessageRepository>(); builder.Services.AddScoped<LoadingMessageRepository>();
builder.Services.AddScoped<AuditLogEntryRepository>(); builder.Services.AddScoped<AuditLogEntryRepository>();
builder.Services.AddScoped<ErrorLogEntryRepository>(); builder.Services.AddScoped<ErrorLogEntryRepository>();
builder.Services.AddScoped<SecurityLogEntryRepository>(); builder.Services.AddScoped<SecurityLogEntryRepository>();
@ -90,6 +92,7 @@ namespace Moonlight
builder.Services.AddScoped<NotificationClientService>(); builder.Services.AddScoped<NotificationClientService>();
builder.Services.AddScoped<ModalService>(); builder.Services.AddScoped<ModalService>();
builder.Services.AddScoped<SmartDeployService>(); builder.Services.AddScoped<SmartDeployService>();
builder.Services.AddScoped<WebsiteService>();
builder.Services.AddScoped<GoogleOAuth2Service>(); builder.Services.AddScoped<GoogleOAuth2Service>();
builder.Services.AddScoped<DiscordOAuth2Service>(); builder.Services.AddScoped<DiscordOAuth2Service>();
@ -121,6 +124,7 @@ namespace Moonlight
builder.Services.AddSingleton<PaperApiHelper>(); builder.Services.AddSingleton<PaperApiHelper>();
builder.Services.AddSingleton<HostSystemHelper>(); builder.Services.AddSingleton<HostSystemHelper>();
builder.Services.AddScoped<DaemonApiHelper>(); builder.Services.AddScoped<DaemonApiHelper>();
builder.Services.AddScoped<PleskApiHelper>();
// Background services // Background services
builder.Services.AddSingleton<DiscordBotService>(); builder.Services.AddSingleton<DiscordBotService>();

View file

@ -36,6 +36,17 @@
wingsException.Message wingsException.Message
); );
} }
else if (exception is PleskException pleskException)
{
await AlertService.Error(
SmartTranslateService.Translate("Error from plesk"),
pleskException.Message
);
}
else if (exception is NotImplementedException)
{
await AlertService.Error(SmartTranslateService.Translate("This function is not implemented"));
}
else else
{ {
throw exception; throw exception;

View file

@ -166,8 +166,8 @@ else
{ {
var stream = await Access.DownloadStream(x); var stream = await Access.DownloadStream(x);
await ToastService.Info(SmartTranslateService.Translate("Starting download")); await ToastService.Info(SmartTranslateService.Translate("Starting download"));
await FileService.AddBuffer(stream); stream.Position = 0;
await FileService.DownloadBinaryBuffers(x.Name); await FileService.DownloadFile(fileName: x.Name, stream: stream, contentType: "application/octet-stream");
} }
catch (NotImplementedException) catch (NotImplementedException)
{ {

View file

@ -35,12 +35,7 @@ else
{ {
if (Confirm) if (Confirm)
{ {
var b = await AlertService.YesNo( var b = await AlertService.ConfirmMath();
SmartTranslateService.Translate("Are you sure?"),
SmartTranslateService.Translate("Do you really want to delete it?"),
SmartTranslateService.Translate("Yes"),
SmartTranslateService.Translate("No")
);
if (b) if (b)
{ {

View file

@ -0,0 +1,22 @@
<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/websites">
<TL>Websites</TL>
</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/websites/servers">
<TL>Plesk servers</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View file

@ -155,32 +155,13 @@ else
</div> </div>
</div> </div>
</div> </div>
<div data-kt-menu-trigger="click" class="menu-item menu-accordion"> <div class="menu-item">
<span class="menu-link"> <a class="menu-link" href="/admin/websites">
<span class="menu-icon"> <span class="menu-icon">
<i class="bx bx-cube"></i> <i class="bx bx-globe"></i>
</span> </span>
<span class="menu-title"><TL>aaPanel</TL></span> <span class="menu-title"><TL>Websites</TL></span>
<span class="menu-arrow"></span> </a>
</span>
<div class="menu-sub menu-sub-accordion">
<div class="menu-item">
<a class="menu-link" href="/admin/aapanel/">
<span class="menu-bullet">
<span class="bullet bullet-dot"></span>
</span>
<span class="menu-title"><TL>Overview</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/aapanel/databases">
<span class="menu-bullet">
<span class="bullet bullet-dot"></span>
</span>
<span class="menu-title"><TL>Databases</TL></span>
</a>
</div>
</div>
</div> </div>
<div class="menu-item"> <div class="menu-item">
<a class="menu-link" href="/admin/users"> <a class="menu-link" href="/admin/users">

View file

@ -0,0 +1,111 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@inject WebsiteService WebsiteService
@inject SmartTranslateService SmartTranslateService
<div class="row gy-5 g-xl-10">
<div class="col-xl-4 mb-xl-10">
<div class="card h-md-100">
<div class="card-body d-flex flex-column flex-center">
<img class="img-fluid" src="https://image.thum.io/get/http://@(CurrentWebsite.BaseDomain)" alt="Website screenshot"/>
</div>
</div>
</div>
<div class="col-xl-8 mb-5 mb-xl-10">
<div class="card card-flush h-xl-100">
<div class="card-body pt-2">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="row mt-5">
<div class="card border">
<div class="card-header">
<span class="card-title">
<TL>SSL certificates</TL>
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("Issue certificate"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-success"
OnClick="CreateCertificate">
</WButton>
</div>
</div>
<div class="card-body">
@if (Certs.Any())
{
<table class="table align-middle gs-0 gy-3">
<thead>
<tr>
<th class="p-0 w-50px"></th>
<th class="p-0 min-w-150px"></th>
<th class="p-0 min-w-120px"></th>
</tr>
</thead>
<tbody>
@foreach (var cert in Certs)
{
<tr>
<td>
<div class="symbol symbol-50px me-2">
<span class="symbol-label">
<i class="bx bx-md bx-receipt text-dark"></i>
</span>
</div>
</td>
<td>
<span class="text-dark fw-bold fs-6">@(cert)</span>
</td>
<td class="text-end">
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn btn-danger"
OnClick="() => DeleteCertificate(cert)">
</WButton>
</td>
</tr>
}
</tbody>
</table>
}
else
{
<div class="alert alert-warning">
<TL>No SSL certificates found</TL>
</div>
}
</div>
</div>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public Website CurrentWebsite { get; set; }
private string[] Certs;
private LazyLoader LazyLoader;
private async Task Load(LazyLoader lazyLoader)
{
await lazyLoader.SetText("Loading certificates");
Certs = await WebsiteService.GetSslCertificates(CurrentWebsite);
}
private async Task CreateCertificate()
{
await WebsiteService.CreateSslCertificate(CurrentWebsite);
await LazyLoader.Reload();
}
private async Task DeleteCertificate(string name)
{
await WebsiteService.DeleteSslCertificate(CurrentWebsite, name);
await LazyLoader.Reload();
}
}

View file

@ -0,0 +1,135 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Models.Plesk.Resources
@using Moonlight.App.Services
@inject SmartTranslateService SmartTranslateService
@inject WebsiteService WebsiteService
<div class="card card-flush h-xl-100">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card-header">
<span class="card-toolbar">
<div class="mt-4">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<div class="input-group">
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Name"))"></InputText>
<InputText @bind-Value="Model.Password" type="password" class="form-control" placeholder="@(SmartTranslateService.Translate("Password"))"></InputText>
<button class="btn btn-primary" type="submit">
<TL>Create</TL>
</button>
</div>
</SmartForm>
</div>
</span>
</div>
<div class="card-body pt-2">
@if (Databases.Any())
{
<div class="accordion" id="databases">
@foreach (var database in Databases)
{
<div class="accordion-item">
<h2 class="accordion-header" id="databases_header_@(database.Id)">
<button class="accordion-button fs-4 fw-semibold" type="button" data-bs-toggle="collapse" data-bs-target="#databases_body_@(database.Id)">
@(database.Name) - @(database.Type.ToUpper())
</button>
</h2>
<div id="databases_body_@(database.Id)" class="accordion-collapse collapse" data-bs-parent="#databases">
<div class="accordion-body">
<div class="mt-7 row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Host</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Host)">
</div>
</div>
<div class="mt-7 row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Port</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(DatabaseServer.Port)">
</div>
</div>
<div class="mt-7 row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Username</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.Name)">
</div>
</div>
<div class="mt-7 row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Database</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.Name)">
</div>
</div>
<div class="text-end">
<DeleteButton Confirm="true" OnClick="() => DeleteDatabase(database)" />
</div>
</div>
</div>
</div>
}
</div>
}
else
{
<div class="alert alert-warning">
<TL>No databases found for this website</TL>
</div>
}
</div>
</LazyLoader>
</div>
@code
{
[CascadingParameter]
public Website CurrentWebsite { get; set; }
private LazyLoader LazyLoader;
private Database[] Databases;
private DatabaseServer DatabaseServer;
private string Host;
private DatabaseDataModel Model = new();
private async Task Load(LazyLoader arg)
{
Databases = await WebsiteService.GetDatabases(CurrentWebsite);
if (Databases.Any())
{
DatabaseServer = (await WebsiteService.GetDefaultDatabaseServer(CurrentWebsite))!;
Host = await WebsiteService.GetHost(CurrentWebsite);
}
}
private async Task OnValidSubmit()
{
await WebsiteService.CreateDatabase(CurrentWebsite, Model.Name, Model.Password);
Model = new();
await LazyLoader.Reload();
}
private async Task DeleteDatabase(Database database)
{
await WebsiteService.DeleteDatabase(CurrentWebsite, database);
await LazyLoader.Reload();
}
}

View file

@ -0,0 +1,24 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Services
@using Moonlight.Shared.Components.FileManagerPartials
@inject WebsiteService WebsiteService
<LazyLoader Load="Load">
<FileManager Access="Access">
</FileManager>
</LazyLoader>
@code
{
[CascadingParameter]
public Website CurrentWebsite { get; set; }
private FileAccess Access;
private async Task Load(LazyLoader arg)
{
Access = await WebsiteService.CreateFileAccess(CurrentWebsite);
}
}

View file

@ -0,0 +1,64 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@inject WebsiteService WebsiteService
<div class="card card-flush h-xl-100">
<div class="card-body pt-2">
<LazyLoader Load="Load">
<div class="mt-7 row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Ftp Host</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(FtpHost)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Ftp Port</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="21">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Ftp Username</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Website.FtpLogin)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Ftp Password</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled blur-unless-hover" disabled="disabled" value="@(Website.FtpPassword)">
</div>
</div>
</LazyLoader>
</div>
</div>
@code
{
[CascadingParameter]
public Website Website { get; set; }
private string FtpHost = "N/A";
private async Task Load(LazyLoader arg)
{
FtpHost = await WebsiteService.GetHost(Website.PleskServer);
}
}

View file

@ -0,0 +1,57 @@
@using Moonlight.App.Database.Entities
<div class="card card-body">
<div class="row">
<div class="col-8">
<div class="d-flex align-items-center">
<div class="symbol symbol-circle me-5">
<div class="symbol-label bg-transparent text-primary border border-secondary border-dashed">
<i class="bx bx-globe bx-md"></i>
</div>
</div>
<div class="d-flex flex-column">
<div class="mb-1 fs-4">@(Website.BaseDomain)</div>
<div class="text-muted fs-5">@(Website.PleskServer.Name)</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="separator my-5"></div>
</div>
<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="/website/@(Website.Id)">
<TL>Dashboard</TL>
</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="/website/@(Website.Id)/files">
<TL>Files</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/website/@(Website.Id)/ftp">
<TL>Ftp</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/website/@(Website.Id)/databases">
<TL>Databases</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; }
[Parameter]
public Website Website { get; set; }
}

View file

@ -1,12 +1,10 @@
@page "/admin/servers/new" @page "/admin/servers/new"
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Services @using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Moonlight.App.Exceptions @using Moonlight.App.Exceptions
@using Moonlight.App.Services.Interop @using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@using Logging.Net @using Logging.Net
@using Blazored.Typeahead @using Blazored.Typeahead

View file

@ -0,0 +1,93 @@
@page "/admin/websites/"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Microsoft.EntityFrameworkCore
@using BlazorTable
@inject SmartTranslateService SmartTranslateService
@inject WebsiteRepository WebsiteRepository
@inject WebsiteService WebsiteService
<OnlyAdmin>
<AdminWebsitesNavigation Index="0"/>
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<TL>Websites</TL>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/websites/new" class="btn btn-sm btn-light-success">
<i class="bx bx-user-plus"></i>
<TL>New website</TL>
</a>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="table-responsive">
<Table TableItem="Website" Items="Websites" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="Website" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="Website" Title="@(SmartTranslateService.Translate("Base domain"))" Field="@(x => x.BaseDomain)" Sortable="true" Filterable="true">
<Template>
<a href="/website/@(context.Id)/">
@(context.BaseDomain)
</a>
</Template>
</Column>
<Column TableItem="Website" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/admin/users/view/@(context.Owner.Id)">
@(context.Owner.Email)
</a>
</Template>
</Column>
<Column TableItem="Website" Title="@(SmartTranslateService.Translate("Plesk server"))" Field="@(x => x.PleskServer.Id)" Sortable="true" Filterable="true">
<Template>
<a href="/admin/websites/servers/edit/@(context.PleskServer.Id)/">
@(context.PleskServer.Name)
</a>
</Template>
</Column>
<Column TableItem="Website" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<DeleteButton Confirm="true" OnClick="() => Delete(context)">
</DeleteButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
</OnlyAdmin>
@code
{
private LazyLoader LazyLoader;
private Website[] Websites;
private Task Load(LazyLoader lazyLoader)
{
Websites = WebsiteRepository
.Get()
.Include(x => x.Owner)
.Include(x => x.PleskServer)
.ToArray();
return Task.CompletedTask;
}
private async Task Delete(Website website)
{
await WebsiteService.Delete(website);
await LazyLoader.Reload();
}
}

View file

@ -0,0 +1,79 @@
@page "/admin/websites/new"
@using Moonlight.App.Models.Forms
@using Moonlight.App.Services
@using Blazored.Typeahead
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@inject WebsiteService WebsiteService
@inject UserRepository UserRepository
@inject NavigationManager NavigationManager
<OnlyAdmin>
<div class="card card-body p-10">
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<label class="form-label">
<TL>Base domain</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.BaseDomain" class="form-control"></InputText>
</div>
<label class="form-label">
<TL>Owner</TL>
</label>
<div class="input-group mb-5">
<BlazoredTypeahead SearchMethod="SearchUsers"
@bind-Value="Model.User">
<SelectedTemplate>
@(context.Email)
</SelectedTemplate>
<ResultTemplate>
@(context.Email)
</ResultTemplate>
</BlazoredTypeahead>
</div>
<div>
<button type="submit" class="btn btn-primary float-end">
<TL>Create</TL>
</button>
</div>
</SmartForm>
</LazyLoader>
</div>
</OnlyAdmin>
@code
{
private WebsiteAdminDataModel Model = new();
private User[] Users;
private async Task OnValidSubmit()
{
await WebsiteService.Create(Model.BaseDomain, Model.User);
NavigationManager.NavigateTo("/admin/websites");
}
private Task Load(LazyLoader arg)
{
Users = UserRepository
.Get()
.ToArray();
return Task.CompletedTask;
}
private Task<IEnumerable<User>> SearchUsers(string input)
{
if (string.IsNullOrEmpty(input))
{
return Task.FromResult(Array.Empty<User>().Cast<User>());
}
else
{
return Task.FromResult(Users.Where(x => x.Email.ToLower().StartsWith(input)));
}
}
}

View file

@ -0,0 +1,98 @@
@page "/admin/websites/servers/edit/{Id:int}"
@using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@inject PleskServerRepository PleskServerRepository
@inject NavigationManager NavigationManager
<OnlyAdmin>
<LazyLoader Load="Load">
@if (PleskServer == null)
{
<div class="d-flex justify-content-center flex-center">
<div class="card">
<img src="/assets/media/svg/nodata.svg" class="card-img-top w-50 mx-auto pt-5" alt="Not found image"/>
<div class="card-body text-center">
<h1 class="card-title">
<TL>Plesk server not found</TL>
</h1>
<p class="card-text fs-4">
<TL>A plesk server with that id cannot be found</TL>
</p>
</div>
</div>
</div>
}
else
{
<div class="card card-body p-10">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<label class="form-label">
<TL>Name</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
</div>
<label class="form-label">
<TL>Api Url</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.ApiUrl" class="form-control"></InputText>
</div>
<label class="form-label">
<TL>Api Key</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.ApiKey" class="blur-unless-hover form-control"></InputText>
</div>
<div>
<button type="submit" class="btn btn-primary float-end">
<TL>Save</TL>
</button>
</div>
</SmartForm>
</div>
}
</LazyLoader>
</OnlyAdmin>
@code
{
[Parameter]
public int Id { get; set; }
private PleskServer? PleskServer;
private PleskServerDataModel Model = new();
private Task OnValidSubmit()
{
PleskServer!.Name = Model.Name;
PleskServer.ApiUrl = Model.ApiUrl;
PleskServer.ApiKey = Model.ApiKey;
PleskServerRepository.Update(PleskServer);
NavigationManager.NavigateTo("/admin/websites/servers");
return Task.CompletedTask;
}
private Task Load(LazyLoader arg)
{
PleskServer = PleskServerRepository
.Get()
.FirstOrDefault(x => x.Id == Id);
if (PleskServer != null)
{
Model.Name = PleskServer.Name;
Model.ApiUrl = PleskServer.ApiUrl;
Model.ApiKey = PleskServer.ApiKey;
}
return Task.CompletedTask;
}
}

View file

@ -0,0 +1,115 @@
@page "/admin/websites/servers"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using BlazorTable
@inject SmartTranslateService SmartTranslateService
@inject PleskServerRepository PleskServerRepository
@inject WebsiteService WebsiteService
<OnlyAdmin>
<AdminWebsitesNavigation Index="1"/>
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1">
<TL>Plesk servers</TL>
</span>
</h3>
<div class="card-toolbar">
<a href="/admin/websites/servers/new" class="btn btn-sm btn-light-success">
<i class="bx bx-user-plus"></i>
<TL>New plesk server</TL>
</a>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="table-responsive">
<Table TableItem="PleskServer" Items="PleskServers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Api url"))" Field="@(x => x.ApiUrl)" Sortable="true" Filterable="true"/>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
@if (OnlineCache.ContainsKey(context))
{
if (OnlineCache[context])
{
<span class="text-success">
<TL>Online</TL>
</span>
}
else
{
<span class="text-danger">
<TL>Offline</TL>
</span>
}
}
else
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
</Template>
</Column>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Edit"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/admin/websites/servers/edit/@(context.Id)/">
<TL>Manage</TL>
</a>
</Template>
</Column>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<DeleteButton Confirm="true" OnClick="() => OnClick(context)">
</DeleteButton>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
</OnlyAdmin>
@code
{
private PleskServer[] PleskServers;
private LazyLoader LazyLoader;
private Dictionary<PleskServer, bool> OnlineCache = new();
private Task Load(LazyLoader arg)
{
PleskServers = PleskServerRepository
.Get()
.ToArray();
Task.Run(async () =>
{
foreach (var pleskServer in PleskServers)
{
OnlineCache.Add(pleskServer, await WebsiteService.IsHostUp(pleskServer));
await InvokeAsync(StateHasChanged);
}
});
return Task.CompletedTask;
}
private async Task OnClick(PleskServer pleskServer)
{
PleskServerRepository.Delete(pleskServer);
await LazyLoader.Reload();
}
}

View file

@ -0,0 +1,55 @@
@page "/admin/websites/servers/new"
@using Moonlight.App.Repositories
@using Moonlight.App.Models.Forms
@inject PleskServerRepository PleskServerRepository
@inject NavigationManager NavigationManager
<OnlyAdmin>
<div class="card card-body p-10">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<label class="form-label">
<TL>Name</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
</div>
<label class="form-label">
<TL>Api Url</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.ApiUrl" class="form-control"></InputText>
</div>
<label class="form-label">
<TL>Api Key</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.ApiKey" class="form-control"></InputText>
</div>
<div>
<button type="submit" class="btn btn-primary float-end">
<TL>Create</TL>
</button>
</div>
</SmartForm>
</div>
</OnlyAdmin>
@code
{
private PleskServerDataModel Model = new();
private Task OnValidSubmit()
{
PleskServerRepository.Add(new()
{
Name = Model.Name,
ApiUrl = Model.ApiUrl,
ApiKey = Model.ApiKey
});
NavigationManager.NavigateTo("/admin/websites/servers");
return Task.CompletedTask;
}
}

View file

@ -17,7 +17,7 @@
@inject ServerRepository ServerRepository @inject ServerRepository ServerRepository
@inject WingsConsoleHelper WingsConsoleHelper @inject WingsConsoleHelper WingsConsoleHelper
@inject MessageService MessageService @inject MessageService MessageService
@inject NodeService NodeService @inject ServerService ServerService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@implements IDisposable @implements IDisposable
@ -227,31 +227,23 @@
.Include(x => x.MainAllocation) .Include(x => x.MainAllocation)
.Include(x => x.Owner) .Include(x => x.Owner)
.First(x => x.Uuid == uuid); .First(x => x.Uuid == uuid);
if (CurrentServer.Owner.Id != User!.Id && !User.Admin)
CurrentServer = null;
} }
catch (Exception) catch (Exception)
{ {
// ignored // ignored
} }
if (CurrentServer != null)
{
if (CurrentServer.Owner.Id != User!.Id && !User.Admin)
CurrentServer = null;
}
if (CurrentServer != null) if (CurrentServer != null)
{ {
await lazyLoader.SetText("Checking node online status"); await lazyLoader.SetText("Checking node online status");
try NodeOnline = await ServerService.IsHostUp(CurrentServer);
{
//TODO: Implement status caching
var data = await NodeService.GetStatus(CurrentServer.Node);
if (data != null)
NodeOnline = true;
}
catch (Exception)
{
// ignored
}
if (NodeOnline) if (NodeOnline)
{ {

View file

@ -0,0 +1,132 @@
@page "/website/{Id:int}/{Route?}"
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Moonlight.App.Services
@using Moonlight.Shared.Components.WebsiteControl
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services.Interop
@inject WebsiteRepository WebsiteRepository
@inject WebsiteService WebsiteService
@inject ToastService ToastService
<LazyLoader Load="Load">
@if (CurrentWebsite == null)
{
<div class="d-flex justify-content-center flex-center">
<div class="card">
<img src="/assets/media/svg/nodata.svg" class="card-img-top w-50 mx-auto pt-5" alt="Not found image"/>
<div class="card-body text-center">
<h1 class="card-title">
<TL>Website not found</TL>
</h1>
<p class="card-text fs-4">
<TL>A website with that id cannot be found or you have no access for this server</TL>
</p>
</div>
</div>
</div>
}
else
{
if (HostOnline)
{
<CascadingValue Value="CurrentWebsite">
@{
var index = 0;
switch (Route)
{
case "files":
index = 1;
break;
case "ftp":
index = 2;
break;
case "databases":
index = 3;
break;
default:
index = 0;
break;
}
<WebsiteNavigation Index="index" Website="CurrentWebsite" />
@switch (Route)
{
case "files":
<WebsiteFiles />
break;
case "ftp":
<WebsiteFtp />
break;
case "databases":
<WebsiteDatabases />
break;
default:
<WebsiteDashboard />
break;
}
}
</CascadingValue>
}
else
{
<div class="d-flex justify-content-center flex-center">
<div class="card">
<img src="/assets/media/svg/serverdown.svg" class="card-img-top w-50 mx-auto pt-5" alt="Not found image"/>
<div class="card-body text-center">
<h1 class="card-title">
<TL>Host system offline</TL>
</h1>
<p class="card-text fs-4">
<TL>The host system the website is running on is currently offline</TL>
</p>
</div>
</div>
</div>
}
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
[Parameter]
public string? Route { get; set; }
[CascadingParameter]
public User User { get; set; }
private Website? CurrentWebsite;
private bool HostOnline = false;
private async Task Load(LazyLoader lazyLoader)
{
CurrentWebsite = WebsiteRepository
.Get()
.Include(x => x.PleskServer)
.Include(x => x.Owner)
.FirstOrDefault(x => x.Id == Id);
if (CurrentWebsite != null)
{
if (CurrentWebsite.Owner.Id != User!.Id && !User.Admin)
CurrentWebsite = null;
}
if (CurrentWebsite != null)
{
await lazyLoader.SetText("Checking host system online status");
HostOnline = await WebsiteService.IsHostUp(CurrentWebsite);
if (HostOnline)
{
}
}
}
}

View file

@ -0,0 +1 @@
@page "/websites"

View file

@ -0,0 +1 @@
@page "/websites/new"

View file

@ -487,3 +487,28 @@ Decompress;Decompress
Moving;Moving Moving;Moving
Compressing;Compressing Compressing;Compressing
selected;selected selected;selected
New website;New website
Plesk servers;Plesk servers
Base domain;Base domain
Plesk server;Plesk server
Ftp;Ftp
No SSL certificate found;No SSL certificate found
Ftp Host;Ftp Host
Ftp Port;Ftp Port
Ftp Username;Ftp Username
Ftp Password;Ftp Password
Use;Use
SSL Certificates;SSL Certificates
SSL certificates;SSL certificates
Issue certificate;Issue certificate
New plesk server;New plesk server
Api url;Api url
Host system offline;Host system offline
The host system the website is running on is currently offline;The host system the website is running on is currently offline
No SSL certificates found;No SSL certificates found
No databases found for this website;No databases found for this website
The name should be at least 8 characters long;The name should be at least 8 characters long
The name should only contain of lower case characters and numbers;The name should only contain of lower case characters and numbers
Error from plesk;Error from plesk
Host;Host
Username;Username

View file

@ -0,0 +1,21 @@
Open support;Open support
About us;About us
Imprint;Imprint
Privacy;Privacy
Create;Create
Server;Server
Domain;Domain
Website;Website
Login;Login
Register;Register
Email;Email
Password;Password
Sign In;Sign In
Sign in to start with moonlight;Sign in to start with moonlight
Sign in with Discord;Sign in with Discord
Sign in with Google;Sign in with Google
Or with email;Or with email
Forgot password?;Forgot password?
Sign-in;Sign-in
Not registered yet?;Not registered yet?
Sign up;Sign up