Revert "Merge pull request #106 from Moonlight-Panel/DiscordBot"

This reverts commit f71fcc0f5d, reversing
changes made to e0bea9b61c.
This commit is contained in:
Marcel Baumgartner
2023-04-29 23:37:03 +02:00
parent f71fcc0f5d
commit ca64184faf
152 changed files with 9159 additions and 3251 deletions

View File

@@ -0,0 +1,83 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Plesk.Resources;
using Newtonsoft.Json;
using RestSharp;
namespace Moonlight.App.ApiClients.CloudPanel;
public class CloudPanelApiHelper
{
private readonly RestClient Client;
public CloudPanelApiHelper()
{
Client = new();
}
public async Task Post(Database.Entities.CloudPanel cloudPanel, string resource, object? body)
{
var request = CreateRequest(cloudPanel, 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 CloudPanelException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
public async Task Delete(Database.Entities.CloudPanel cloudPanel, string resource, object? body)
{
var request = CreateRequest(cloudPanel, 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 CloudPanelException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
private RestRequest CreateRequest(Database.Entities.CloudPanel cloudPanel, string resource)
{
var url = $"{cloudPanel.ApiUrl}/" + resource;
var request = new RestRequest(url);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization", "Bearer " + cloudPanel.ApiKey);
return request;
}
}

View File

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

View File

@@ -0,0 +1,18 @@
using Newtonsoft.Json;
namespace Moonlight.App.ApiClients.CloudPanel.Requests;
public class AddDatabase
{
[JsonProperty("domainName")]
public string DomainName { get; set; }
[JsonProperty("databaseName")]
public string DatabaseName { get; set; }
[JsonProperty("databaseUserName")]
public string DatabaseUserName { get; set; }
[JsonProperty("databaseUserPassword")]
public string DatabaseUserPassword { get; set; }
}

View File

@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace Moonlight.App.ApiClients.CloudPanel.Requests;
public class AddPhpSite
{
[JsonProperty("domainName")] public string DomainName { get; set; } = "";
[JsonProperty("siteUser")] public string SiteUser { get; set; } = "";
[JsonProperty("siteUserPassword")] public string SiteUserPassword { get; set; } = "";
[JsonProperty("vHostTemplate")] public string VHostTemplate { get; set; } = "";
[JsonProperty("phpVersion")] public string PhpVersion { get; set; } = "";
}

View File

@@ -0,0 +1,9 @@
using Newtonsoft.Json;
namespace Moonlight.App.ApiClients.CloudPanel.Requests;
public class InstallLetsEncrypt
{
[JsonProperty("domainName")]
public string DomainName { get; set; }
}

View File

@@ -2,6 +2,7 @@
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Database.Entities.LogsEntries; using Moonlight.App.Database.Entities.LogsEntries;
using Moonlight.App.Database.Entities.Notification; using Moonlight.App.Database.Entities.Notification;
using Moonlight.App.Database.Interceptors;
using Moonlight.App.Models.Misc; using Moonlight.App.Models.Misc;
using Moonlight.App.Services; using Moonlight.App.Services;
@@ -29,8 +30,7 @@ public class DataContext : DbContext
public DbSet<AuditLogEntry> AuditLog { get; set; } public DbSet<AuditLogEntry> AuditLog { get; set; }
public DbSet<ErrorLogEntry> ErrorLog { get; set; } public DbSet<ErrorLogEntry> ErrorLog { get; set; }
public DbSet<SecurityLogEntry> SecurityLog { get; set; } public DbSet<SecurityLogEntry> SecurityLog { get; set; }
public DbSet<SupportMessage> SupportMessages { get; set; }
public DbSet<SharedDomain> SharedDomains { get; set; } public DbSet<SharedDomain> SharedDomains { get; set; }
public DbSet<Domain> Domains { get; set; } public DbSet<Domain> Domains { get; set; }
public DbSet<Revoke> Revokes { get; set; } public DbSet<Revoke> Revokes { get; set; }
@@ -38,10 +38,13 @@ 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; }
public DbSet<StatisticsData> Statistics { get; set; } public DbSet<StatisticsData> Statistics { get; set; }
public DbSet<NewsEntry> NewsEntries { get; set; } public DbSet<NewsEntry> NewsEntries { get; set; }
public DbSet<CloudPanel> CloudPanels { get; set; }
public DbSet<MySqlDatabase> Databases { get; set; }
public DbSet<WebSpace> WebSpaces { get; set; }
public DbSet<SupportChatMessage> SupportChatMessages { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
@@ -65,6 +68,9 @@ public class DataContext : DbContext
builder.EnableRetryOnFailure(5); builder.EnableRetryOnFailure(5);
} }
); );
if(ConfigService.SqlDebugMode)
optionsBuilder.AddInterceptors(new SqlLoggingInterceptor());
} }
} }
} }

View File

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

View File

@@ -1,7 +1,10 @@
namespace Moonlight.App.Database.Entities; using Newtonsoft.Json;
namespace Moonlight.App.Database.Entities;
public class DockerImage public class DockerImage
{ {
[JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
public bool Default { get; set; } = false; public bool Default { get; set; } = false;
public string Name { get; set; } = ""; public string Name { get; set; } = "";

View File

@@ -1,7 +1,10 @@
namespace Moonlight.App.Database.Entities; using Newtonsoft.Json;
namespace Moonlight.App.Database.Entities;
public class Image public class Image
{ {
[JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
public Guid Uuid { get; set; } public Guid Uuid { get; set; }
public string Name { get; set; } = ""; public string Name { get; set; } = "";

View File

@@ -1,7 +1,10 @@
namespace Moonlight.App.Database.Entities; using Newtonsoft.Json;
namespace Moonlight.App.Database.Entities;
public class ImageVariable public class ImageVariable
{ {
[JsonIgnore]
public int Id { get; set; } public int Id { get; set; }
public string Key { get; set; } = ""; public string Key { get; set; } = "";
public string DefaultValue { get; set; } = ""; public string DefaultValue { get; set; } = "";

View File

@@ -0,0 +1,9 @@
namespace Moonlight.App.Database.Entities;
public class MySqlDatabase
{
public int Id { get; set; }
public WebSpace WebSpace { get; set; }
public string UserName { get; set; } = "";
public string Password { get; set; } = "";
}

View File

@@ -0,0 +1,21 @@
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Database.Entities;
public class SupportChatMessage
{
public int Id { get; set; }
public string Content { get; set; } = "";
public string Attachment { get; set; } = "";
public User? Sender { get; set; }
public User Recipient { get; set; }
public bool IsQuestion { get; set; } = false;
public QuestionType QuestionType { get; set; }
public string Answer { get; set; } = "";
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}

View File

@@ -1,17 +0,0 @@
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Database.Entities;
public class SupportMessage
{
public int Id { get; set; }
public string Message { get; set; } = "";
public User? Sender { get; set; } = null;
public User? Recipient { get; set; } = null;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public bool IsQuestion { get; set; } = false;
public QuestionType Type { get; set; }
public string Answer { get; set; } = "";
public bool IsSystem { get; set; } = false;
public bool IsSupport { get; set; } = false;
}

View File

@@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations; using Moonlight.App.Models.Misc;
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Database.Entities; namespace Moonlight.App.Database.Entities;
@@ -34,7 +33,7 @@ public class User
// Security // Security
public bool TotpEnabled { get; set; } = false; public bool TotpEnabled { get; set; } = false;
public string TotpSecret { get; set; } = ""; public string TotpSecret { get; set; } = "";
public DateTime TokenValidTime { get; set; } = DateTime.Now; public DateTime TokenValidTime { get; set; } = DateTime.UtcNow;
// Discord // Discord
public ulong DiscordId { get; set; } public ulong DiscordId { get; set; }

View File

@@ -0,0 +1,13 @@
namespace Moonlight.App.Database.Entities;
public class WebSpace
{
public int Id { get; set; }
public string Domain { get; set; } = "";
public string UserName { get; set; } = "";
public string Password { get; set; } = "";
public string VHostTemplate { get; set; } = "";
public User Owner { get; set; }
public List<MySqlDatabase> Databases { get; set; } = new();
public CloudPanel CloudPanel { get; set; }
}

View File

@@ -1,12 +0,0 @@
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,40 @@
using System.Data.Common;
using Logging.Net;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace Moonlight.App.Database.Interceptors;
public class SqlLoggingInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
LogSql(command.CommandText);
return base.ReaderExecuting(command, eventData, result);
}
public override InterceptionResult<object> ScalarExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<object> result)
{
LogSql(command.CommandText);
return base.ScalarExecuting(command, eventData, result);
}
public override InterceptionResult<int> NonQueryExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<int> result)
{
LogSql(command.CommandText);
return base.NonQueryExecuting(command, eventData, result);
}
private void LogSql(string sql)
{
Logger.Info($"[SQL DEBUG] {sql.Replace("\n", "")}");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedCloudPanelModels : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "CloudPanels",
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_CloudPanels", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "WebSpaces",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Domain = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
UserName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Password = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
VHostTemplate = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
OwnerId = table.Column<int>(type: "int", nullable: false),
CloudPanelId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_WebSpaces", x => x.Id);
table.ForeignKey(
name: "FK_WebSpaces_CloudPanels_CloudPanelId",
column: x => x.CloudPanelId,
principalTable: "CloudPanels",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_WebSpaces_Users_OwnerId",
column: x => x.OwnerId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "Databases",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
WebSpaceId = table.Column<int>(type: "int", nullable: false),
UserName = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Password = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_Databases", x => x.Id);
table.ForeignKey(
name: "FK_Databases_WebSpaces_WebSpaceId",
column: x => x.WebSpaceId,
principalTable: "WebSpaces",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Databases_WebSpaceId",
table: "Databases",
column: "WebSpaceId");
migrationBuilder.CreateIndex(
name: "IX_WebSpaces_CloudPanelId",
table: "WebSpaces",
column: "CloudPanelId");
migrationBuilder.CreateIndex(
name: "IX_WebSpaces_OwnerId",
table: "WebSpaces",
column: "OwnerId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Databases");
migrationBuilder.DropTable(
name: "WebSpaces");
migrationBuilder.DropTable(
name: "CloudPanels");
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddedNewSupportChatModels : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Websites");
migrationBuilder.DropTable(
name: "PleskServers");
migrationBuilder.CreateTable(
name: "SupportChatMessages",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Content = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Attachment = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
SenderId = table.Column<int>(type: "int", nullable: true),
RecipientId = table.Column<int>(type: "int", nullable: false),
IsQuestion = table.Column<bool>(type: "tinyint(1)", nullable: false),
QuestionType = table.Column<int>(type: "int", nullable: false),
Answer = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SupportChatMessages", x => x.Id);
table.ForeignKey(
name: "FK_SupportChatMessages_Users_RecipientId",
column: x => x.RecipientId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_SupportChatMessages_Users_SenderId",
column: x => x.SenderId,
principalTable: "Users",
principalColumn: "Id");
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_SupportChatMessages_RecipientId",
table: "SupportChatMessages",
column: "RecipientId");
migrationBuilder.CreateIndex(
name: "IX_SupportChatMessages_SenderId",
table: "SupportChatMessages",
column: "SenderId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SupportChatMessages");
migrationBuilder.CreateTable(
name: "PleskServers",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
ApiKey = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ApiUrl = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Name = 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),
OwnerId = table.Column<int>(type: "int", nullable: false),
PleskServerId = table.Column<int>(type: "int", nullable: false),
BaseDomain = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
FtpLogin = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
FtpPassword = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
PleskId = 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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class RemovedOldSupportChatModel : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SupportMessages");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SupportMessages",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
RecipientId = table.Column<int>(type: "int", nullable: true),
SenderId = table.Column<int>(type: "int", nullable: true),
Answer = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
IsQuestion = table.Column<bool>(type: "tinyint(1)", nullable: false),
IsSupport = table.Column<bool>(type: "tinyint(1)", nullable: false),
IsSystem = table.Column<bool>(type: "tinyint(1)", nullable: false),
Message = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Type = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SupportMessages", x => x.Id);
table.ForeignKey(
name: "FK_SupportMessages_Users_RecipientId",
column: x => x.RecipientId,
principalTable: "Users",
principalColumn: "Id");
table.ForeignKey(
name: "FK_SupportMessages_Users_SenderId",
column: x => x.SenderId,
principalTable: "Users",
principalColumn: "Id");
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_SupportMessages_RecipientId",
table: "SupportMessages",
column: "RecipientId");
migrationBuilder.CreateIndex(
name: "IX_SupportMessages_SenderId",
table: "SupportMessages",
column: "SenderId");
}
}
}

View File

@@ -19,6 +19,33 @@ namespace Moonlight.App.Database.Migrations
.HasAnnotation("ProductVersion", "7.0.3") .HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", 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>("Host")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("CloudPanels");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -281,6 +308,30 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("SecurityLog"); b.ToTable("SecurityLog");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("WebSpaceId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("WebSpaceId");
b.ToTable("Databases");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -402,29 +453,6 @@ 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")
@@ -622,7 +650,7 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("Subscriptions"); b.ToTable("Subscriptions");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b => modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@@ -632,30 +660,31 @@ namespace Moonlight.App.Database.Migrations
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("Attachment")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTime>("CreatedAt") b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<bool>("IsQuestion") b.Property<bool>("IsQuestion")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<bool>("IsSupport") b.Property<int>("QuestionType")
.HasColumnType("tinyint(1)"); .HasColumnType("int");
b.Property<bool>("IsSystem") b.Property<int>("RecipientId")
.HasColumnType("tinyint(1)");
b.Property<string>("Message")
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("RecipientId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int?>("SenderId") b.Property<int?>("SenderId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("Type") b.Property<DateTime>("UpdatedAt")
.HasColumnType("int"); .HasColumnType("datetime(6)");
b.HasKey("Id"); b.HasKey("Id");
@@ -663,7 +692,7 @@ namespace Moonlight.App.Database.Migrations
b.HasIndex("SenderId"); b.HasIndex("SenderId");
b.ToTable("SupportMessages"); b.ToTable("SupportChatMessages");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
@@ -748,40 +777,41 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("Users"); b.ToTable("Users");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b => modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("BaseDomain") b.Property<int>("CloudPanelId")
.IsRequired() .HasColumnType("int");
.HasColumnType("longtext");
b.Property<string>("FtpLogin") b.Property<string>("Domain")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("FtpPassword")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<int>("OwnerId") b.Property<int>("OwnerId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("PleskId") b.Property<string>("Password")
.HasColumnType("int"); .IsRequired()
.HasColumnType("longtext");
b.Property<int>("PleskServerId") b.Property<string>("UserName")
.HasColumnType("int"); .IsRequired()
.HasColumnType("longtext");
b.Property<string>("VHostTemplate")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("CloudPanelId");
b.HasIndex("OwnerId"); b.HasIndex("OwnerId");
b.HasIndex("PleskServerId"); b.ToTable("WebSpaces");
b.ToTable("Websites");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
@@ -828,6 +858,17 @@ namespace Moonlight.App.Database.Migrations
.HasForeignKey("ImageId"); .HasForeignKey("ImageId");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
{
b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace")
.WithMany("Databases")
.HasForeignKey("WebSpaceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("WebSpace");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
{ {
b.HasOne("Moonlight.App.Database.Entities.Node", null) b.HasOne("Moonlight.App.Database.Entities.Node", null)
@@ -908,11 +949,13 @@ namespace Moonlight.App.Database.Migrations
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b => modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b =>
{ {
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
.WithMany() .WithMany()
.HasForeignKey("RecipientId"); .HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.User", "Sender") b.HasOne("Moonlight.App.Database.Entities.User", "Sender")
.WithMany() .WithMany()
@@ -932,23 +975,23 @@ namespace Moonlight.App.Database.Migrations
b.Navigation("CurrentSubscription"); b.Navigation("CurrentSubscription");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b => modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
{ {
b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel")
.WithMany()
.HasForeignKey("CloudPanelId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Moonlight.App.Database.Entities.User", "Owner") b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
.WithMany() .WithMany()
.HasForeignKey("OwnerId") .HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Moonlight.App.Database.Entities.PleskServer", "PleskServer") b.Navigation("CloudPanel");
.WithMany()
.HasForeignKey("PleskServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Owner"); b.Navigation("Owner");
b.Navigation("PleskServer");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
@@ -971,6 +1014,11 @@ namespace Moonlight.App.Database.Migrations
b.Navigation("Variables"); b.Navigation("Variables");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
{
b.Navigation("Databases");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@@ -0,0 +1,142 @@
using System.Diagnostics;
using Logging.Net;
namespace Moonlight.App.Events;
public class EventSystem
{
private Dictionary<int, object> Storage = new();
private List<Subscriber> Subscribers = new();
private readonly bool Debug = false;
private readonly bool DisableWarning = false;
private readonly TimeSpan TookToLongTime = TimeSpan.FromSeconds(1);
public Task On<T>(string id, object handle, Func<T, Task> action)
{
if(Debug)
Logger.Debug($"{handle} subscribed to '{id}'");
lock (Subscribers)
{
if (!Subscribers.Any(x => x.Id == id && x.Handle == handle))
{
Subscribers.Add(new ()
{
Action = action,
Handle = handle,
Id = id
});
}
}
return Task.CompletedTask;
}
public Task Emit(string id, object? d = null)
{
int hashCode = -1;
if (d != null)
{
hashCode = d.GetHashCode();
Storage.TryAdd(hashCode, d);
}
Subscriber[] subscribers;
lock (Subscribers)
{
subscribers = Subscribers
.Where(x => x.Id == id)
.ToArray();
}
var tasks = new List<Task>();
foreach (var subscriber in subscribers)
{
tasks.Add(new Task(() =>
{
int storageId = hashCode + 0; // To create a copy of the hash code
object? data = null;
if (storageId != -1)
{
if (Storage.TryGetValue(storageId, out var value))
{
data = value;
}
else
{
Logger.Warn($"Object with the hash '{storageId}' was not present in the storage");
return;
}
}
var stopWatch = new Stopwatch();
stopWatch.Start();
var del = (Delegate)subscriber.Action;
try
{
((Task)del.DynamicInvoke(data)!).Wait();
}
catch (Exception e)
{
Logger.Warn($"Error emitting '{subscriber.Id} on {subscriber.Handle}'");
Logger.Warn(e);
}
stopWatch.Stop();
if (!DisableWarning)
{
if (stopWatch.Elapsed.TotalMilliseconds > TookToLongTime.TotalMilliseconds)
{
Logger.Warn($"Subscriber {subscriber.Handle} for event '{subscriber.Id}' took long to process. {stopWatch.Elapsed.TotalMilliseconds}ms");
}
}
if (Debug)
{
Logger.Debug($"Subscriber {subscriber.Handle} for event '{subscriber.Id}' took {stopWatch.Elapsed.TotalMilliseconds}ms");
}
}));
}
foreach (var task in tasks)
{
task.Start();
}
Task.Run(() =>
{
Task.WaitAll(tasks.ToArray());
Storage.Remove(hashCode);
if(Debug)
Logger.Debug($"Completed all event tasks for '{id}' and removed object from storage");
});
if(Debug)
Logger.Debug($"Completed event emit '{id}'");
return Task.CompletedTask;
}
public Task Off(string id, object handle)
{
if(Debug)
Logger.Debug($"{handle} unsubscribed to '{id}'");
lock (Subscribers)
{
Subscribers.RemoveAll(x => x.Id == id && x.Handle == handle);
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,8 @@
namespace Moonlight.App.Events;
public class Subscriber
{
public string Id { get; set; }
public object Action { get; set; }
public object Handle { get; set; }
}

View File

@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
[Serializable] [Serializable]
public class DaemonException : Exception public class DaemonException : Exception
{ {
public int StatusCode { private get; set; } public int StatusCode { get; set; }
public DaemonException() public DaemonException()
{ {

View File

@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
[Serializable] [Serializable]
public class PleskException : Exception public class PleskException : Exception
{ {
public int StatusCode { private get; set; } public int StatusCode { get; set; }
public PleskException() public PleskException()
{ {

View File

@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
[Serializable] [Serializable]
public class WingsException : Exception public class WingsException : Exception
{ {
public int StatusCode { private get; set; } public int StatusCode { get; set; }
public WingsException() public WingsException()
{ {

View File

@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
namespace Moonlight.App.Extensions;
public static class DbSetExtensions
{
public static T Random<T>(this DbSet<T> repo) where T : class
{
Random rand = new Random();
int toSkip = rand.Next(0, repo.Count());
return repo.Skip(toSkip).Take(1).First();
}
}

View File

@@ -0,0 +1,206 @@
using Logging.Net;
using Renci.SshNet;
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
namespace Moonlight.App.Helpers.Files;
public class SftpFileAccess : FileAccess
{
private readonly string SftpHost;
private readonly string SftpUser;
private readonly string SftpPassword;
private readonly int SftpPort;
private readonly bool ForceUserDir;
private readonly SftpClient Client;
private string InternalPath
{
get
{
if (ForceUserDir)
return $"/home/{SftpUser}{CurrentPath}";
return InternalPath;
}
}
public SftpFileAccess(string sftpHost, string sftpUser, string sftpPassword, int sftpPort,
bool forceUserDir = false)
{
SftpHost = sftpHost;
SftpUser = sftpUser;
SftpPassword = sftpPassword;
SftpPort = sftpPort;
ForceUserDir = forceUserDir;
Client = new(
new ConnectionInfo(
SftpHost,
SftpPort,
SftpUser,
new PasswordAuthenticationMethod(
SftpUser,
SftpPassword
)
)
);
}
private void EnsureConnect()
{
if (!Client.IsConnected)
Client.Connect();
}
public override Task<FileData[]> Ls()
{
EnsureConnect();
var x = new List<FileData>();
foreach (var file in Client.ListDirectory(InternalPath))
{
if (file.Name != "." && file.Name != "..")
{
x.Add(new()
{
Name = file.Name,
Size = file.Attributes.Size,
IsFile = !file.IsDirectory
});
}
}
return Task.FromResult(x.ToArray());
}
public override Task Cd(string dir)
{
var x = Path.Combine(CurrentPath, dir).Replace("\\", "/") + "/";
x = x.Replace("//", "/");
CurrentPath = x;
return Task.CompletedTask;
}
public override Task Up()
{
CurrentPath = Path.GetFullPath(Path.Combine(CurrentPath, "..")).Replace("\\", "/").Replace("C:", "");
return Task.CompletedTask;
}
public override Task SetDir(string dir)
{
CurrentPath = dir;
return Task.CompletedTask;
}
public override Task<string> Read(FileData fileData)
{
EnsureConnect();
var textStream = Client.Open(InternalPath.TrimEnd('/') + "/" + fileData.Name, FileMode.Open);
if (textStream == null)
return Task.FromResult("");
var streamReader = new StreamReader(textStream);
var text = streamReader.ReadToEnd();
streamReader.Close();
textStream.Close();
return Task.FromResult(text);
}
public override Task Write(FileData fileData, string content)
{
EnsureConnect();
var textStream = Client.Open(InternalPath.TrimEnd('/') + "/" + fileData.Name, FileMode.Create);
var streamWriter = new StreamWriter(textStream);
streamWriter.Write(content);
streamWriter.Flush();
textStream.Flush();
streamWriter.Close();
textStream.Close();
return Task.CompletedTask;
}
public override async Task Upload(string name, Stream stream, Action<int>? progressUpdated = null)
{
var dataStream = new SyncStreamAdapter(stream);
await Task.Factory.FromAsync((x, _) => Client.BeginUploadFile(dataStream, InternalPath + name, x, null, u =>
{
progressUpdated?.Invoke((int)((long)u / stream.Length));
}),
Client.EndUploadFile, null);
}
public override Task MkDir(string name)
{
Client.CreateDirectory(InternalPath + name);
return Task.CompletedTask;
}
public override Task<string> Pwd()
{
return Task.FromResult(CurrentPath);
}
public override Task<string> DownloadUrl(FileData fileData)
{
throw new NotImplementedException();
}
public override Task<Stream> DownloadStream(FileData fileData)
{
var stream = new MemoryStream(100 * 1024 * 1024);
Client.DownloadFile(InternalPath + fileData.Name, stream);
return Task.FromResult<Stream>(stream);
}
public override Task Delete(FileData fileData)
{
Client.Delete(InternalPath + fileData.Name);
return Task.CompletedTask;
}
public override Task Move(FileData fileData, string newPath)
{
Client.RenameFile(InternalPath + fileData.Name, InternalPath + newPath);
return Task.CompletedTask;
}
public override Task Compress(params FileData[] files)
{
throw new NotImplementedException();
}
public override Task Decompress(FileData fileData)
{
throw new NotImplementedException();
}
public override Task<string> GetLaunchUrl()
{
return Task.FromResult($"sftp://{SftpUser}@{SftpHost}:{SftpPort}");
}
public override object Clone()
{
return new SftpFileAccess(SftpHost, SftpUser, SftpPassword, SftpPort, ForceUserDir);
}
}

View File

@@ -1,15 +1,23 @@
namespace Moonlight.App.Helpers; using Logging.Net;
namespace Moonlight.App.Helpers;
public static class ParseHelper public static class ParseHelper
{ {
public static int MinecraftToInt(string raw) public static int MinecraftToInt(string raw)
{ {
var versionWithoutPre = raw.Split("-")[0]; var versionWithoutPre = raw.Split("_")[0];
versionWithoutPre = versionWithoutPre.Split("-")[0];
// Fuck you 1.7.10 ;)
versionWithoutPre = versionWithoutPre.Replace("1.7.10", "1.7");
if (versionWithoutPre.Count(x => x == "."[0]) == 1) if (versionWithoutPre.Count(x => x == "."[0]) == 1)
versionWithoutPre += ".0"; versionWithoutPre += ".0";
return int.Parse(versionWithoutPre.Replace(".", "")); var x = versionWithoutPre.Replace(".", "");
return int.Parse(x);
} }
public static string FirstPartStartingWithNumber(string raw) public static string FirstPartStartingWithNumber(string raw)
@@ -29,4 +37,61 @@ public static class ParseHelper
return res; return res;
} }
public static string GetHighestVersion(string[] versions)
{
// Initialize the highest version to the first version in the array
string highestVersion = versions[0];
// Loop through the remaining versions in the array
for (int i = 1; i < versions.Length; i++)
{
// Compare the current version to the highest version
if (CompareVersions(versions[i], highestVersion) > 0)
{
// If the current version is higher, update the highest version
highestVersion = versions[i];
}
}
return highestVersion;
}
public static int CompareVersions(string version1, string version2)
{
// Split the versions into their component parts
string[] version1Parts = version1.Split('.');
string[] version2Parts = version2.Split('.');
// Compare each component part in turn
for (int i = 0; i < version1Parts.Length && i < version2Parts.Length; i++)
{
int part1 = int.Parse(version1Parts[i]);
int part2 = int.Parse(version2Parts[i]);
if (part1 < part2)
{
return -1;
}
else if (part1 > part2)
{
return 1;
}
}
// If we get here, the versions are equal up to the length of the shorter one.
// If one version has more parts than the other, the longer one is considered higher.
if (version1Parts.Length < version2Parts.Length)
{
return -1;
}
else if (version1Parts.Length > version2Parts.Length)
{
return 1;
}
else
{
return 0;
}
}
} }

View File

@@ -1,220 +0,0 @@
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,58 @@
namespace Moonlight.App.Helpers;
public class SyncStreamAdapter : Stream
{
private readonly Stream _stream;
public SyncStreamAdapter(Stream stream)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
}
public override bool CanRead => _stream.CanRead;
public override bool CanSeek => _stream.CanSeek;
public override bool CanWrite => _stream.CanWrite;
public override long Length => _stream.Length;
public override long Position
{
get => _stream.Position;
set => _stream.Position = value;
}
public override void Flush()
{
_stream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
var task = Task.Run(() => _stream.ReadAsync(buffer, offset, count));
return task.GetAwaiter().GetResult();
}
public override long Seek(long offset, SeekOrigin origin)
{
return _stream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_stream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
var task = Task.Run(() => _stream.WriteAsync(buffer, offset, count));
task.GetAwaiter().GetResult();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_stream?.Dispose();
}
base.Dispose(disposing);
}
}

View File

@@ -18,17 +18,19 @@ public class OAuth2Controller : Controller
private readonly DiscordOAuth2Service DiscordOAuth2Service; private readonly DiscordOAuth2Service DiscordOAuth2Service;
private readonly UserRepository UserRepository; private readonly UserRepository UserRepository;
private readonly UserService UserService; private readonly UserService UserService;
private readonly DateTimeService DateTimeService;
public OAuth2Controller( public OAuth2Controller(
GoogleOAuth2Service googleOAuth2Service, GoogleOAuth2Service googleOAuth2Service,
UserRepository userRepository, UserRepository userRepository,
UserService userService, UserService userService,
DiscordOAuth2Service discordOAuth2Service) DiscordOAuth2Service discordOAuth2Service, DateTimeService dateTimeService)
{ {
GoogleOAuth2Service = googleOAuth2Service; GoogleOAuth2Service = googleOAuth2Service;
UserRepository = userRepository; UserRepository = userRepository;
UserService = userService; UserService = userService;
DiscordOAuth2Service = discordOAuth2Service; DiscordOAuth2Service = discordOAuth2Service;
DateTimeService = dateTimeService;
} }
[HttpGet("google")] [HttpGet("google")]
@@ -63,7 +65,7 @@ public class OAuth2Controller : Controller
Response.Cookies.Append("token", token, new () Response.Cookies.Append("token", token, new ()
{ {
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10)) Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
}); });
return Redirect("/"); return Redirect("/");
@@ -121,7 +123,7 @@ public class OAuth2Controller : Controller
Response.Cookies.Append("token", token, new () Response.Cookies.Append("token", token, new ()
{ {
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10)) Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
}); });
return Redirect("/"); return Redirect("/");

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Events;
using Moonlight.App.Http.Requests.Wings; using Moonlight.App.Http.Requests.Wings;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers; using Moonlight.App.Repositories.Servers;
@@ -11,17 +12,17 @@ namespace Moonlight.App.Http.Controllers.Api.Remote;
public class BackupController : Controller public class BackupController : Controller
{ {
private readonly ServerBackupRepository ServerBackupRepository; private readonly ServerBackupRepository ServerBackupRepository;
private readonly MessageService MessageService; private readonly EventSystem Event;
private readonly NodeRepository NodeRepository; private readonly NodeRepository NodeRepository;
public BackupController( public BackupController(
ServerBackupRepository serverBackupRepository, ServerBackupRepository serverBackupRepository,
NodeRepository nodeRepository, NodeRepository nodeRepository,
MessageService messageService) EventSystem eventSystem)
{ {
ServerBackupRepository = serverBackupRepository; ServerBackupRepository = serverBackupRepository;
NodeRepository = nodeRepository; NodeRepository = nodeRepository;
MessageService = messageService; Event = eventSystem;
} }
[HttpGet("{uuid}")] [HttpGet("{uuid}")]
@@ -57,11 +58,11 @@ public class BackupController : Controller
ServerBackupRepository.Update(backup); ServerBackupRepository.Update(backup);
await MessageService.Emit($"wings.backups.create", backup); await Event.Emit($"wings.backups.create", backup);
} }
else else
{ {
await MessageService.Emit($"wings.backups.createfailed", backup); await Event.Emit($"wings.backups.createFailed", backup);
ServerBackupRepository.Delete(backup); ServerBackupRepository.Delete(backup);
} }
@@ -88,7 +89,7 @@ public class BackupController : Controller
if (backup == null) if (backup == null)
return NotFound(); return NotFound();
await MessageService.Emit($"wings.backups.restore", backup); await Event.Emit($"wings.backups.restore", backup);
return NoContent(); return NoContent();
} }

View File

@@ -1,6 +1,7 @@
using Logging.Net; using Logging.Net;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Http.Requests.Daemon; using Moonlight.App.Http.Requests.Daemon;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Services; using Moonlight.App.Services;
@@ -12,13 +13,13 @@ namespace Moonlight.App.Http.Controllers.Api.Remote;
public class DdosController : Controller public class DdosController : Controller
{ {
private readonly NodeRepository NodeRepository; private readonly NodeRepository NodeRepository;
private readonly MessageService MessageService; private readonly EventSystem Event;
private readonly DdosAttackRepository DdosAttackRepository; private readonly DdosAttackRepository DdosAttackRepository;
public DdosController(NodeRepository nodeRepository, MessageService messageService, DdosAttackRepository ddosAttackRepository) public DdosController(NodeRepository nodeRepository, EventSystem eventSystem, DdosAttackRepository ddosAttackRepository)
{ {
NodeRepository = nodeRepository; NodeRepository = nodeRepository;
MessageService = messageService; Event = eventSystem;
DdosAttackRepository = ddosAttackRepository; DdosAttackRepository = ddosAttackRepository;
} }
@@ -47,7 +48,7 @@ public class DdosController : Controller
ddosAttack = DdosAttackRepository.Add(ddosAttack); ddosAttack = DdosAttackRepository.Add(ddosAttack);
await MessageService.Emit("node.ddos", ddosAttack); await Event.Emit("node.ddos", ddosAttack);
return Ok(); return Ok();
} }

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Events;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Http.Resources.Wings; using Moonlight.App.Http.Resources.Wings;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
@@ -15,18 +16,18 @@ public class ServersController : Controller
private readonly WingsServerConverter Converter; private readonly WingsServerConverter Converter;
private readonly ServerRepository ServerRepository; private readonly ServerRepository ServerRepository;
private readonly NodeRepository NodeRepository; private readonly NodeRepository NodeRepository;
private readonly MessageService MessageService; private readonly EventSystem Event;
public ServersController( public ServersController(
WingsServerConverter converter, WingsServerConverter converter,
ServerRepository serverRepository, ServerRepository serverRepository,
NodeRepository nodeRepository, NodeRepository nodeRepository,
MessageService messageService) EventSystem eventSystem)
{ {
Converter = converter; Converter = converter;
ServerRepository = serverRepository; ServerRepository = serverRepository;
NodeRepository = nodeRepository; NodeRepository = nodeRepository;
MessageService = messageService; Event = eventSystem;
} }
[HttpGet] [HttpGet]
@@ -68,7 +69,7 @@ public class ServersController : Controller
totalPages = slice.Length - 1; totalPages = slice.Length - 1;
} }
await MessageService.Emit($"wings.{node.Id}.serverlist", node); await Event.Emit($"wings.{node.Id}.serverList", node);
//Logger.Debug($"[BRIDGE] Node '{node.Name}' is requesting server list page {page} with {perPage} items per page"); //Logger.Debug($"[BRIDGE] Node '{node.Name}' is requesting server list page {page} with {perPage} items per page");
@@ -97,7 +98,7 @@ public class ServersController : Controller
if (token != node.Token) if (token != node.Token)
return Unauthorized(); return Unauthorized();
await MessageService.Emit($"wings.{node.Id}.statereset", node); await Event.Emit($"wings.{node.Id}.stateReset", node);
foreach (var server in ServerRepository foreach (var server in ServerRepository
.Get() .Get()
@@ -136,7 +137,7 @@ public class ServersController : Controller
if (server == null) if (server == null)
return NotFound(); return NotFound();
await MessageService.Emit($"wings.{node.Id}.serverfetch", server); await Event.Emit($"wings.{node.Id}.serverFetch", server);
try //TODO: Remove try //TODO: Remove
{ {
@@ -169,7 +170,7 @@ public class ServersController : Controller
if (server == null) if (server == null)
return NotFound(); return NotFound();
await MessageService.Emit($"wings.{node.Id}.serverinstallfetch", server); await Event.Emit($"wings.{node.Id}.serverInstallFetch", server);
return new WingsServerInstall() return new WingsServerInstall()
{ {
@@ -202,8 +203,8 @@ public class ServersController : Controller
server.Installing = false; server.Installing = false;
ServerRepository.Update(server); ServerRepository.Update(server);
await MessageService.Emit($"wings.{node.Id}.serverinstallcomplete", server); await Event.Emit($"wings.{node.Id}.serverInstallComplete", server);
await MessageService.Emit($"server.{server.Uuid}.installcomplete", server); await Event.Emit($"server.{server.Uuid}.installComplete", server);
return Ok(); return Ok();
} }

View File

@@ -1,83 +0,0 @@
using System.Diagnostics;
using Logging.Net;
namespace Moonlight.App.MessageSystem;
public class MessageSender
{
private readonly List<MessageSubscriber> Subscribers;
public bool Debug { get; set; }
public TimeSpan TookToLongTime { get; set; } = TimeSpan.FromSeconds(1);
public MessageSender()
{
Subscribers = new();
}
public void Subscribe<T, K>(string name, object bind, Func<K, Task> method)
{
lock (Subscribers)
{
Subscribers.Add(new ()
{
Name = name,
Action = method,
Type = typeof(T),
Bind = bind
});
}
if(Debug)
Logger.Debug($"{bind} subscribed to '{name}'");
}
public void Unsubscribe(string name, object bind)
{
lock (Subscribers)
{
Subscribers.RemoveAll(x => x.Bind == bind);
}
if(Debug)
Logger.Debug($"{bind} unsubscribed from '{name}'");
}
public Task Emit(string name, object? value, bool disableWarning = false)
{
lock (Subscribers)
{
foreach (var subscriber in Subscribers)
{
if (subscriber.Name == name)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
var del = (Delegate)subscriber.Action;
((Task)del.DynamicInvoke(value)!).Wait();
stopWatch.Stop();
if (!disableWarning)
{
if (stopWatch.Elapsed.TotalMilliseconds > TookToLongTime.TotalMilliseconds)
{
Logger.Warn(
$"Subscriber {subscriber.Type.Name} for event '{name}' took long to process. {stopWatch.Elapsed.TotalMilliseconds}ms");
}
}
if (Debug)
{
Logger.Debug(
$"Subscriber {subscriber.Type.Name} for event '{name}' took {stopWatch.Elapsed.TotalMilliseconds}ms");
}
}
}
}
return Task.CompletedTask;
}
}

View File

@@ -1,9 +0,0 @@
namespace Moonlight.App.MessageSystem;
public class MessageSubscriber
{
public string Name { get; set; }
public object Action { get; set; }
public Type Type { get; set; }
public object Bind { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
namespace Moonlight.App.Models.Forms;
public class CloudPanelDataModel
{
[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 specify the host")]
public string Host { 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

@@ -7,7 +7,7 @@ public class DomainOrderDataModel
{ {
[Required(ErrorMessage = "You need to specify a name")] [Required(ErrorMessage = "You need to specify a name")]
[MaxLength(32, ErrorMessage = "The max lenght for the name is 32 characters")] [MaxLength(32, ErrorMessage = "The max lenght for the name is 32 characters")]
[RegularExpression(@"^[a-z]+$", ErrorMessage = "The name should only consist of lower case characters")] [RegularExpression(@"^[a-z0-9]+$", ErrorMessage = "The name should only consist of lower case characters or numbers")]
public string Name { get; set; } = ""; public string Name { get; set; } = "";
[Required(ErrorMessage = "You need to specify a shared domain")] [Required(ErrorMessage = "You need to specify a shared domain")]

View File

@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using Moonlight.App.Database.Entities;
namespace Moonlight.App.Models.Forms;
public class ServerEditDataModel
{
[Required(ErrorMessage = "You need to enter a name")]
[MaxLength(32, ErrorMessage = "The name cannot be longer that 32 characters")]
public string Name { get; set; }
[Required(ErrorMessage = "You need to specify a user")]
public User Owner { get; set; }
[Required(ErrorMessage = "You need to specify the cpu cores")]
public int Cpu { get; set; }
[Required(ErrorMessage = "You need to specify the memory")]
public long Memory { get; set; }
[Required(ErrorMessage = "You need to specify the disk")]
public long Disk { get; set; }
public string OverrideStartup { get; set; }
public int DockerImageIndex { get; set; }
public bool IsCleanupException { get; set; }
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database;
using Moonlight.App.Database.Entities;
namespace Moonlight.App.Repositories;
public class NodeAllocationRepository : IDisposable
{
// This repository is ONLY for the server creation service, so allocations can be found
// using raw sql. DO NOT use this in any other component
private readonly DataContext DataContext;
public NodeAllocationRepository(DataContext dataContext)
{
DataContext = dataContext;
}
public DbSet<NodeAllocation> Get()
{
return DataContext.NodeAllocations;
}
public void Dispose()
{
DataContext.Dispose();
}
}

View File

@@ -1,44 +0,0 @@
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,40 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database;
namespace Moonlight.App.Repositories;
public class Repository<TEntity> where TEntity : class
{
private readonly DataContext DataContext;
private readonly DbSet<TEntity> DbSet;
public Repository(DataContext dbContext)
{
DataContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
DbSet = DataContext.Set<TEntity>();
}
public DbSet<TEntity> Get()
{
return DbSet;
}
public TEntity Add(TEntity entity)
{
var x = DbSet.Add(entity);
DataContext.SaveChanges();
return x.Entity;
}
public void Update(TEntity entity)
{
DbSet.Update(entity);
DataContext.SaveChanges();
}
public void Delete(TEntity entity)
{
DbSet.Remove(entity);
DataContext.SaveChanges();
}
}

View File

@@ -1,16 +1,19 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database; using Moonlight.App.Database;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Services;
namespace Moonlight.App.Repositories; namespace Moonlight.App.Repositories;
public class StatisticsRepository : IDisposable public class StatisticsRepository : IDisposable
{ {
private readonly DataContext DataContext; private readonly DataContext DataContext;
private readonly DateTimeService DateTimeService;
public StatisticsRepository(DataContext dataContext) public StatisticsRepository(DataContext dataContext, DateTimeService dateTimeService)
{ {
DataContext = dataContext; DataContext = dataContext;
DateTimeService = dateTimeService;
} }
public DbSet<StatisticsData> Get() public DbSet<StatisticsData> Get()
@@ -27,7 +30,7 @@ public class StatisticsRepository : IDisposable
public StatisticsData Add(string chart, double value) public StatisticsData Add(string chart, double value)
{ {
return Add(new StatisticsData() {Chart = chart, Value = value, Date = DateTime.Now}); return Add(new StatisticsData() {Chart = chart, Value = value, Date = DateTimeService.GetCurrent()});
} }
public void Dispose() public void Dispose()

View File

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

View File

@@ -1,44 +0,0 @@
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

@@ -7,6 +7,7 @@ using Moonlight.App.Models.Wings;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers; using Moonlight.App.Repositories.Servers;
using Logging.Net; using Logging.Net;
using Moonlight.App.Events;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -23,21 +24,24 @@ public class CleanupService
#endregion #endregion
private readonly ConfigService ConfigService; private readonly ConfigService ConfigService;
private readonly MessageService MessageService; private readonly DateTimeService DateTimeService;
private readonly EventSystem Event;
private readonly IServiceScopeFactory ServiceScopeFactory; private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly PeriodicTimer Timer; private readonly PeriodicTimer Timer;
public CleanupService( public CleanupService(
ConfigService configService, ConfigService configService,
IServiceScopeFactory serviceScopeFactory, IServiceScopeFactory serviceScopeFactory,
MessageService messageService) DateTimeService dateTimeService,
EventSystem eventSystem)
{ {
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
MessageService = messageService; DateTimeService = dateTimeService;
ConfigService = configService; ConfigService = configService;
Event = eventSystem;
StartedAt = DateTime.Now; StartedAt = DateTimeService.GetCurrent();
CompletedAt = DateTime.Now; CompletedAt = DateTimeService.GetCurrent();
IsRunning = false; IsRunning = false;
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup"); var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
@@ -145,7 +149,7 @@ public class CleanupService
ServersRunning++; ServersRunning++;
} }
await MessageService.Emit("cleanup.updated", null); await Event.Emit("cleanup.updated");
} }
} }
else else
@@ -175,7 +179,7 @@ public class CleanupService
ServersRunning++; ServersRunning++;
} }
await MessageService.Emit("cleanup.updated", null); await Event.Emit("cleanup.updated");
} }
} }
} }
@@ -196,7 +200,7 @@ public class CleanupService
IsRunning = false; IsRunning = false;
CleanupsPerformed++; CleanupsPerformed++;
await MessageService.Emit("cleanup.updated", null); await Event.Emit("cleanup.updated");
} }
} }

View File

@@ -12,6 +12,7 @@ public class ConfigService : IConfiguration
private IConfiguration Configuration; private IConfiguration Configuration;
public bool DebugMode { get; private set; } = false; public bool DebugMode { get; private set; } = false;
public bool SqlDebugMode { get; private set; } = false;
public ConfigService(StorageService storageService) public ConfigService(StorageService storageService)
{ {
@@ -28,6 +29,14 @@ public class ConfigService : IConfiguration
if (DebugMode) if (DebugMode)
Logger.Debug("Debug mode enabled"); Logger.Debug("Debug mode enabled");
var sqlDebugVar = Environment.GetEnvironmentVariable("ML_SQL_DEBUG");
if (sqlDebugVar != null)
SqlDebugMode = bool.Parse(sqlDebugVar);
if (SqlDebugMode)
Logger.Debug("Sql debug mode enabled");
} }
public void Reload() public void Reload()

View File

@@ -0,0 +1,31 @@
using Moonlight.App.Helpers;
namespace Moonlight.App.Services;
public class DateTimeService
{
public long GetCurrentUnix()
{
return new DateTimeOffset(GetCurrent()).ToUnixTimeMilliseconds();
}
public long GetCurrentUnixSeconds()
{
return new DateTimeOffset(GetCurrent()).ToUnixTimeSeconds();
}
public DateTime GetCurrent()
{
return DateTime.UtcNow;
}
public string GetDate()
{
return Formatter.FormatDateOnly(GetCurrent());
}
public string GetDateTime()
{
return Formatter.FormatDate(GetCurrent());
}
}

View File

@@ -0,0 +1,99 @@
using Discord;
using Discord.Webhook;
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
namespace Moonlight.App.Services;
public class DiscordNotificationService
{
private readonly EventSystem Event;
private readonly ResourceService ResourceService;
private readonly DiscordWebhookClient Client;
private readonly string AppUrl;
public DiscordNotificationService(
EventSystem eventSystem,
ConfigService configService,
ResourceService resourceService)
{
Event = eventSystem;
ResourceService = resourceService;
var config = configService.GetSection("Moonlight").GetSection("DiscordNotifications");
if (config.GetValue<bool>("Enable"))
{
Logger.Info("Discord notifications enabled");
Client = new(config.GetValue<string>("WebHook"));
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
Event.On<User>("supportChat.new", this, OnNewSupportChat);
Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage);
Event.On<User>("supportChat.close", this, OnSupportChatClose);
}
else
{
Logger.Info("Discord notifications disabled");
}
}
private async Task OnSupportChatClose(User user)
{
await SendNotification("", builder =>
{
builder.Title = "A new support chat has been marked as closed";
builder.Color = Color.Red;
builder.AddField("Email", user.Email);
builder.AddField("Firstname", user.FirstName);
builder.AddField("Lastname", user.LastName);
builder.Url = $"{AppUrl}/admin/support/view/{user.Id}";
});
}
private async Task OnSupportChatMessage(SupportChatMessage message)
{
if(message.Sender == null)
return;
await SendNotification("", builder =>
{
builder.Title = "New message in support chat";
builder.Color = Color.Blue;
builder.AddField("Message", message.Content);
builder.Author = new EmbedAuthorBuilder()
.WithName($"{message.Sender.FirstName} {message.Sender.LastName}")
.WithIconUrl(ResourceService.Avatar(message.Sender));
builder.Url = $"{AppUrl}/admin/support/view/{message.Recipient.Id}";
});
}
private async Task OnNewSupportChat(User user)
{
await SendNotification("", builder =>
{
builder.Title = "A new support chat has been marked as active";
builder.Color = Color.Green;
builder.AddField("Email", user.Email);
builder.AddField("Firstname", user.FirstName);
builder.AddField("Lastname", user.LastName);
builder.Url = $"{AppUrl}/admin/support/view/{user.Id}";
});
}
private async Task SendNotification(string content, Action<EmbedBuilder>? embed = null)
{
var e = new EmbedBuilder();
embed?.Invoke(e);
await Client.SendMessageAsync(
content,
false,
new []{e.Build()},
"Moonlight Notification",
ResourceService.Image("logo.svg")
);
}
}

View File

@@ -0,0 +1,96 @@
using System.Text;
using Moonlight.App.Helpers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Moonlight.App.Services;
public class FabricService
{
private readonly HttpClient Client;
public FabricService()
{
Client = new();
}
public async Task<string> GetLatestInstallerVersion()
{
var data = await Client
.GetStringAsync("https://meta.fabricmc.net/v2/versions/installer");
var x = JsonConvert.DeserializeObject<JObject[]>(data) ?? Array.Empty<JObject>();
var stableVersions = new List<string>();
foreach (var y in x)
{
var section = new ConfigurationBuilder().AddJsonStream(
new MemoryStream(Encoding.ASCII.GetBytes(
y.Root.ToString()
)
)
).Build();
if (section.GetValue<bool>("stable"))
{
stableVersions.Add(section.GetValue<string>("version"));
}
}
return ParseHelper.GetHighestVersion(stableVersions.ToArray());
}
public async Task<string> GetLatestLoaderVersion()
{
var data = await Client
.GetStringAsync("https://meta.fabricmc.net/v2/versions/loader");
var x = JsonConvert.DeserializeObject<JObject[]>(data) ?? Array.Empty<JObject>();
var stableVersions = new List<string>();
foreach (var y in x)
{
var section = new ConfigurationBuilder().AddJsonStream(
new MemoryStream(Encoding.ASCII.GetBytes(
y.Root.ToString()
)
)
).Build();
if (section.GetValue<bool>("stable"))
{
stableVersions.Add(section.GetValue<string>("version"));
}
}
return ParseHelper.GetHighestVersion(stableVersions.ToArray());
}
public async Task<string[]> GetGameVersions()
{
var data = await Client
.GetStringAsync("https://meta.fabricmc.net/v2/versions/game");
var x = JsonConvert.DeserializeObject<JObject[]>(data) ?? Array.Empty<JObject>();
var stableVersions = new List<string>();
foreach (var y in x)
{
var section = new ConfigurationBuilder().AddJsonStream(
new MemoryStream(Encoding.ASCII.GetBytes(
y.Root.ToString()
)
)
).Build();
if (section.GetValue<bool>("stable"))
{
stableVersions.Add(section.GetValue<string>("version"));
}
}
return stableVersions.ToArray();
}
}

View File

@@ -0,0 +1,33 @@
using System.Text;
using Microsoft.JSInterop;
namespace Moonlight.App.Services;
public class FileDownloadService
{
private readonly IJSRuntime JSRuntime;
public FileDownloadService(IJSRuntime jsRuntime)
{
JSRuntime = jsRuntime;
}
public async Task DownloadStream(string fileName, Stream stream)
{
using var streamRef = new DotNetStreamReference(stream);
await JSRuntime.InvokeVoidAsync("moonlight.downloads.downloadStream", fileName, streamRef);
}
public async Task DownloadBytes(string fileName, byte[] bytes)
{
var ms = new MemoryStream(bytes);
await DownloadStream(fileName, ms);
}
public async Task DownloadString(string fileName, string content)
{
await DownloadBytes(fileName, Encoding.UTF8.GetBytes(content));
}
}

View File

@@ -0,0 +1,37 @@
using System.Text;
using Moonlight.App.Helpers;
namespace Moonlight.App.Services;
public class ForgeService
{
private readonly HttpClient Client;
public ForgeService()
{
Client = new();
}
// Key: 1.9.4-recommended Value: 12.17.0.2317
public async Task<Dictionary<string, string>> GetVersions()
{
var data = await Client.GetStringAsync(
"https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json");
var json = new ConfigurationBuilder().AddJsonStream(
new MemoryStream(Encoding.ASCII.GetBytes(
data
)
)
).Build();
var d = new Dictionary<string, string>();
foreach (var section in json.GetSection("promos").GetChildren())
{
d.Add(section.Key, section.Value!);
}
return d;
}
}

View File

@@ -10,14 +10,9 @@ public class ClipboardService
{ {
JsRuntime = jsRuntime; JsRuntime = jsRuntime;
} }
public async Task CopyToClipboard(string data)
{
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data);
}
public async Task Copy(string data) public async Task Copy(string data)
{ {
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data); await JsRuntime.InvokeVoidAsync("moonlight.clipboard.copy", data);
} }
} }

View File

@@ -13,36 +13,36 @@ public class ToastService
public async Task Info(string message) public async Task Info(string message)
{ {
await JsRuntime.InvokeVoidAsync("showInfoToast", message); await JsRuntime.InvokeVoidAsync("moonlight.toasts.info", message);
} }
public async Task Error(string message) public async Task Error(string message)
{ {
await JsRuntime.InvokeVoidAsync("showErrorToast", message); await JsRuntime.InvokeVoidAsync("moonlight.toasts.error", message);
} }
public async Task Warning(string message) public async Task Warning(string message)
{ {
await JsRuntime.InvokeVoidAsync("showWarningToast", message); await JsRuntime.InvokeVoidAsync("moonlight.toasts.warning", message);
} }
public async Task Success(string message) public async Task Success(string message)
{ {
await JsRuntime.InvokeVoidAsync("showSuccessToast", message); await JsRuntime.InvokeVoidAsync("moonlight.toasts.success", message);
} }
public async Task CreateProcessToast(string id, string text) public async Task CreateProcessToast(string id, string text)
{ {
await JsRuntime.InvokeVoidAsync("createToast", id, text); await JsRuntime.InvokeVoidAsync("moonlight.toasts.create", id, text);
} }
public async Task UpdateProcessToast(string id, string text) public async Task UpdateProcessToast(string id, string text)
{ {
await JsRuntime.InvokeVoidAsync("modifyToast", id, text); await JsRuntime.InvokeVoidAsync("moonlight.toasts.modify", id, text);
} }
public async Task RemoveProcessToast(string id) public async Task RemoveProcessToast(string id)
{ {
await JsRuntime.InvokeVoidAsync("removeToast", id); await JsRuntime.InvokeVoidAsync("moonlight.toasts.remove", id);
} }
} }

View File

@@ -1,9 +1,11 @@
using System.Net; using System.Net;
using System.Net.Mail; using System.Net.Mail;
using Logging.Net; using Logging.Net;
using MimeKit;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
namespace Moonlight.App.Services; namespace Moonlight.App.Services;
@@ -13,6 +15,7 @@ public class MailService
private readonly string Password; private readonly string Password;
private readonly string Email; private readonly string Email;
private readonly int Port; private readonly int Port;
private readonly bool Ssl;
public MailService(ConfigService configService) public MailService(ConfigService configService)
{ {
@@ -24,6 +27,7 @@ public class MailService
Password = mailConfig.GetValue<string>("Password"); Password = mailConfig.GetValue<string>("Password");
Email = mailConfig.GetValue<string>("Email"); Email = mailConfig.GetValue<string>("Email");
Port = mailConfig.GetValue<int>("Port"); Port = mailConfig.GetValue<int>("Port");
Ssl = mailConfig.GetValue<bool>("Ssl");
} }
public async Task SendMail( public async Task SendMail(
@@ -54,20 +58,24 @@ public class MailService
{ {
using var client = new SmtpClient(); using var client = new SmtpClient();
client.Host = Server; var mailMessage = new MimeMessage();
client.Port = Port; mailMessage.From.Add(new MailboxAddress(Email, Email));
client.EnableSsl = true; mailMessage.To.Add(new MailboxAddress(user.Email, user.Email));
client.Credentials = new NetworkCredential(Email, Password); mailMessage.Subject = $"Hey {user.FirstName}, there are news from moonlight";
await client.SendMailAsync(new MailMessage() var body = new BodyBuilder
{ {
From = new MailAddress(Email), HtmlBody = parsed
Sender = new MailAddress(Email), };
Body = parsed, mailMessage.Body = body.ToMessageBody();
IsBodyHtml = true,
Subject = $"Hey {user.FirstName}, there are news from moonlight", using (var smtpClient = new SmtpClient())
To = { new MailAddress(user.Email) } {
}); await smtpClient.ConnectAsync(Server, Port, Ssl);
await smtpClient.AuthenticateAsync(Email, Password);
await smtpClient.SendAsync(mailMessage);
await smtpClient.DisconnectAsync(true);
}
} }
catch (Exception e) catch (Exception e)
{ {

View File

@@ -1,11 +0,0 @@
using Moonlight.App.MessageSystem;
namespace Moonlight.App.Services;
public class MessageService : MessageSender
{
public MessageService()
{
Debug = false;
}
}

View File

@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database; using Moonlight.App.Database;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.Helpers.Files; using Moonlight.App.Helpers.Files;
@@ -21,8 +22,8 @@ public class ServerService
private readonly UserRepository UserRepository; private readonly UserRepository UserRepository;
private readonly ImageRepository ImageRepository; private readonly ImageRepository ImageRepository;
private readonly NodeRepository NodeRepository; private readonly NodeRepository NodeRepository;
private readonly NodeAllocationRepository NodeAllocationRepository;
private readonly WingsApiHelper WingsApiHelper; private readonly WingsApiHelper WingsApiHelper;
private readonly MessageService MessageService;
private readonly UserService UserService; private readonly UserService UserService;
private readonly ConfigService ConfigService; private readonly ConfigService ConfigService;
private readonly WingsJwtHelper WingsJwtHelper; private readonly WingsJwtHelper WingsJwtHelper;
@@ -30,6 +31,8 @@ public class ServerService
private readonly AuditLogService AuditLogService; private readonly AuditLogService AuditLogService;
private readonly ErrorLogService ErrorLogService; private readonly ErrorLogService ErrorLogService;
private readonly NodeService NodeService; private readonly NodeService NodeService;
private readonly DateTimeService DateTimeService;
private readonly EventSystem Event;
public ServerService( public ServerService(
ServerRepository serverRepository, ServerRepository serverRepository,
@@ -37,21 +40,22 @@ public class ServerService
UserRepository userRepository, UserRepository userRepository,
ImageRepository imageRepository, ImageRepository imageRepository,
NodeRepository nodeRepository, NodeRepository nodeRepository,
MessageService messageService,
UserService userService, UserService userService,
ConfigService configService, ConfigService configService,
WingsJwtHelper wingsJwtHelper, WingsJwtHelper wingsJwtHelper,
SecurityLogService securityLogService, SecurityLogService securityLogService,
AuditLogService auditLogService, AuditLogService auditLogService,
ErrorLogService errorLogService, ErrorLogService errorLogService,
NodeService nodeService) NodeService nodeService,
NodeAllocationRepository nodeAllocationRepository,
DateTimeService dateTimeService,
EventSystem eventSystem)
{ {
ServerRepository = serverRepository; ServerRepository = serverRepository;
WingsApiHelper = wingsApiHelper; WingsApiHelper = wingsApiHelper;
UserRepository = userRepository; UserRepository = userRepository;
ImageRepository = imageRepository; ImageRepository = imageRepository;
NodeRepository = nodeRepository; NodeRepository = nodeRepository;
MessageService = messageService;
UserService = userService; UserService = userService;
ConfigService = configService; ConfigService = configService;
WingsJwtHelper = wingsJwtHelper; WingsJwtHelper = wingsJwtHelper;
@@ -59,6 +63,9 @@ public class ServerService
AuditLogService = auditLogService; AuditLogService = auditLogService;
ErrorLogService = errorLogService; ErrorLogService = errorLogService;
NodeService = nodeService; NodeService = nodeService;
NodeAllocationRepository = nodeAllocationRepository;
DateTimeService = dateTimeService;
Event = eventSystem;
} }
private Server EnsureNodeData(Server s) private Server EnsureNodeData(Server s)
@@ -112,9 +119,9 @@ public class ServerService
var backup = new ServerBackup() var backup = new ServerBackup()
{ {
Name = $"Created at {DateTime.Now.ToShortDateString()} {DateTime.Now.ToShortTimeString()}", Name = $"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
Uuid = Guid.NewGuid(), Uuid = Guid.NewGuid(),
CreatedAt = DateTime.Now, CreatedAt = DateTimeService.GetCurrent(),
Created = false Created = false
}; };
@@ -186,15 +193,27 @@ public class ServerService
.Include(x => x.Backups) .Include(x => x.Backups)
.First(x => x.Id == server.Id); .First(x => x.Id == server.Id);
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}", try
null); {
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
null);
}
catch (WingsException e)
{
// when a backup is not longer there we can
// safely delete the backup so we ignore this error
if (e.StatusCode != 404)
{
throw;
}
}
var backup = serverData.Backups.First(x => x.Uuid == serverBackup.Uuid); var backup = serverData.Backups.First(x => x.Uuid == serverBackup.Uuid);
serverData.Backups.Remove(backup); serverData.Backups.Remove(backup);
ServerRepository.Update(serverData); ServerRepository.Update(serverData);
await MessageService.Emit("wings.backups.delete", backup); await Event.Emit("wings.backups.delete", backup);
await AuditLogService.Log(AuditLogType.DeleteBackup, await AuditLogService.Log(AuditLogType.DeleteBackup,
x => x =>
@@ -244,7 +263,7 @@ public class ServerService
} }
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null, public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null,
Action<Server>? modifyDetails = null, int allocations = 1) Action<Server>? modifyDetails = null)
{ {
var user = UserRepository var user = UserRepository
.Get() .Get()
@@ -256,32 +275,21 @@ public class ServerService
.Include(x => x.DockerImages) .Include(x => x.DockerImages)
.First(x => x.Id == i.Id); .First(x => x.Id == i.Id);
Node node; var allocations = image.Allocations;
if (n == null) Node node = n ?? NodeRepository.Get().First();
{
node = NodeRepository
.Get()
.Include(x => x.Allocations)
.First(); //TODO: Add smart deploy maybe
}
else
{
node = NodeRepository
.Get()
.Include(x => x.Allocations)
.First(x => x.Id == n.Id);
}
NodeAllocation[] freeAllocations; NodeAllocation[] freeAllocations;
try try
{ {
freeAllocations = node.Allocations // We have sadly no choice to use entity framework to do what the sql call does, there
.Where(a => !ServerRepository.Get() // are only slower ways, so we will use a raw sql call as a exception
.SelectMany(s => s.Allocations)
.Any(b => b.Id == a.Id)) freeAllocations = NodeAllocationRepository
.Take(allocations).ToArray(); .Get()
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
.ToArray();
} }
catch (Exception) catch (Exception)
{ {
@@ -372,7 +380,7 @@ public class ServerService
var user = await UserService.SftpLogin(id, password); var user = await UserService.SftpLogin(id, password);
if (server.Owner.Id == user.Id) if (server.Owner.Id == user.Id || user.Admin)
{ {
return server; return server;
} }

View File

@@ -4,6 +4,7 @@ using JWT.Builder;
using JWT.Exceptions; using JWT.Exceptions;
using Logging.Net; using Logging.Net;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc; using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Services.LogServices; using Moonlight.App.Services.LogServices;
@@ -123,9 +124,9 @@ public class IdentityService
return null; return null;
} }
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(iat).DateTime; var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime;
if (issuedAt < user.TokenValidTime.ToUniversalTime()) if (iatD < user.TokenValidTime)
return null; return null;
UserCache = user; UserCache = user;

View File

@@ -12,6 +12,7 @@ public class SessionService
private readonly IdentityService IdentityService; private readonly IdentityService IdentityService;
private readonly NavigationManager NavigationManager; private readonly NavigationManager NavigationManager;
private readonly AlertService AlertService; private readonly AlertService AlertService;
private readonly DateTimeService DateTimeService;
private Session? OwnSession; private Session? OwnSession;
@@ -19,12 +20,14 @@ public class SessionService
SessionRepository sessionRepository, SessionRepository sessionRepository,
IdentityService identityService, IdentityService identityService,
NavigationManager navigationManager, NavigationManager navigationManager,
AlertService alertService) AlertService alertService,
DateTimeService dateTimeService)
{ {
SessionRepository = sessionRepository; SessionRepository = sessionRepository;
IdentityService = identityService; IdentityService = identityService;
NavigationManager = navigationManager; NavigationManager = navigationManager;
AlertService = alertService; AlertService = alertService;
DateTimeService = dateTimeService;
} }
public async Task Register() public async Task Register()
@@ -36,7 +39,7 @@ public class SessionService
Ip = IdentityService.GetIp(), Ip = IdentityService.GetIp(),
Url = NavigationManager.Uri, Url = NavigationManager.Uri,
Device = IdentityService.GetDevice(), Device = IdentityService.GetDevice(),
CreatedAt = DateTime.Now, CreatedAt = DateTimeService.GetCurrent(),
User = user, User = user,
Navigation = NavigationManager, Navigation = NavigationManager,
AlertService = AlertService AlertService = AlertService
@@ -64,10 +67,8 @@ public class SessionService
{ {
foreach (var session in SessionRepository.Get()) foreach (var session in SessionRepository.Get())
{ {
if (session.User.Id == user.Id) if(session.User != null && session.User.Id == user.Id)
{
session.Navigation.NavigateTo(session.Navigation.Uri, true); session.Navigation.NavigateTo(session.Navigation.Uri, true);
}
} }
} }
} }

View File

@@ -6,18 +6,20 @@ namespace Moonlight.App.Services;
public class SmartDeployService public class SmartDeployService
{ {
private readonly NodeRepository NodeRepository; private readonly NodeRepository NodeRepository;
private readonly PleskServerRepository PleskServerRepository; private readonly Repository<CloudPanel> CloudPanelRepository;
private readonly WebsiteService WebsiteService; private readonly WebSpaceService WebSpaceService;
private readonly NodeService NodeService; private readonly NodeService NodeService;
public SmartDeployService( public SmartDeployService(
NodeRepository nodeRepository, NodeRepository nodeRepository,
NodeService nodeService, PleskServerRepository pleskServerRepository, WebsiteService websiteService) NodeService nodeService,
WebSpaceService webSpaceService,
Repository<CloudPanel> cloudPanelRepository)
{ {
NodeRepository = nodeRepository; NodeRepository = nodeRepository;
NodeService = nodeService; NodeService = nodeService;
PleskServerRepository = pleskServerRepository; WebSpaceService = webSpaceService;
WebsiteService = websiteService; CloudPanelRepository = cloudPanelRepository;
} }
public async Task<Node?> GetNode() public async Task<Node?> GetNode()
@@ -38,16 +40,14 @@ public class SmartDeployService
return data.MaxBy(x => x.Value).Key; return data.MaxBy(x => x.Value).Key;
} }
public async Task<PleskServer?> GetPleskServer() public async Task<CloudPanel?> GetCloudPanel()
{ {
var result = new List<PleskServer>(); var result = new List<CloudPanel>();
foreach (var pleskServer in PleskServerRepository.Get().ToArray()) foreach (var cloudPanel in CloudPanelRepository.Get().ToArray())
{ {
if (await WebsiteService.IsHostUp(pleskServer)) if (await WebSpaceService.IsHostUp(cloudPanel))
{ result.Add(cloudPanel);
result.Add(pleskServer);
}
} }
return result.FirstOrDefault(); return result.FirstOrDefault();

View File

@@ -1,57 +1,78 @@
using Moonlight.App.Database; using Logging.Net;
using Moonlight.App.Database;
using Moonlight.App.Database.Entities;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services.Statistics; namespace Moonlight.App.Services.Statistics;
public class StatisticsCaptureService public class StatisticsCaptureService
{ {
private readonly DataContext DataContext;
private readonly ConfigService ConfigService;
private readonly StatisticsRepository StatisticsRepository;
private readonly IServiceScopeFactory ServiceScopeFactory; private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly WebsiteService WebsiteService; private readonly DateTimeService DateTimeService;
private readonly PleskServerRepository PleskServerRepository; private readonly PeriodicTimer Timer;
private PeriodicTimer Timer;
public StatisticsCaptureService(IServiceScopeFactory serviceScopeFactory, ConfigService configService) public StatisticsCaptureService(IServiceScopeFactory serviceScopeFactory, ConfigService configService, DateTimeService dateTimeService)
{ {
ServiceScopeFactory = serviceScopeFactory; ServiceScopeFactory = serviceScopeFactory;
var provider = ServiceScopeFactory.CreateScope().ServiceProvider; DateTimeService = dateTimeService;
DataContext = provider.GetRequiredService<DataContext>();
ConfigService = configService;
StatisticsRepository = provider.GetRequiredService<StatisticsRepository>();
WebsiteService = provider.GetRequiredService<WebsiteService>();
PleskServerRepository = provider.GetRequiredService<PleskServerRepository>();
var config = ConfigService.GetSection("Moonlight").GetSection("Statistics"); var config = configService
.GetSection("Moonlight")
.GetSection("Statistics");
if(!config.GetValue<bool>("Enabled")) if(!config.GetValue<bool>("Enabled"))
return; return;
var _period = config.GetValue<int>("Wait"); var period = TimeSpan.FromMinutes(config.GetValue<int>("Wait"));
var period = TimeSpan.FromMinutes(_period);
Timer = new(period); Timer = new(period);
Logger.Info("Starting statistics system");
Task.Run(Run); Task.Run(Run);
} }
private async Task Run() private async Task Run()
{ {
while (await Timer.WaitForNextTickAsync()) try
{ {
StatisticsRepository.Add("statistics.usersCount", DataContext.Users.Count()); while (await Timer.WaitForNextTickAsync())
StatisticsRepository.Add("statistics.serversCount", DataContext.Servers.Count());
StatisticsRepository.Add("statistics.domainsCount", DataContext.Domains.Count());
StatisticsRepository.Add("statistics.websitesCount", DataContext.Websites.Count());
int databases = 0;
await foreach (var pleskServer in PleskServerRepository.Get())
{ {
databases += (await WebsiteService.GetDefaultDatabaseServer(pleskServer)).DbCount; Logger.Warn("Creating statistics");
using var scope = ServiceScopeFactory.CreateScope();
var statisticsRepo = scope.ServiceProvider.GetRequiredService<Repository<StatisticsData>>();
var usersRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
var serversRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
var domainsRepo = scope.ServiceProvider.GetRequiredService<Repository<Domain>>();
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
var databasesRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
var sessionService = scope.ServiceProvider.GetRequiredService<SessionService>();
void AddEntry(string chart, int value)
{
statisticsRepo!.Add(new StatisticsData()
{
Chart = chart,
Value = value,
Date = DateTimeService.GetCurrent()
});
}
AddEntry("usersCount", usersRepo.Get().Count());
AddEntry("serversCount", serversRepo.Get().Count());
AddEntry("domainsCount", domainsRepo.Get().Count());
AddEntry("webspacesCount", webspacesRepo.Get().Count());
AddEntry("databasesCount", databasesRepo.Get().Count());
AddEntry("sessionsCount", sessionService.GetAll().Length);
} }
StatisticsRepository.Add("statistics.databasesCount", databases); Logger.Log("Statistics are weird");
}
catch (Exception e)
{
Logger.Error("An unexpected error occured while capturing statistics");
Logger.Error(e);
} }
} }
} }

View File

@@ -7,15 +7,17 @@ namespace Moonlight.App.Services.Statistics;
public class StatisticsViewService public class StatisticsViewService
{ {
private readonly StatisticsRepository StatisticsRepository; private readonly StatisticsRepository StatisticsRepository;
private readonly DateTimeService DateTimeService;
public StatisticsViewService(StatisticsRepository statisticsRepository) public StatisticsViewService(StatisticsRepository statisticsRepository, DateTimeService dateTimeService)
{ {
StatisticsRepository = statisticsRepository; StatisticsRepository = statisticsRepository;
DateTimeService = dateTimeService;
} }
public StatisticsData[] GetData(string chart, StatisticsTimeSpan timeSpan) public StatisticsData[] GetData(string chart, StatisticsTimeSpan timeSpan)
{ {
var startDate = DateTime.Now - TimeSpan.FromHours((int)timeSpan); var startDate = DateTimeService.GetCurrent() - TimeSpan.FromHours((int)timeSpan);
var objs = StatisticsRepository.Get().Where(x => x.Date > startDate && x.Chart == chart); var objs = StatisticsRepository.Get().Where(x => x.Date > startDate && x.Chart == chart);

View File

@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions; using Moonlight.App.Exceptions;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc; using Moonlight.App.Models.Misc;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Sessions;
@@ -14,20 +15,18 @@ public class SubscriptionService
private readonly OneTimeJwtService OneTimeJwtService; private readonly OneTimeJwtService OneTimeJwtService;
private readonly IdentityService IdentityService; private readonly IdentityService IdentityService;
private readonly UserRepository UserRepository; private readonly UserRepository UserRepository;
private readonly ConfigService ConfigService;
public SubscriptionService( public SubscriptionService(
SubscriptionRepository subscriptionRepository, SubscriptionRepository subscriptionRepository,
OneTimeJwtService oneTimeJwtService, OneTimeJwtService oneTimeJwtService,
IdentityService identityService, IdentityService identityService,
UserRepository userRepository, UserRepository userRepository
ConfigService configService) )
{ {
SubscriptionRepository = subscriptionRepository; SubscriptionRepository = subscriptionRepository;
OneTimeJwtService = oneTimeJwtService; OneTimeJwtService = oneTimeJwtService;
IdentityService = identityService; IdentityService = identityService;
UserRepository = userRepository; UserRepository = userRepository;
ConfigService = configService;
} }
public async Task<Subscription?> GetCurrent() public async Task<Subscription?> GetCurrent()
@@ -90,13 +89,15 @@ public class SubscriptionService
} }
} }
public async Task<SubscriptionLimit> GetLimit(string identifier) public async Task<SubscriptionLimit> GetLimit(string identifier) // Cache, optimize sql code
{ {
var subscription = await GetCurrent(); var subscription = await GetCurrent();
var defaultLimits = await GetDefaultLimits();
if (subscription == null) if (subscription == null)
{ {
return new() // If the default subscription limit with identifier is found, return it. if not, return empty
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
{ {
Identifier = identifier, Identifier = identifier,
Amount = 0 Amount = 0
@@ -111,8 +112,9 @@ public class SubscriptionService
if (foundLimit != null) if (foundLimit != null)
return foundLimit; return foundLimit;
return new() // If the default subscription limit with identifier is found, return it. if not, return empty
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
{ {
Identifier = identifier, Identifier = identifier,
Amount = 0 Amount = 0
@@ -133,4 +135,17 @@ public class SubscriptionService
return userWithData; return userWithData;
} }
private async Task<SubscriptionLimit[]> GetDefaultLimits() // Add cache and reload option
{
var defaultSubscriptionJson = "[]";
if (File.Exists(PathBuilder.File("storage", "configs", "default_subscription.json")))
{
defaultSubscriptionJson =
await File.ReadAllTextAsync(PathBuilder.File("storage", "configs", "default_subscription.json"));
}
return JsonConvert.DeserializeObject<SubscriptionLimit[]>(defaultSubscriptionJson) ?? Array.Empty<SubscriptionLimit>();
}
} }

View File

@@ -1,132 +0,0 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services.Support;
public class SupportAdminService
{
private readonly SupportServerService SupportServerService;
private readonly IdentityService IdentityService;
private readonly MessageService MessageService;
public EventHandler<SupportMessage> OnNewMessage;
public EventHandler OnUpdateTyping;
private List<string> TypingUsers = new();
private User Self;
private User Recipient;
public SupportAdminService(
SupportServerService supportServerService,
IdentityService identityService,
MessageService messageService)
{
SupportServerService = supportServerService;
IdentityService = identityService;
MessageService = messageService;
}
public async Task Start(User user)
{
Self = (await IdentityService.Get())!;
Recipient = user;
MessageService.Subscribe<SupportClientService, SupportMessage>(
$"support.{Recipient.Id}.message",
this,
message =>
{
OnNewMessage?.Invoke(this, message);
return Task.CompletedTask;
});
MessageService.Subscribe<SupportClientService, User>(
$"support.{Self.Id}.typing",
this,
user =>
{
HandleTyping(user);
return Task.CompletedTask;
});
}
#region Typing
private void HandleTyping(User user)
{
var name = $"{user.FirstName} {user.LastName}";
lock (TypingUsers)
{
if (!TypingUsers.Contains(name))
{
TypingUsers.Add(name);
OnUpdateTyping!.Invoke(this, null!);
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
if (TypingUsers.Contains(name))
{
TypingUsers.Remove(name);
OnUpdateTyping!.Invoke(this, null!);
}
});
}
}
}
public string[] GetTypingUsers()
{
lock (TypingUsers)
{
return TypingUsers.ToArray();
}
}
public Task TriggerTyping()
{
Task.Run(async () =>
{
await MessageService.Emit($"support.{Recipient.Id}.admintyping", Self);
});
return Task.CompletedTask;
}
#endregion
public async Task<SupportMessage[]> GetMessages()
{
return await SupportServerService.GetMessages(Recipient);
}
public async Task SendMessage(string content)
{
var message = new SupportMessage()
{
Message = content
};
await SupportServerService.SendMessage(
Recipient,
message,
Self,
true
);
}
public async Task Close()
{
await SupportServerService.Close(Recipient);
}
public void Dispose()
{
MessageService.Unsubscribe($"support.{Recipient.Id}.message", this);
MessageService.Unsubscribe($"support.{Recipient.Id}.typing", this);
}
}

View File

@@ -1,124 +0,0 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services.Support;
public class SupportClientService : IDisposable
{
private readonly SupportServerService SupportServerService;
private readonly IdentityService IdentityService;
private readonly MessageService MessageService;
public EventHandler<SupportMessage> OnNewMessage;
public EventHandler OnUpdateTyping;
private List<string> TypingUsers = new();
private User Self;
public SupportClientService(
SupportServerService supportServerService,
IdentityService identityService,
MessageService messageService)
{
SupportServerService = supportServerService;
IdentityService = identityService;
MessageService = messageService;
}
public async Task Start()
{
Self = (await IdentityService.Get())!;
MessageService.Subscribe<SupportClientService, SupportMessage>(
$"support.{Self.Id}.message",
this,
message =>
{
OnNewMessage?.Invoke(this, message);
return Task.CompletedTask;
});
MessageService.Subscribe<SupportClientService, User>(
$"support.{Self.Id}.admintyping",
this,
user =>
{
HandleTyping(user);
return Task.CompletedTask;
});
}
#region Typing
private void HandleTyping(User user)
{
var name = $"{user.FirstName} {user.LastName}";
lock (TypingUsers)
{
if (!TypingUsers.Contains(name))
{
TypingUsers.Add(name);
OnUpdateTyping!.Invoke(this, null!);
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
if (TypingUsers.Contains(name))
{
TypingUsers.Remove(name);
OnUpdateTyping!.Invoke(this, null!);
}
});
}
}
}
public string[] GetTypingUsers()
{
lock (TypingUsers)
{
return TypingUsers.ToArray();
}
}
public Task TriggerTyping()
{
Task.Run(async () =>
{
await MessageService.Emit($"support.{Self.Id}.typing", Self);
});
return Task.CompletedTask;
}
#endregion
public async Task<SupportMessage[]> GetMessages()
{
return await SupportServerService.GetMessages(Self);
}
public async Task SendMessage(string content)
{
var message = new SupportMessage()
{
Message = content
};
await SupportServerService.SendMessage(
Self,
message,
Self
);
}
public void Dispose()
{
MessageService.Unsubscribe($"support.{Self.Id}.message", this);
MessageService.Unsubscribe($"support.{Self.Id}.admintyping", this);
}
}

View File

@@ -1,138 +0,0 @@
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Repositories;
namespace Moonlight.App.Services.Support;
public class SupportServerService : IDisposable
{
private SupportMessageRepository SupportMessageRepository;
private MessageService MessageService;
private UserRepository UserRepository;
private readonly IServiceScopeFactory ServiceScopeFactory;
private IServiceScope ServiceScope;
public SupportServerService(IServiceScopeFactory serviceScopeFactory)
{
ServiceScopeFactory = serviceScopeFactory;
Task.Run(Run);
}
public async Task SendMessage(User r, SupportMessage message, User s, bool isSupport = false)
{
var recipient = UserRepository.Get().First(x => x.Id == r.Id);
var sender = UserRepository.Get().First(x => x.Id == s.Id);
Task.Run(async () =>
{
try
{
message.CreatedAt = DateTime.UtcNow;
message.Sender = sender;
message.Recipient = recipient;
message.IsSupport = isSupport;
SupportMessageRepository.Add(message);
await MessageService.Emit($"support.{recipient.Id}.message", message);
if (!recipient.SupportPending)
{
recipient.SupportPending = true;
UserRepository.Update(recipient);
if (!message.IsSupport)
{
var systemMessage = new SupportMessage()
{
Recipient = recipient,
Sender = null,
IsSystem = true,
Message = "The support team has been notified. Please be patient"
};
SupportMessageRepository.Add(systemMessage);
await MessageService.Emit($"support.{recipient.Id}.message", systemMessage);
}
await MessageService.Emit($"support.new", recipient);
Logger.Info("Support ticket created: " + recipient.Id);
//TODO: Ping or so
}
}
catch (Exception e)
{
Logger.Error("Error sending message");
Logger.Error(e);
}
});
}
public async Task Close(User user)
{
var recipient = UserRepository.Get().First(x => x.Id == user.Id);
recipient.SupportPending = false;
UserRepository.Update(recipient);
var systemMessage = new SupportMessage()
{
Recipient = recipient,
Sender = null,
IsSystem = true,
Message = "The ticket is now closed. Type a message to open it again"
};
SupportMessageRepository.Add(systemMessage);
await MessageService.Emit($"support.{recipient.Id}.message", systemMessage);
await MessageService.Emit($"support.close", recipient);
}
public Task<SupportMessage[]> GetMessages(User r)
{
var recipient = UserRepository.Get().First(x => x.Id == r.Id);
var messages = SupportMessageRepository
.Get()
.Include(x => x.Recipient)
.Include(x => x.Sender)
.Where(x => x.Recipient.Id == recipient.Id)
.AsEnumerable()
.TakeLast(50)
.OrderBy(x => x.Id)
.ToArray();
return Task.FromResult(messages);
}
private Task Run()
{
ServiceScope = ServiceScopeFactory.CreateScope();
SupportMessageRepository = ServiceScope
.ServiceProvider
.GetRequiredService<SupportMessageRepository>();
MessageService = ServiceScope
.ServiceProvider
.GetRequiredService<MessageService>();
UserRepository = ServiceScope
.ServiceProvider
.GetRequiredService<UserRepository>();
return Task.CompletedTask;
}
public void Dispose()
{
SupportMessageRepository.Dispose();
UserRepository.Dispose();
ServiceScope.Dispose();
}
}

View File

@@ -0,0 +1,134 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services.SupportChat;
public class SupportChatAdminService
{
private readonly EventSystem Event;
private readonly IdentityService IdentityService;
private readonly SupportChatServerService ServerService;
public Func<SupportChatMessage, Task>? OnMessage { get; set; }
public Func<string[], Task>? OnTypingChanged { get; set; }
private User? User;
private User Recipient = null!;
private readonly List<User> TypingUsers = new();
public SupportChatAdminService(
EventSystem eventSystem,
SupportChatServerService serverService,
IdentityService identityService)
{
Event = eventSystem;
ServerService = serverService;
IdentityService = identityService;
}
public async Task Start(User recipient)
{
User = await IdentityService.Get();
Recipient = recipient;
if (User != null)
{
await Event.On<SupportChatMessage>($"supportChat.{Recipient.Id}.message", this, async message =>
{
if (OnMessage != null)
{
if(message.Sender != null && message.Sender.Id == User.Id)
return;
await OnMessage.Invoke(message);
}
});
await Event.On<User>($"supportChat.{Recipient.Id}.typing", this, async user =>
{
await HandleTyping(user);
});
}
}
public async Task<SupportChatMessage[]> GetMessages()
{
if (User == null)
return Array.Empty<SupportChatMessage>();
return await ServerService.GetMessages(Recipient);
}
public async Task<SupportChatMessage> SendMessage(string content)
{
if (User != null)
{
return await ServerService.SendMessage(Recipient, content, User);
}
return null!;
}
private Task HandleTyping(User user)
{
lock (TypingUsers)
{
if (!TypingUsers.Contains(user))
{
TypingUsers.Add(user);
if (OnTypingChanged != null)
{
OnTypingChanged.Invoke(
TypingUsers
.Where(x => x.Id != User!.Id)
.Select(x => $"{x.FirstName} {x.LastName}")
.ToArray()
);
}
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
if (TypingUsers.Contains(user))
{
TypingUsers.Remove(user);
if (OnTypingChanged != null)
{
await OnTypingChanged.Invoke(
TypingUsers
.Where(x => x.Id != User!.Id)
.Select(x => $"{x.FirstName} {x.LastName}")
.ToArray()
);
}
}
});
}
}
return Task.CompletedTask;
}
public async Task SendTyping()
{
await Event.Emit($"supportChat.{Recipient.Id}.typing", User);
}
public async Task Close()
{
await ServerService.CloseChat(Recipient);
}
public async void Dispose()
{
if (User != null)
{
await Event.Off($"supportChat.{Recipient.Id}.message", this);
await Event.Off($"supportChat.{Recipient.Id}.typing", this);
}
}
}

View File

@@ -0,0 +1,128 @@
using Logging.Net;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Services.Sessions;
namespace Moonlight.App.Services.SupportChat;
public class SupportChatClientService : IDisposable
{
private readonly EventSystem Event;
private readonly IdentityService IdentityService;
private readonly SupportChatServerService ServerService;
public Func<SupportChatMessage, Task>? OnMessage { get; set; }
public Func<string[], Task>? OnTypingChanged { get; set; }
private User? User;
private readonly List<User> TypingUsers = new();
public SupportChatClientService(
EventSystem eventSystem,
SupportChatServerService serverService,
IdentityService identityService)
{
Event = eventSystem;
ServerService = serverService;
IdentityService = identityService;
}
public async Task Start()
{
User = await IdentityService.Get();
if (User != null)
{
await Event.On<SupportChatMessage>($"supportChat.{User.Id}.message", this, async message =>
{
if (OnMessage != null)
{
if(message.Sender != null && message.Sender.Id == User.Id)
return;
await OnMessage.Invoke(message);
}
});
await Event.On<User>($"supportChat.{User.Id}.typing", this, async user =>
{
await HandleTyping(user);
});
}
}
public async Task<SupportChatMessage[]> GetMessages()
{
if (User == null)
return Array.Empty<SupportChatMessage>();
return await ServerService.GetMessages(User);
}
public async Task<SupportChatMessage> SendMessage(string content)
{
if (User != null)
{
return await ServerService.SendMessage(User, content, User);
}
return null!;
}
private Task HandleTyping(User user)
{
lock (TypingUsers)
{
if (!TypingUsers.Contains(user))
{
TypingUsers.Add(user);
if (OnTypingChanged != null)
{
OnTypingChanged.Invoke(
TypingUsers
.Where(x => x.Id != User!.Id)
.Select(x => $"{x.FirstName} {x.LastName}")
.ToArray()
);
}
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(5));
if (TypingUsers.Contains(user))
{
TypingUsers.Remove(user);
if (OnTypingChanged != null)
{
await OnTypingChanged.Invoke(
TypingUsers
.Where(x => x.Id != User!.Id)
.Select(x => $"{x.FirstName} {x.LastName}")
.ToArray()
);
}
}
});
}
}
return Task.CompletedTask;
}
public async Task SendTyping()
{
await Event.Emit($"supportChat.{User!.Id}.typing", User);
}
public async void Dispose()
{
if (User != null)
{
await Event.Off($"supportChat.{User.Id}.message", this);
await Event.Off($"supportChat.{User.Id}.typing", this);
}
}
}

View File

@@ -0,0 +1,146 @@
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Repositories;
namespace Moonlight.App.Services.SupportChat;
public class SupportChatServerService
{
private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly DateTimeService DateTimeService;
private readonly EventSystem Event;
public SupportChatServerService(
IServiceScopeFactory serviceScopeFactory,
DateTimeService dateTimeService,
EventSystem eventSystem)
{
ServiceScopeFactory = serviceScopeFactory;
DateTimeService = dateTimeService;
Event = eventSystem;
}
public Task<SupportChatMessage[]> GetMessages(User recipient)
{
using var scope = ServiceScopeFactory.CreateScope();
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
var messages = msgRepo
.Get()
.Include(x => x.Recipient)
.Include(x => x.Sender)
.Where(x => x.Recipient.Id == recipient.Id)
.OrderByDescending(x => x.CreatedAt)
.AsEnumerable()
.Take(50)
.ToArray();
return Task.FromResult(messages);
}
public async Task<SupportChatMessage> SendMessage(User recipient, string content, User? sender, string? attachment = null)
{
using var scope = ServiceScopeFactory.CreateScope();
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
var message = new SupportChatMessage()
{
CreatedAt = DateTimeService.GetCurrent(),
IsQuestion = false,
Sender = sender == null ? null : userRepo.Get().First(x => x.Id == sender.Id),
Recipient = userRepo.Get().First(x => x.Id == recipient.Id),
Answer = "",
Attachment = attachment ?? "",
Content = content,
UpdatedAt = DateTimeService.GetCurrent()
};
var finalMessage = msgRepo.Add(message);
await Event.Emit($"supportChat.{recipient.Id}.message", finalMessage);
await Event.Emit("supportChat.message", finalMessage);
if (!userRepo.Get().First(x => x.Id == recipient.Id).SupportPending)
{
var ticketStart = new SupportChatMessage()
{
CreatedAt = DateTimeService.GetCurrent(),
IsQuestion = false,
Sender = null,
Recipient = userRepo.Get().First(x => x.Id == recipient.Id),
Answer = "",
Attachment = "",
Content = "Support ticket open", //TODO: Config
UpdatedAt = DateTimeService.GetCurrent()
};
var ticketStartFinal = msgRepo.Add(ticketStart);
var user = userRepo.Get().First(x => x.Id == recipient.Id);
user.SupportPending = true;
userRepo.Update(user);
await Event.Emit($"supportChat.{recipient.Id}.message", ticketStartFinal);
await Event.Emit("supportChat.message", ticketStartFinal);
await Event.Emit("supportChat.new", recipient);
}
return finalMessage;
}
public Task<Dictionary<User, SupportChatMessage?>> GetOpenChats()
{
var result = new Dictionary<User, SupportChatMessage?>();
using var scope = ServiceScopeFactory.CreateScope();
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
foreach (var user in userRepo.Get().Where(x => x.SupportPending).ToArray())
{
var lastMessage = msgRepo
.Get()
.Include(x => x.Recipient)
.Include(x => x.Sender)
.Where(x => x.Recipient.Id == user.Id)
.OrderByDescending(x => x.CreatedAt)
.AsEnumerable()
.FirstOrDefault();
result.Add(user, lastMessage);
}
return Task.FromResult(result);
}
public async Task CloseChat(User recipient)
{
using var scope = ServiceScopeFactory.CreateScope();
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
var ticketEnd = new SupportChatMessage()
{
CreatedAt = DateTimeService.GetCurrent(),
IsQuestion = false,
Sender = null,
Recipient = userRepo.Get().First(x => x.Id == recipient.Id),
Answer = "",
Attachment = "",
Content = "Support ticket closed", //TODO: Config
UpdatedAt = DateTimeService.GetCurrent()
};
var ticketEndFinal = msgRepo.Add(ticketEnd);
var user = userRepo.Get().First(x => x.Id == recipient.Id);
user.SupportPending = false;
userRepo.Update(user);
await Event.Emit($"supportChat.{recipient.Id}.message", ticketEndFinal);
await Event.Emit("supportChat.message", ticketEndFinal);
await Event.Emit("supportChat.close", recipient);
}
}

View File

@@ -19,6 +19,7 @@ public class UserService
private readonly MailService MailService; private readonly MailService MailService;
private readonly IdentityService IdentityService; private readonly IdentityService IdentityService;
private readonly IpLocateService IpLocateService; private readonly IpLocateService IpLocateService;
private readonly DateTimeService DateTimeService;
private readonly string JwtSecret; private readonly string JwtSecret;
@@ -29,7 +30,9 @@ public class UserService
SecurityLogService securityLogService, SecurityLogService securityLogService,
AuditLogService auditLogService, AuditLogService auditLogService,
MailService mailService, MailService mailService,
IdentityService identityService, IpLocateService ipLocateService) IdentityService identityService,
IpLocateService ipLocateService,
DateTimeService dateTimeService)
{ {
UserRepository = userRepository; UserRepository = userRepository;
TotpService = totpService; TotpService = totpService;
@@ -38,6 +41,7 @@ public class UserService
MailService = mailService; MailService = mailService;
IdentityService = identityService; IdentityService = identityService;
IpLocateService = ipLocateService; IpLocateService = ipLocateService;
DateTimeService = dateTimeService;
JwtSecret = configService JwtSecret = configService
.GetSection("Moonlight") .GetSection("Moonlight")
@@ -70,12 +74,12 @@ public class UserService
LastName = lastname, LastName = lastname,
State = "", State = "",
Status = UserStatus.Unverified, Status = UserStatus.Unverified,
CreatedAt = DateTime.UtcNow, CreatedAt = DateTimeService.GetCurrent(),
DiscordId = 0, DiscordId = 0,
TotpEnabled = false, TotpEnabled = false,
TotpSecret = "", TotpSecret = "",
UpdatedAt = DateTime.UtcNow, UpdatedAt = DateTimeService.GetCurrent(),
TokenValidTime = DateTime.Now.AddDays(-5) TokenValidTime = DateTimeService.GetCurrent().AddDays(-5)
}); });
await MailService.SendMail(user!, "register", values => {}); await MailService.SendMail(user!, "register", values => {});
@@ -168,7 +172,7 @@ public class UserService
public async Task ChangePassword(User user, string password, bool isSystemAction = false) public async Task ChangePassword(User user, string password, bool isSystemAction = false)
{ {
user.Password = BCrypt.Net.BCrypt.HashPassword(password); user.Password = BCrypt.Net.BCrypt.HashPassword(password);
user.TokenValidTime = DateTime.Now; user.TokenValidTime = DateTimeService.GetCurrent();
UserRepository.Update(user); UserRepository.Update(user);
if (isSystemAction) if (isSystemAction)
@@ -244,8 +248,8 @@ public class UserService
var token = JwtBuilder.Create() var token = JwtBuilder.Create()
.WithAlgorithm(new HMACSHA256Algorithm()) .WithAlgorithm(new HMACSHA256Algorithm())
.WithSecret(JwtSecret) .WithSecret(JwtSecret)
.AddClaim("exp", DateTimeOffset.UtcNow.AddDays(10).ToUnixTimeSeconds()) .AddClaim("exp", new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10)).ToUnixTimeSeconds())
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds()) .AddClaim("iat", DateTimeService.GetCurrentUnixSeconds())
.AddClaim("userid", user.Id) .AddClaim("userid", user.Id)
.Encode(); .Encode();

View File

@@ -0,0 +1,191 @@
using Logging.Net;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.ApiClients.CloudPanel.Requests;
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 WebSpaceService
{
private readonly Repository<CloudPanel> CloudPanelRepository;
private readonly Repository<WebSpace> WebSpaceRepository;
private readonly Repository<MySqlDatabase> DatabaseRepository;
private readonly CloudPanelApiHelper CloudPanelApiHelper;
public WebSpaceService(Repository<CloudPanel> cloudPanelRepository, Repository<WebSpace> webSpaceRepository, CloudPanelApiHelper cloudPanelApiHelper, Repository<MySqlDatabase> databaseRepository)
{
CloudPanelRepository = cloudPanelRepository;
WebSpaceRepository = webSpaceRepository;
CloudPanelApiHelper = cloudPanelApiHelper;
DatabaseRepository = databaseRepository;
}
public async Task<WebSpace> Create(string domain, User owner, CloudPanel? ps = null)
{
if (WebSpaceRepository.Get().Any(x => x.Domain == domain))
throw new DisplayException("A website with this domain does already exist");
var cloudPanel = ps ?? CloudPanelRepository.Get().First();
var ftpLogin = domain.Replace(".", "_");
var ftpPassword = StringHelper.GenerateString(16);
var phpVersion = "8.1"; // TODO: Add config option or smth
var w = new WebSpace()
{
CloudPanel = cloudPanel,
Owner = owner,
Domain = domain,
UserName = ftpLogin,
Password = ftpPassword,
VHostTemplate = "Generic" //TODO: Implement as select option
};
var webSpace = WebSpaceRepository.Add(w);
try
{
await CloudPanelApiHelper.Post(cloudPanel, "site/php", new AddPhpSite()
{
VHostTemplate = w.VHostTemplate,
DomainName = w.Domain,
PhpVersion = phpVersion,
SiteUser = w.UserName,
SiteUserPassword = w.Password
});
}
catch (Exception)
{
WebSpaceRepository.Delete(webSpace);
throw;
}
return webSpace;
}
public async Task Delete(WebSpace w)
{
var website = EnsureData(w);
await CloudPanelApiHelper.Delete(website.CloudPanel, $"site/{website.Domain}", null);
WebSpaceRepository.Delete(website);
}
public async Task<bool> IsHostUp(CloudPanel cloudPanel)
{
try
{
await CloudPanelApiHelper.Post(cloudPanel, "", null);
return true;
}
catch (CloudPanelException e)
{
if (e.StatusCode == 404)
return true;
}
catch (Exception)
{
// ignored
}
return false;
}
public async Task<bool> IsHostUp(WebSpace w)
{
var webSpace = EnsureData(w);
return await IsHostUp(webSpace.CloudPanel);
}
public async Task IssueSslCertificate(WebSpace w)
{
var webspace = EnsureData(w);
await CloudPanelApiHelper.Post(webspace.CloudPanel, "letsencrypt/install/certificate", new InstallLetsEncrypt()
{
DomainName = webspace.Domain
});
}
#region Databases
public Task<MySqlDatabase[]> GetDatabases(WebSpace w)
{
return Task.FromResult(WebSpaceRepository
.Get()
.Include(x => x.Databases)
.First(x => x.Id == w.Id)
.Databases.ToArray());
}
public async Task CreateDatabase(WebSpace w, string name, string password)
{
if (DatabaseRepository.Get().Any(x => x.UserName == name))
throw new DisplayException("A database with this name does already exist");
var webspace = EnsureData(w);
var database = new MySqlDatabase()
{
UserName = name,
Password = password
};
await CloudPanelApiHelper.Post(webspace.CloudPanel, "db", new AddDatabase()
{
DomainName = webspace.Domain,
DatabaseName = database.UserName,
DatabaseUserName = database.UserName,
DatabaseUserPassword = database.Password
});
webspace.Databases.Add(database);
WebSpaceRepository.Update(webspace);
}
public async Task DeleteDatabase(WebSpace w, MySqlDatabase database)
{
var webspace = EnsureData(w);
await CloudPanelApiHelper.Delete(webspace.CloudPanel, $"db/{database.UserName}", null);
webspace.Databases.Remove(database);
WebSpaceRepository.Update(webspace);
}
#endregion
public Task<FileAccess> CreateFileAccess(WebSpace w)
{
var webspace = EnsureData(w);
return Task.FromResult<FileAccess>(
new SftpFileAccess(webspace.CloudPanel.Host, webspace.UserName, webspace.Password, 22, true)
);
}
private WebSpace EnsureData(WebSpace webSpace)
{
if (webSpace.CloudPanel == null || webSpace.Owner == null)
return WebSpaceRepository
.Get()
.Include(x => x.CloudPanel)
.Include(x => x.Owner)
.First(x => x.Id == webSpace.Id);
return webSpace;
}
}

View File

@@ -1,383 +0,0 @@
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

@@ -19,10 +19,12 @@
<PackageReference Include="CloudFlare.Client" Version="6.1.4" /> <PackageReference Include="CloudFlare.Client" Version="6.1.4" />
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" /> <PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
<PackageReference Include="Discord.Net" Version="3.10.0" /> <PackageReference Include="Discord.Net" Version="3.10.0" />
<PackageReference Include="Discord.Net.Webhook" Version="3.10.0" />
<PackageReference Include="FluentFTP" Version="46.0.2" /> <PackageReference Include="FluentFTP" Version="46.0.2" />
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" /> <PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
<PackageReference Include="JWT" Version="10.0.2" /> <PackageReference Include="JWT" Version="10.0.2" />
<PackageReference Include="Logging.Net" Version="1.1.0" /> <PackageReference Include="Logging.Net" Version="1.1.0" />
<PackageReference Include="MailKit" Version="4.0.0" />
<PackageReference Include="Mappy.Net" Version="1.0.2" /> <PackageReference Include="Mappy.Net" Version="1.0.2" />
<PackageReference Include="Markdig" Version="0.31.0" /> <PackageReference Include="Markdig" Version="0.31.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
@@ -41,6 +43,7 @@
<PackageReference Include="PteroConsole.NET" Version="1.0.4" /> <PackageReference Include="PteroConsole.NET" Version="1.0.4" />
<PackageReference Include="QRCoder" Version="1.4.3" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" /> <PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
<PackageReference Include="SSH.NET" Version="2020.0.2" />
<PackageReference Include="UAParser" Version="3.1.47" /> <PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="XtermBlazor" Version="1.6.1" /> <PackageReference Include="XtermBlazor" Version="1.6.1" />
</ItemGroup> </ItemGroup>
@@ -67,6 +70,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
<Folder Include="App\Http\Middleware" /> <Folder Include="App\Http\Middleware" />
<Folder Include="App\Models\Daemon\Requests" /> <Folder Include="App\Models\Daemon\Requests" />
<Folder Include="App\Models\Google\Resources" /> <Folder Include="App\Models\Google\Resources" />

View File

@@ -1,29 +1,54 @@
@page "/" @page "/"
@using Moonlight.App.Services
@namespace Moonlight.Pages @namespace Moonlight.Pages
@inject SmartTranslateService SmartTranslateService
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{ @{
Layout = "_Layout"; Layout = "_Layout";
} }
<component type="typeof(BlazorApp)" render-mode="ServerPrerendered" /> <component type="typeof(BlazorApp)" render-mode="ServerPrerendered"/>
<div id="components-reconnect-modal" class="my-reconnect-modal components-reconnect-hide"> <div id="components-reconnect-modal" class="my-reconnect-modal components-reconnect-hide">
<div class="show"> <div class="show">
<p> <div class="modal d-block">
<br/> <div class="modal-dialog modal-dialog-centered mw-900px">
Connecting to moonlight servers <div class="modal-content">
</p> <img src="/assets/media/svg/loading.svg" class="card-img-top w-25 mx-auto pt-5" alt="...">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Your connection has been paused"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("We paused your connection because of inactivity. The resume just focus the tab and wait a few seconds"))</p>
</div>
</div>
</div>
</div>
</div> </div>
<div class="failed"> <div class="failed">
<p> <div class="modal d-block">
<br /> <div class="modal-dialog modal-dialog-centered mw-900px">
Connection to moonlight servers failed <div class="modal-content">
</p> <img src="/assets/media/svg/serverdown.svg" class="card-img-top w-25 mx-auto pt-5" alt="...">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Failed to reconnect to the moonlight servers"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("We were unable to reconnect to moonlight. Please refresh the page"))</p>
</div>
</div>
</div>
</div>
</div> </div>
<div class="rejected"> <div class="rejected">
<p> <div class="modal d-block">
<br /> <div class="modal-dialog modal-dialog-centered mw-900px">
Connection to moonlight servers rejected <div class="modal-content">
</p> <img src="/assets/media/svg/serverdown.svg" class="card-img-top w-25 mx-auto pt-5" alt="...">
<div class="pt-2 modal-body py-lg-10 px-lg-10">
<h2>@(SmartTranslateService.Translate("Failed to reconnect to the moonlight servers. The connection has been rejected"))</h2>
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("We were unable to reconnect to moonlight. Most of the time this is caused by an update of moonlight. Please refresh the page"))</p>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,12 @@
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using Moonlight.App.Extensions
@using Moonlight.App.Repositories
@using Moonlight.App.Services @using Moonlight.App.Services
@namespace Moonlight.Pages @namespace Moonlight.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@inject ConfigService ConfigService @inject ConfigService ConfigService
@inject LoadingMessageRepository LoadingMessageRepository
@{ @{
var headerConfig = ConfigService var headerConfig = ConfigService
@@ -51,7 +54,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<base href="~/"/> <base href="~/"/>
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/> <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
<title>Loading</title> <title>Loading</title>
</head> </head>
<body <body
@@ -73,9 +76,13 @@
<div class="app-page-loader flex-column"> <div class="app-page-loader flex-column">
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-25px"/> <img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-25px"/>
@{
var loadingMessage = LoadingMessageRepository.Get().Random();
}
<div class="d-flex align-items-center mt-5"> <div class="d-flex align-items-center mt-5">
<span class="spinner-border text-primary" role="status"></span> <span class="spinner-border text-primary" role="status"></span>
<span class="text-muted fs-6 fw-semibold ms-5">CHANGEME</span> <span class="text-muted fs-6 fw-semibold ms-5">@(loadingMessage.Message)</span>
</div> </div>
</div> </div>
@@ -93,25 +100,18 @@
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
<script src="/assets/js/xtermAddons.js"></script>
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script> <script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
<script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script> <script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script> <script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
<script src="/_content/BlazorMonaco/jsInterop.js"></script> <script src="/_content/BlazorMonaco/jsInterop.js"></script>
<script src="/assets/js/monacoTheme.js"></script>
<script src="/assets/js/scripts.bundle.js"></script> <script src="/assets/js/scripts.bundle.js"></script>
<script src="/assets/js/flashbang.js"></script>
<script src="/assets/js/cookieUtils.js"></script>
<script src="/assets/js/clipboard.js"></script>
<script src="/assets/js/toastUtils.js"></script>
<script src="/assets/js/alertUtils.js"></script>
<script src="/assets/js/utils.js"></script>
<script src="/assets/js/loggingUtils.js"></script>
<script src="/assets/js/snow.js"></script>
<script src="/assets/js/recaptcha.js"></script>
<script src="/assets/js/moonlight.js"></script> <script src="/assets/js/moonlight.js"></script>
<script>
moonlight.loading.registerXterm();
</script>
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script> <script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script> <script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
</body> </body>

View File

@@ -2,7 +2,9 @@ using BlazorDownloadFile;
using BlazorTable; using BlazorTable;
using CurrieTechnologies.Razor.SweetAlert2; using CurrieTechnologies.Razor.SweetAlert2;
using Logging.Net; using Logging.Net;
using Moonlight.App.ApiClients.CloudPanel;
using Moonlight.App.Database; using Moonlight.App.Database;
using Moonlight.App.Events;
using Moonlight.App.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.LogMigrator; using Moonlight.App.LogMigrator;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
@@ -17,7 +19,7 @@ using Moonlight.App.Services.Notifications;
using Moonlight.App.Services.OAuth2; using Moonlight.App.Services.OAuth2;
using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Sessions;
using Moonlight.App.Services.Statistics; using Moonlight.App.Services.Statistics;
using Moonlight.App.Services.Support; using Moonlight.App.Services.SupportChat;
namespace Moonlight namespace Moonlight
{ {
@@ -41,7 +43,13 @@ namespace Moonlight
// Add services to the container. // Add services to the container.
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor()
.AddHubOptions(options =>
{
options.MaximumReceiveMessageSize = 10000000;
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
options.HandshakeTimeout = TimeSpan.FromSeconds(10);
});
builder.Services.AddHttpContextAccessor(); builder.Services.AddHttpContextAccessor();
// Databases // Databases
@@ -54,23 +62,20 @@ namespace Moonlight
builder.Services.AddScoped<ServerRepository>(); builder.Services.AddScoped<ServerRepository>();
builder.Services.AddScoped<ServerBackupRepository>(); builder.Services.AddScoped<ServerBackupRepository>();
builder.Services.AddScoped<ImageRepository>(); builder.Services.AddScoped<ImageRepository>();
builder.Services.AddScoped<SupportMessageRepository>();
builder.Services.AddScoped<DomainRepository>(); builder.Services.AddScoped<DomainRepository>();
builder.Services.AddScoped<SharedDomainRepository>(); builder.Services.AddScoped<SharedDomainRepository>();
builder.Services.AddScoped<RevokeRepository>(); builder.Services.AddScoped<RevokeRepository>();
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<NewsEntryRepository>(); builder.Services.AddScoped<NewsEntryRepository>();
builder.Services.AddScoped<NodeAllocationRepository>();
builder.Services.AddScoped<StatisticsRepository>(); builder.Services.AddScoped<StatisticsRepository>();
builder.Services.AddScoped<AuditLogEntryRepository>(); builder.Services.AddScoped<AuditLogEntryRepository>();
builder.Services.AddScoped<ErrorLogEntryRepository>(); builder.Services.AddScoped<ErrorLogEntryRepository>();
builder.Services.AddScoped<SecurityLogEntryRepository>(); builder.Services.AddScoped<SecurityLogEntryRepository>();
builder.Services.AddScoped(typeof(Repository<>));
// Services // Services
builder.Services.AddSingleton<ConfigService>(); builder.Services.AddSingleton<ConfigService>();
@@ -85,7 +90,6 @@ namespace Moonlight
builder.Services.AddScoped<TotpService>(); builder.Services.AddScoped<TotpService>();
builder.Services.AddScoped<ToastService>(); builder.Services.AddScoped<ToastService>();
builder.Services.AddScoped<NodeService>(); builder.Services.AddScoped<NodeService>();
builder.Services.AddSingleton<MessageService>();
builder.Services.AddScoped<ServerService>(); builder.Services.AddScoped<ServerService>();
builder.Services.AddSingleton<PaperService>(); builder.Services.AddSingleton<PaperService>();
builder.Services.AddScoped<ClipboardService>(); builder.Services.AddScoped<ClipboardService>();
@@ -97,8 +101,13 @@ 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<WebSpaceService>();
builder.Services.AddScoped<StatisticsViewService>(); builder.Services.AddScoped<StatisticsViewService>();
builder.Services.AddSingleton<DateTimeService>();
builder.Services.AddSingleton<EventSystem>();
builder.Services.AddScoped<FileDownloadService>();
builder.Services.AddScoped<ForgeService>();
builder.Services.AddScoped<FabricService>();
builder.Services.AddScoped<GoogleOAuth2Service>(); builder.Services.AddScoped<GoogleOAuth2Service>();
builder.Services.AddScoped<DiscordOAuth2Service>(); builder.Services.AddScoped<DiscordOAuth2Service>();
@@ -106,8 +115,6 @@ namespace Moonlight
builder.Services.AddScoped<SubscriptionService>(); builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>(); builder.Services.AddScoped<SubscriptionAdminService>();
builder.Services.AddSingleton<CleanupService>();
// Loggers // Loggers
builder.Services.AddScoped<SecurityLogService>(); builder.Services.AddScoped<SecurityLogService>();
builder.Services.AddScoped<AuditLogService>(); builder.Services.AddScoped<AuditLogService>();
@@ -116,10 +123,10 @@ namespace Moonlight
builder.Services.AddScoped<MailService>(); builder.Services.AddScoped<MailService>();
builder.Services.AddSingleton<TrashMailDetectorService>(); builder.Services.AddSingleton<TrashMailDetectorService>();
// Support // Support chat
builder.Services.AddSingleton<SupportServerService>(); builder.Services.AddSingleton<SupportChatServerService>();
builder.Services.AddScoped<SupportAdminService>(); builder.Services.AddScoped<SupportChatClientService>();
builder.Services.AddScoped<SupportClientService>(); builder.Services.AddScoped<SupportChatAdminService>();
// Helpers // Helpers
builder.Services.AddSingleton<SmartTranslateHelper>(); builder.Services.AddSingleton<SmartTranslateHelper>();
@@ -130,11 +137,13 @@ 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>(); builder.Services.AddScoped<CloudPanelApiHelper>();
// Background services // Background services
builder.Services.AddSingleton<DiscordBotService>(); builder.Services.AddSingleton<DiscordBotService>();
builder.Services.AddSingleton<StatisticsCaptureService>(); builder.Services.AddSingleton<StatisticsCaptureService>();
builder.Services.AddSingleton<DiscordNotificationService>();
builder.Services.AddSingleton<CleanupService>();
// Third party services // Third party services
builder.Services.AddBlazorTable(); builder.Services.AddBlazorTable();
@@ -160,14 +169,12 @@ namespace Moonlight
app.MapBlazorHub(); app.MapBlazorHub();
app.MapFallbackToPage("/_Host"); app.MapFallbackToPage("/_Host");
// Support service
var supportServerService = app.Services.GetRequiredService<SupportServerService>();
// AutoStart services // AutoStart services
_ = app.Services.GetRequiredService<CleanupService>(); _ = app.Services.GetRequiredService<CleanupService>();
_ = app.Services.GetRequiredService<DiscordBotService>(); _ = app.Services.GetRequiredService<DiscordBotService>();
_ = app.Services.GetRequiredService<StatisticsCaptureService>(); _ = app.Services.GetRequiredService<StatisticsCaptureService>();
_ = app.Services.GetRequiredService<DiscordNotificationService>();
// Discord bot service // Discord bot service
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>(); //var discordBotService = app.Services.GetRequiredService<DiscordBotService>();

View File

@@ -17,12 +17,15 @@
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118", "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
"dotnetRunMessages": true "dotnetRunMessages": true
}, },
"IIS Express": { "Sql Debug": {
"commandName": "IISExpress", "commandName": "Project",
"launchBrowser": true, "launchBrowser": true,
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development",
} "ML_SQL_DEBUG": "true"
},
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
"dotnetRunMessages": true
}, },
"Docker": { "Docker": {
"commandName": "Docker", "commandName": "Docker",

View File

@@ -2,6 +2,7 @@
@using Moonlight.App.Exceptions @using Moonlight.App.Exceptions
@using Moonlight.App.Services @using Moonlight.App.Services
@using Logging.Net @using Logging.Net
@using Moonlight.App.ApiClients.CloudPanel
@inherits ErrorBoundaryBase @inherits ErrorBoundaryBase
@inject AlertService AlertService @inject AlertService AlertService
@@ -57,12 +58,16 @@ else
SmartTranslateService.Translate("Error from daemon"), SmartTranslateService.Translate("Error from daemon"),
wingsException.Message wingsException.Message
); );
//TODO: Error log service
Logger.Warn($"Wings exception status code: {wingsException.StatusCode}");
} }
else if (exception is PleskException pleskException) else if (exception is CloudPanelException cloudPanelException)
{ {
await AlertService.Error( await AlertService.Error(
SmartTranslateService.Translate("Error from plesk"), SmartTranslateService.Translate("Error from cloud panel"),
pleskException.Message cloudPanelException.Message
); );
} }
else if (exception is NotImplementedException) else if (exception is NotImplementedException)

View File

@@ -71,7 +71,7 @@
{ {
if (firstRender) if (firstRender)
{ {
await JsRuntime.InvokeVoidAsync("initMonacoTheme"); await JsRuntime.InvokeVoidAsync("moonlight.loading.loadMonaco");
Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () => Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () =>
{ {

View File

@@ -1,4 +1,5 @@
@using Moonlight.App.Helpers.Files @using Moonlight.App.Helpers.Files
@using Logging.Net
<div class="badge badge-lg badge-light-primary"> <div class="badge badge-lg badge-light-primary">
<div class="d-flex align-items-center flex-wrap"> <div class="d-flex align-items-center flex-wrap">

View File

@@ -35,88 +35,90 @@
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;"> <table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
<tbody class="fw-semibold text-gray-600"> <tbody class="fw-semibold text-gray-600">
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<tr class="even"> <ContentBlock @ref="ContentBlock" AllowContentOverride="true">
<td class="w-10px">
</td>
<td>
<div class="d-flex align-items-center">
<span class="icon-wrapper">
<i class="bx bx-md bx-up-arrow-alt text-primary"></i>
</span>
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
<TL>Go up</TL>
</a>
</div>
</td>
<td></td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2">
</div>
</div>
</td>
</tr>
@foreach (var file in Data)
{
<tr class="even"> <tr class="even">
<td class="w-10px"> <td class="w-10px">
@if (!HideSelect)
{
<div class="form-check form-check-sm form-check-custom form-check-solid">
@{
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
}
@if (toggle)
{
<input @onclick="() => SetToggleState(file, false)" class="form-check-input" type="checkbox" checked="checked">
}
else
{
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
}
</div>
}
</td> </td>
<td> <td>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="icon-wrapper"> <span class="icon-wrapper">
@if (file.IsFile) <i class="bx bx-md bx-up-arrow-alt text-primary"></i>
{
<i class="bx bx-md bx-file text-primary"></i>
}
else
{
<i class="bx bx-md bx-folder text-primary"></i>
}
</span> </span>
<a href="#" @onclick:preventDefault @onclick="() => OnClicked(file)" class="ms-3 text-gray-800 text-hover-primary">@(file.Name)</a> <a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
<TL>Go up</TL>
</a>
</div> </div>
</td> </td>
<td>@(Formatter.FormatSize(file.Size))</td> <td></td>
<td class="text-end"> <td class="text-end">
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<div class="ms-2 me-6"> <div class="ms-2">
@if (ContextActions.Any())
{
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
<span class="svg-icon svg-icon-5 m-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
</svg>
</span>
</button>
</ContextMenuTrigger>
}
</div> </div>
</div> </div>
</td> </td>
</tr> </tr>
} @foreach (var file in Data)
{
<tr class="even">
<td class="w-10px">
@if (!HideSelect)
{
<div class="form-check form-check-sm form-check-custom form-check-solid">
@{
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
}
@if (toggle)
{
<input @onclick="() => SetToggleState(file, false)" class="form-check-input" type="checkbox" checked="checked">
}
else
{
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
}
</div>
}
</td>
<td>
<div class="d-flex align-items-center">
<span class="icon-wrapper">
@if (file.IsFile)
{
<i class="bx bx-md bx-file text-primary"></i>
}
else
{
<i class="bx bx-md bx-folder text-primary"></i>
}
</span>
<a href="#" @onclick:preventDefault @onclick="() => OnClicked(file)" class="ms-3 text-gray-800 text-hover-primary">@(file.Name)</a>
</div>
</td>
<td>@(Formatter.FormatSize(file.Size))</td>
<td class="text-end">
<div class="d-flex justify-content-end">
<div class="ms-2 me-6">
@if (ContextActions.Any())
{
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
<span class="svg-icon svg-icon-5 m-0">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
</svg>
</span>
</button>
</ContextMenuTrigger>
}
</div>
</div>
</td>
</tr>
}
</ContentBlock>
</LazyLoader> </LazyLoader>
</tbody> </tbody>
</table> </table>
@@ -138,12 +140,13 @@
@code @code
{ {
[Parameter] [Parameter]
public FileAccess Access { get; set; } public FileAccess Access { get; set; }
[Parameter] [Parameter]
public Func<FileData, Task<bool>>? OnElementClicked { get; set; } public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
[Parameter] [Parameter]
public Func<Task>? OnSelectionChanged { get; set; } public Func<Task>? OnSelectionChanged { get; set; }
@@ -155,7 +158,7 @@
[Parameter] [Parameter]
public bool DisableScrolling { get; set; } = false; public bool DisableScrolling { get; set; } = false;
[Parameter] [Parameter]
public Func<FileData, bool>? Filter { get; set; } public Func<FileData, bool>? Filter { get; set; }
@@ -169,15 +172,19 @@
private Dictionary<FileData, bool> ToggleStatusCache = new(); private Dictionary<FileData, bool> ToggleStatusCache = new();
private bool AllToggled = false; private bool AllToggled = false;
private ContentBlock ContentBlock;
public async Task Refresh() public async Task Refresh()
{ {
ContentBlock?.SetBlocking(true);
var list = new List<FileData>(); var list = new List<FileData>();
foreach (var fileData in await Access.Ls()) foreach (var fileData in await Access.Ls())
{ {
if (Filter != null) if (Filter != null)
{ {
if(Filter.Invoke(fileData)) if (Filter.Invoke(fileData))
list.Add(fileData); list.Add(fileData);
} }
else else
@@ -185,7 +192,7 @@
} }
Data = list.ToArray(); Data = list.ToArray();
ToggleStatusCache.Clear(); ToggleStatusCache.Clear();
AllToggled = false; AllToggled = false;
@@ -196,6 +203,8 @@
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
OnSelectionChanged?.Invoke(); OnSelectionChanged?.Invoke();
ContentBlock?.SetBlocking(false);
} }
private async Task Load(LazyLoader arg) private async Task Load(LazyLoader arg)
@@ -257,7 +266,7 @@
if (canceled) if (canceled)
return; return;
} }
await Access.Up(); await Access.Up();
await Refresh(); await Refresh();
} }
@@ -273,4 +282,4 @@
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@@ -2,13 +2,13 @@
<div class="card-body pt-0 pb-0"> <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"> <ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2"> <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"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/webspaces">
<TL>Websites</TL> <TL>Webspaces</TL>
</a> </a>
</li> </li>
<li class="nav-item mt-2"> <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"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/webspaces/servers">
<TL>Plesk servers</TL> <TL>Cloud panels</TL>
</a> </a>
</li> </li>
</ul> </ul>

View File

@@ -0,0 +1,52 @@
@if (AllowContentOverride)
{
if (IsBlocking)
{
<div class="overlay overlay-block">
<div class="overlay-wrapper p-5">
@(ChildContent)
</div>
<div class="overlay-layer">
<div class="spinner-border text-primary" role="status">
</div>
</div>
</div>
}
else
{
@ChildContent
}
}
else
{
<div class="@(IsBlocking ? "overlay overlay-block" : "")">
<div class="@(IsBlocking ? "overlay-wrapper p-5" : "")">
@(ChildContent)
</div>
@if (IsBlocking)
{
<div class="overlay-layer">
<div class="spinner-border text-primary" role="status">
</div>
</div>
}
</div>
}
@code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool AllowContentOverride { get; set; } = false;
private bool IsBlocking = false;
public async Task SetBlocking(bool b)
{
IsBlocking = b;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -9,33 +9,36 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject CookieService CookieService @inject CookieService CookieService
<div class="menu menu-column justify-content-center" @if (User != null)
data-kt-menu="true"> {
<div class="menu-item"> <div class="menu menu-column justify-content-center"
<div class="dropdown"> data-kt-menu="true">
<button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown"> <div class="menu-item">
<TL>Create</TL> <div class="dropdown">
</button> <button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
<ul class="dropdown-menu"> <TL>Create</TL>
<li> </button>
<a class="dropdown-item py-2" href="/servers/create"> <ul class="dropdown-menu">
<TL>Server</TL> <li>
</a> <a class="dropdown-item py-2" href="/servers/create">
</li> <TL>Server</TL>
<li> </a>
<a class="dropdown-item py-2" href="/domains/create"> </li>
<TL>Domain</TL> <li>
</a> <a class="dropdown-item py-2" href="/domains/create">
</li> <TL>Domain</TL>
<li> </a>
<a class="dropdown-item py-2" href="/websites/create"> </li>
<TL>Website</TL> <li>
</a> <a class="dropdown-item py-2" href="/webspaces/create">
</li> <TL>Webspace</TL>
</ul> </a>
</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> }
<div class="app-navbar flex-shrink-0"> <div class="app-navbar flex-shrink-0">
<div class="app-navbar-item ms-1 ms-lg-3"> <div class="app-navbar-item ms-1 ms-lg-3">

View File

@@ -45,11 +45,11 @@ else
</a> </a>
</div> </div>
<div class="menu-item"> <div class="menu-item">
<a class="menu-link" href="/websites"> <a class="menu-link" href="/webspaces">
<span class="menu-icon"> <span class="menu-icon">
<i class="bx bx-globe"></i> <i class="bx bx-globe"></i>
</span> </span>
<span class="menu-title"><TL>Websites</TL></span> <span class="menu-title"><TL>Webspaces</TL></span>
</a> </a>
</div> </div>
<div class="menu-item"> <div class="menu-item">
@@ -148,11 +148,11 @@ else
</div> </div>
</div> </div>
<div class="menu-item"> <div class="menu-item">
<a class="menu-link" href="/admin/websites"> <a class="menu-link" href="/admin/webspaces">
<span class="menu-icon"> <span class="menu-icon">
<i class="bx bx-globe"></i> <i class="bx bx-globe"></i>
</span> </span>
<span class="menu-title"><TL>Websites</TL></span> <span class="menu-title"><TL>Webspaces</TL></span>
</a> </a>
</div> </div>
<div class="menu-item"> <div class="menu-item">

View File

@@ -47,6 +47,6 @@
private async void TriggerFlashbang() private async void TriggerFlashbang()
{ {
await JsRuntime.InvokeVoidAsync("flashbang"); await JsRuntime.InvokeVoidAsync("moonlight.flashbang.run");
} }
} }

View File

@@ -5,6 +5,7 @@
@using Logging.Net @using Logging.Net
@using BlazorContextMenu @using BlazorContextMenu
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Services.Interop @using Moonlight.App.Services.Interop
@inject ServerService ServerService @inject ServerService ServerService
@@ -12,7 +13,7 @@
@inject AlertService AlertService @inject AlertService AlertService
@inject ToastService ToastService @inject ToastService ToastService
@inject ClipboardService ClipboardService @inject ClipboardService ClipboardService
@inject MessageService MessageService @inject EventSystem Event
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
@implements IDisposable @implements IDisposable
@@ -112,64 +113,51 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.create", this, (backup) => Event.On<ServerBackup>("wings.backups.create", this, async (backup) =>
{
if (AllBackups == null)
return Task.CompletedTask;
if (AllBackups.Any(x => x.Id == backup.Id))
{
Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1));
await ToastService.Success(SmartTranslateService.Translate("Backup successfully created"));
await LazyLoader.Reload();
});
}
return Task.CompletedTask;
});
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.createfailed", this, (backup) =>
{
if (AllBackups == null)
return Task.CompletedTask;
if (AllBackups.Any(x => x.Id == backup.Id))
{
Task.Run(async () =>
{
await ToastService.Error(SmartTranslateService.Translate("Backup creation failed"));
await LazyLoader.Reload();
});
}
return Task.CompletedTask;
});
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.delete", this, async (backup) =>
{ {
if (AllBackups == null) if (AllBackups == null)
return; return;
if (AllBackups.Any(x => x.Id == backup.Id)) if (AllBackups.Any(x => x.Id == backup.Id))
{ {
Task.Run(async () => await Task.Delay(TimeSpan.FromSeconds(1));
{ await ToastService.Success(SmartTranslateService.Translate("Backup successfully created"));
await ToastService.Success(SmartTranslateService.Translate("Backup successfully deleted")); await LazyLoader.Reload();
await LazyLoader.Reload();
});
} }
}); });
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.restore", this, async (backup) => Event.On<ServerBackup>("wings.backups.createFailed", this, async (backup) =>
{ {
if (AllBackups == null) if (AllBackups == null)
return; return;
if (AllBackups.Any(x => x.Id == backup.Id)) if (AllBackups.Any(x => x.Id == backup.Id))
{ {
Task.Run(async () => { await ToastService.Success(SmartTranslateService.Translate("Backup successfully restored")); }); await ToastService.Error(SmartTranslateService.Translate("Backup creation failed"));
await LazyLoader.Reload();
}
});
Event.On<ServerBackup>("wings.backups.delete", this, async (backup) =>
{
if (AllBackups == null)
return;
if (AllBackups.Any(x => x.Id == backup.Id))
{
await ToastService.Success(SmartTranslateService.Translate("Backup successfully deleted"));
await LazyLoader.Reload();
}
});
Event.On<ServerBackup>("wings.backups.restore", this, async (backup) =>
{
if (AllBackups == null)
return;
if (AllBackups.Any(x => x.Id == backup.Id))
{
await ToastService.Success(SmartTranslateService.Translate("Backup successfully restored"));
} }
}); });
} }
@@ -186,95 +174,102 @@
private async Task Download(ServerBackup serverBackup) private async Task Download(ServerBackup serverBackup)
{ {
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
NavigationManager.NavigateTo(url);
await ToastService.Success(SmartTranslateService.Translate("Backup download successfully started"));
/*
try try
{ {
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
NavigationManager.NavigateTo(url);
await ToastService.Success(SmartTranslateService.Translate("Backup download successfully started"));
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn("Error starting backup download"); Logger.Warn("Error starting backup download");
Logger.Warn(e); Logger.Warn(e);
await ToastService.Error(SmartTranslateService.Translate("Backup download failed")); await ToastService.Error(SmartTranslateService.Translate("Backup download failed"));
} }*/
} }
private async Task CopyUrl(ServerBackup serverBackup) private async Task CopyUrl(ServerBackup serverBackup)
{ {
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
await ClipboardService.Copy(url);
await AlertService.Success(
SmartTranslateService.Translate("Success"),
SmartTranslateService.Translate("Backup URL successfully copied to your clipboard"));
/*
try try
{ {
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
await ClipboardService.CopyToClipboard(url);
await AlertService.Success(
SmartTranslateService.Translate("Success"),
SmartTranslateService.Translate("Backup URL successfully copied to your clipboard"));
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn("Error copying backup url"); Logger.Warn("Error copying backup url");
Logger.Warn(e); Logger.Warn(e);
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while generating backup url")); await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while generating backup url"));
} }*/
} }
private async Task Delete(ServerBackup serverBackup) private async Task Delete(ServerBackup serverBackup)
{ {
await ToastService.Info(SmartTranslateService.Translate("Backup deletion started"));
await ServerService.DeleteBackup(CurrentServer, serverBackup);
/*
try try
{ {
await ToastService.Info(SmartTranslateService.Translate("Backup deletion started"));
await ServerService.DeleteBackup(CurrentServer, serverBackup);
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn("Error deleting backup"); Logger.Warn("Error deleting backup");
Logger.Warn(e); Logger.Warn(e);
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while starting backup deletion")); await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while starting backup deletion"));
} }*/
} }
private async Task Restore(ServerBackup serverBackup) private async Task Restore(ServerBackup serverBackup)
{ {
await ServerService.RestoreBackup(CurrentServer, serverBackup);
await ToastService.Info(SmartTranslateService.Translate("Backup restore started"));
/*
try try
{ {
await ServerService.RestoreBackup(CurrentServer, serverBackup);
await ToastService.Info(SmartTranslateService.Translate("Backup restore started"));
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn("Error restoring backup"); Logger.Warn("Error restoring backup");
Logger.Warn(e); Logger.Warn(e);
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while restoring a backup")); await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while restoring a backup"));
} }*/
} }
private async Task Create() private async Task Create()
{ {
await ToastService.Info(SmartTranslateService.Translate("Started backup creation"));
await ServerService.CreateBackup(CurrentServer);
await LazyLoader.Reload();
/*
try try
{ {
await ToastService.Info(SmartTranslateService.Translate("Started backup creation"));
var backup = await ServerService.CreateBackup(CurrentServer);
/*
// Modify the backup list so no reload needed. Also the create event will work // Modify the backup list so no reload needed. Also the create event will work
var list = AllBackups!.ToList(); //var list = AllBackups!.ToList();
list.Add(backup); //list.Add(backup);
AllBackups = list.ToArray(); //AllBackups = list.ToArray();
*/
await LazyLoader.Reload();
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warn("Error creating backup"); Logger.Warn("Error creating backup");
Logger.Warn(e); Logger.Warn(e);
await ToastService.Error(SmartTranslateService.Translate("An unknown error has occured while creating a backup")); await ToastService.Error(SmartTranslateService.Translate("An unknown error has occured while creating a backup"));
} }*/
} }
private async Task OnContextMenuClick(ItemClickEventArgs args) private async Task OnContextMenuClick(ItemClickEventArgs args)
@@ -300,11 +295,11 @@
await Refresh(LazyLoader); await Refresh(LazyLoader);
} }
public void Dispose() public async void Dispose()
{ {
MessageService.Unsubscribe("wings.backups.create", this); await Event.Off("wings.backups.create", this);
MessageService.Unsubscribe("wings.backups.createfailed", this); await Event.Off("wings.backups.createFailed", this);
MessageService.Unsubscribe("wings.backups.restore", this); await Event.Off("wings.backups.restore", this);
MessageService.Unsubscribe("wings.backups.delete", this); await Event.Off("wings.backups.delete", this);
} }
} }

View File

@@ -55,7 +55,16 @@
</div> </div>
<div class="col fs-5"> <div class="col fs-5">
<span class="fw-bold"><TL>Server ID</TL>:</span> <span class="fw-bold"><TL>Server ID</TL>:</span>
<span class="ms-1 text-muted">@(CurrentServer.Id)</span> <span class="ms-1 text-muted">
@if (User.Admin)
{
<a href="/admin/servers/edit/@(CurrentServer.Id)">@(CurrentServer.Id)</a>
}
else
{
@(CurrentServer.Id)
}
</span>
</div> </div>
<div class="col fs-5"> <div class="col fs-5">
<span class="fw-bold"><TL>Status</TL>:</span> <span class="fw-bold"><TL>Status</TL>:</span>
@@ -164,6 +173,9 @@
{ {
[CascadingParameter] [CascadingParameter]
public Server CurrentServer { get; set; } public Server CurrentServer { get; set; }
[CascadingParameter]
public User User { get; set; }
[CascadingParameter] [CascadingParameter]
public PteroConsole Console { get; set; } public PteroConsole Console { get; set; }

View File

@@ -37,6 +37,12 @@
if(Tags.Contains("paperversion")) if(Tags.Contains("paperversion"))
Settings.Add("Paper version", typeof(PaperVersionSetting)); Settings.Add("Paper version", typeof(PaperVersionSetting));
if(Tags.Contains("forgeversion"))
Settings.Add("Forge version", typeof(ForgeVersionSetting));
if(Tags.Contains("fabricversion"))
Settings.Add("Fabric version", typeof(FabricVersionSetting));
if(Tags.Contains("join2start")) if(Tags.Contains("join2start"))
Settings.Add("Join2Start", typeof(Join2StartSetting)); Settings.Add("Join2Start", typeof(Join2StartSetting));

View File

@@ -0,0 +1,173 @@
@using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@using Moonlight.App.Helpers
@inject ServerService ServerService
@inject ServerRepository ServerRepository
@inject ImageRepository ImageRepository
@inject FabricService FabricService
@inject SmartTranslateService TranslationService
<div class="col">
<div class="card card-body">
<LazyLoader Load="Load">
<label class="mb-2 form-label">
<TL>Fabric version</TL>
</label>
<input class="mb-2 form-control" disabled="" value="@(FabricVersion)"/>
<label class="mb-2 form-label">
<TL>Fabric loader version</TL>
</label>
<input class="mb-2 form-control" disabled="" value="@(LoaderVersion)"/>
<label class="mb-2 form-label">
<TL>Minecraft version</TL>
</label>
<select class="mb-2 form-select" @bind="CurrentVersion">
@foreach (var version in Versions)
{
if (version == CurrentVersion)
{
<option value="@(version)" selected="">@(version)</option>
}
else
{
<option value="@(version)">@(version)</option>
}
}
</select>
<WButton
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary">
</WButton>
</LazyLoader>
</div>
</div>
@code
{
[CascadingParameter]
public Server CurrentServer { get; set; }
private string[] Versions = Array.Empty<string>();
private string CurrentVersion = "";
private string FabricVersion = "";
private string LoaderVersion = "";
private async Task Load(LazyLoader lazyLoader)
{
// Fabric version
var fabricVersion = LiveMigrateVar(
"FABRIC_VERSION",
await FabricService.GetLatestInstallerVersion(),
true
);
FabricVersion = fabricVersion.Value;
// Fabric loader version
var loaderVersion = LiveMigrateVar(
"LOADER_VERSION",
await FabricService.GetLatestLoaderVersion(),
true
);
LoaderVersion = loaderVersion.Value;
// Minecraft versions
Versions = await FabricService.GetGameVersions();
var mcVersion = LiveMigrateVar(
"MC_VERSION",
Versions.First()
);
CurrentVersion = mcVersion.Value;
await InvokeAsync(StateHasChanged);
}
private ServerVariable LiveMigrateVar(string key, string value, bool overwrite = false)
{
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == key);
if (v == null)
{
CurrentServer.Variables.Add(new()
{
Key = key,
Value = value
});
ServerRepository.Update(CurrentServer);
return CurrentServer.Variables.First(x => x.Key == key);
}
else
{
if (string.IsNullOrEmpty(v.Value) || overwrite)
{
v.Value = value;
ServerRepository.Update(CurrentServer);
}
return v;
}
}
private async Task Save()
{
var vars = CurrentServer.Variables;
var versionVar = vars.First(x => x.Key == "MC_VERSION");
versionVar.Value = CurrentVersion;
// This searches for the display name of a version using the constructed full version
var version = ParseHelper.MinecraftToInt(CurrentVersion);
var serverImage = ImageRepository
.Get()
.Include(x => x.DockerImages)
.First(x => x.Id == CurrentServer.Image.Id);
var dockerImages = serverImage.DockerImages;
var dockerImageToUpdate = dockerImages.Last();
if (version < 1130)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("8"));
}
if (version >= 1130)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("11"));
}
if (version >= 1170)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("16"));
}
if (version >= 1190)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("17"));
}
CurrentServer.DockerImageIndex = dockerImages.IndexOf(dockerImageToUpdate);
ServerRepository.Update(CurrentServer);
await ServerService.Reinstall(CurrentServer);
}
}

View File

@@ -0,0 +1,147 @@
@using Moonlight.App.Services
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@using Logging.Net
@using Moonlight.App.Helpers
@inject ServerService ServerService
@inject ServerRepository ServerRepository
@inject ImageRepository ImageRepository
@inject ForgeService ForgeService
@inject SmartTranslateService TranslationService
<div class="col">
<div class="card card-body">
<LazyLoader Load="Load">
<label class="mb-2 form-label"><TL>Forge version</TL></label>
<select class="mb-2 form-select" @bind="CurrentVersion">
@foreach (var version in Versions.Keys)
{
if (DisplayToData(version) == CurrentVersion)
{
<option value="@(DisplayToData(version))" selected="">@(version)</option>
}
else
{
<option value="@(DisplayToData(version))">@(version)</option>
}
}
</select>
<WButton
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary">
</WButton>
</LazyLoader>
</div>
</div>
@code
{
[CascadingParameter]
public Server CurrentServer { get; set; }
private Dictionary<string, string> Versions = new();
private string CurrentVersion = "";
private async Task Load(LazyLoader lazyLoader)
{
Versions = await ForgeService.GetVersions();
var vars = CurrentServer.Variables;
var versionVar = vars.FirstOrDefault(x => x.Key == "FORGE_VERSION");
// Live migration
if (versionVar == null)
{
CurrentServer.Variables.Add(new ()
{
Key = "FORGE_VERSION",
Value = LatestVersion()
});
ServerRepository.Update(CurrentServer);
versionVar = vars.First(x => x.Key == "FORGE_VERSION");
}
else
{
if (string.IsNullOrEmpty(versionVar.Value))
{
versionVar.Value = LatestVersion();
ServerRepository.Update(CurrentServer);
}
}
CurrentVersion = versionVar.Value;
await InvokeAsync(StateHasChanged);
}
private string DisplayToData(string display)
{
return display
.Replace("-recommended", "")
.Replace("-latest", "") + "-" + Versions[display];
}
private string LatestVersion()
{
var versionsSorted = Versions.Keys
.OrderByDescending(ParseHelper.MinecraftToInt);
return DisplayToData(versionsSorted.First());
}
private async Task Save()
{
var vars = CurrentServer.Variables;
var versionVar = vars.First(x => x.Key == "FORGE_VERSION");
versionVar.Value = CurrentVersion;
// This searches for the display name of a version using the constructed full version
var version = ParseHelper.MinecraftToInt(
Versions.First(
x => DisplayToData(x.Key) == CurrentVersion).Key);
var serverImage = ImageRepository
.Get()
.Include(x => x.DockerImages)
.First(x => x.Id == CurrentServer.Image.Id);
var dockerImages = serverImage.DockerImages;
var dockerImageToUpdate = dockerImages.Last();
if (version < 1130)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("8"));
}
if (version >= 1130)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("11"));
}
if (version >= 1170)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("16"));
}
if (version >= 1190)
{
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("17"));
}
CurrentServer.DockerImageIndex = dockerImages.IndexOf(dockerImageToUpdate);
ServerRepository.Update(CurrentServer);
await ServerService.Reinstall(CurrentServer);
}
}

View File

@@ -0,0 +1,60 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject WebSpaceService WebSpaceService
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
<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://@(CurrentWebSpace.Domain)" 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="IssueCertificate">
</WButton>
</div>
</div>
</div>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public WebSpace CurrentWebSpace { get; set; }
private LazyLoader LazyLoader;
private Task Load(LazyLoader lazyLoader)
{
return Task.CompletedTask;
}
private async Task IssueCertificate()
{
await WebSpaceService.IssueSslCertificate(CurrentWebSpace);
await AlertService.Success(SmartTranslateService.Translate("Lets Encrypt certificate successfully issued"));
}
}

View File

@@ -4,12 +4,12 @@
@using Moonlight.App.Services @using Moonlight.App.Services
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
@inject WebsiteService WebsiteService @inject WebSpaceService WebSpaceService
<LazyLoader @ref="LazyLoader" Load="Load"> <LazyLoader @ref="LazyLoader" Load="Load">
<div class="card w-100 mb-4"> <div class="card w-100 mb-4">
<div class="card-body py-2"> <div class="card-body py-2">
<div class="my-4 w-100"> <div class="my-4 w-100">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit"> <SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<div class="input-group"> <div class="input-group">
@@ -34,7 +34,7 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<div class="card-title"> <div class="card-title">
<span>@(database.Name) - @(database.Type.ToUpper().Replace("MYSQL", "MySQL"))</span> <span>@(database.UserName) - @(database.UserName.ToUpper().Replace("MYSQL", "MySQL"))</span>
</div> </div>
</div> </div>
<div class="card-body me-1"> <div class="card-body me-1">
@@ -46,7 +46,7 @@
</label> </label>
</td> </td>
<td class="pb-2"> <td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Host)"> <input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.CloudPanel.Host)">
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -56,7 +56,7 @@
</label> </label>
</td> </td>
<td class="pb-2"> <td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(DatabaseServer.Port)"> <input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="3306">
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -66,7 +66,17 @@
</label> </label>
</td> </td>
<td class="pb-2"> <td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.Name)"> <input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.UserName)">
</td>
</tr>
<tr>
<td>
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Password</TL>
</label>
</td>
<td class="pb-2">
<input type="text" class="form-control form-control-solid disabled blur-unless-hover" disabled="disabled" value="@(database.Password)">
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -76,7 +86,7 @@
</label> </label>
</td> </td>
<td class="pb-2"> <td class="pb-2">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.Name)"> <input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.UserName)">
</td> </td>
</tr> </tr>
</table> </table>
@@ -93,7 +103,7 @@
else else
{ {
<div class="alert alert-warning"> <div class="alert alert-warning">
<TL>No databases found for this website</TL> <TL>No databases found for this webspace</TL>
</div> </div>
} }
</LazyLoader> </LazyLoader>
@@ -101,36 +111,28 @@
@code @code
{ {
[CascadingParameter] [CascadingParameter]
public Website CurrentWebsite { get; set; } public WebSpace CurrentWebSpace { get; set; }
private LazyLoader LazyLoader; private LazyLoader LazyLoader;
private Database[] Databases; private MySqlDatabase[] Databases;
private DatabaseServer DatabaseServer;
private string Host;
private DatabaseDataModel Model = new(); private DatabaseDataModel Model = new();
private async Task Load(LazyLoader arg) private async Task Load(LazyLoader arg)
{ {
Databases = await WebsiteService.GetDatabases(CurrentWebsite); Databases = await WebSpaceService.GetDatabases(CurrentWebSpace);
if (Databases.Any())
{
DatabaseServer = (await WebsiteService.GetDefaultDatabaseServer(CurrentWebsite))!;
Host = await WebsiteService.GetHost(CurrentWebsite);
}
} }
private async Task OnValidSubmit() private async Task OnValidSubmit()
{ {
await WebsiteService.CreateDatabase(CurrentWebsite, Model.Name, Model.Password); await WebSpaceService.CreateDatabase(CurrentWebSpace, Model.Name, Model.Password);
Model = new(); Model = new();
await LazyLoader.Reload(); await LazyLoader.Reload();
} }
private async Task DeleteDatabase(Database database) private async Task DeleteDatabase(MySqlDatabase database)
{ {
await WebsiteService.DeleteDatabase(CurrentWebsite, database); await WebSpaceService.DeleteDatabase(CurrentWebSpace, database);
await LazyLoader.Reload(); await LazyLoader.Reload();
} }
} }

View File

@@ -3,7 +3,7 @@
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.Shared.Components.FileManagerPartials @using Moonlight.Shared.Components.FileManagerPartials
@inject WebsiteService WebsiteService @inject WebSpaceService WebSpaceService
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<FileManager Access="Access"> <FileManager Access="Access">
@@ -13,12 +13,12 @@
@code @code
{ {
[CascadingParameter] [CascadingParameter]
public Website CurrentWebsite { get; set; } public WebSpace CurrentWebSpace { get; set; }
private FileAccess Access; private FileAccess Access;
private async Task Load(LazyLoader arg) private async Task Load(LazyLoader arg)
{ {
Access = await WebsiteService.CreateFileAccess(CurrentWebsite); Access = await WebSpaceService.CreateFileAccess(CurrentWebSpace);
} }
} }

Some files were not shown because too many files have changed in this diff Show More