@@ -1,83 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
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; } = "";
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.ApiClients.CloudPanel.Requests;
|
||||
|
||||
public class InstallLetsEncrypt
|
||||
{
|
||||
[JsonProperty("domainName")]
|
||||
public string DomainName { get; set; }
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database.Entities.LogsEntries;
|
||||
using Moonlight.App.Database.Entities.Notification;
|
||||
using Moonlight.App.Database.Interceptors;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
@@ -30,6 +29,7 @@ public class DataContext : DbContext
|
||||
public DbSet<AuditLogEntry> AuditLog { get; set; }
|
||||
public DbSet<ErrorLogEntry> ErrorLog { get; set; }
|
||||
public DbSet<SecurityLogEntry> SecurityLog { get; set; }
|
||||
public DbSet<SupportMessage> SupportMessages { get; set; }
|
||||
|
||||
public DbSet<SharedDomain> SharedDomains { get; set; }
|
||||
public DbSet<Domain> Domains { get; set; }
|
||||
@@ -38,14 +38,11 @@ public class DataContext : DbContext
|
||||
public DbSet<NotificationAction> NotificationActions { get; set; }
|
||||
public DbSet<DdosAttack> DdosAttacks { 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<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)
|
||||
{
|
||||
if (!optionsBuilder.IsConfigured)
|
||||
@@ -68,9 +65,6 @@ public class DataContext : DbContext
|
||||
builder.EnableRetryOnFailure(5);
|
||||
}
|
||||
);
|
||||
|
||||
if(ConfigService.SqlDebugMode)
|
||||
optionsBuilder.AddInterceptors(new SqlLoggingInterceptor());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class DockerImage
|
||||
{
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
public bool Default { get; set; } = false;
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class Image
|
||||
{
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
public Guid Uuid { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class ImageVariable
|
||||
{
|
||||
[JsonIgnore]
|
||||
public int Id { get; set; }
|
||||
public string Key { get; set; } = "";
|
||||
public string DefaultValue { get; set; } = "";
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
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; } = "";
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class CloudPanel
|
||||
public class PleskServer
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public string ApiUrl { get; set; } = "";
|
||||
public string ApiKey { get; set; } = "";
|
||||
public string Host { get; set; } = "";
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
17
Moonlight/App/Database/Entities/SupportMessage.cs
Normal file
17
Moonlight/App/Database/Entities/SupportMessage.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Moonlight.App.Models.Misc;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.App.Models.Misc;
|
||||
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
@@ -33,7 +34,7 @@ public class User
|
||||
// Security
|
||||
public bool TotpEnabled { get; set; } = false;
|
||||
public string TotpSecret { get; set; } = "";
|
||||
public DateTime TokenValidTime { get; set; } = DateTime.UtcNow;
|
||||
public DateTime TokenValidTime { get; set; } = DateTime.Now;
|
||||
|
||||
// Discord
|
||||
public ulong DiscordId { get; set; }
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
12
Moonlight/App/Database/Entities/Website.cs
Normal file
12
Moonlight/App/Database/Entities/Website.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class Website
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string BaseDomain { get; set; } = "";
|
||||
public int PleskId { get; set; }
|
||||
public PleskServer PleskServer { get; set; }
|
||||
public User Owner { get; set; }
|
||||
public string FtpLogin { get; set; } = "";
|
||||
public string FtpPassword { get; set; } = "";
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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
@@ -1,121 +0,0 @@
|
||||
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
@@ -1,29 +0,0 @@
|
||||
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
@@ -1,138 +0,0 @@
|
||||
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
@@ -1,67 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,33 +19,6 @@ namespace Moonlight.App.Database.Migrations
|
||||
.HasAnnotation("ProductVersion", "7.0.3")
|
||||
.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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -308,30 +281,6 @@ namespace Moonlight.App.Database.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -453,6 +402,29 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.ToTable("NotificationClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.PleskServer", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ApiKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("ApiUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PleskServers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -650,7 +622,7 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.ToTable("Subscriptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b =>
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -660,31 +632,30 @@ namespace Moonlight.App.Database.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Attachment")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("IsQuestion")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("QuestionType")
|
||||
.HasColumnType("int");
|
||||
b.Property<bool>("IsSupport")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<int>("RecipientId")
|
||||
b.Property<bool>("IsSystem")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int?>("RecipientId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("SenderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -692,7 +663,7 @@ namespace Moonlight.App.Database.Migrations
|
||||
|
||||
b.HasIndex("SenderId");
|
||||
|
||||
b.ToTable("SupportChatMessages");
|
||||
b.ToTable("SupportMessages");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
|
||||
@@ -777,41 +748,40 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("CloudPanelId")
|
||||
.HasColumnType("int");
|
||||
b.Property<string>("BaseDomain")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Domain")
|
||||
b.Property<string>("FtpLogin")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("FtpPassword")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
b.Property<int>("PleskId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("VHostTemplate")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
b.Property<int>("PleskServerId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CloudPanelId");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.ToTable("WebSpaces");
|
||||
b.HasIndex("PleskServerId");
|
||||
|
||||
b.ToTable("Websites");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
||||
@@ -858,17 +828,6 @@ namespace Moonlight.App.Database.Migrations
|
||||
.HasForeignKey("ImageId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace")
|
||||
.WithMany("Databases")
|
||||
.HasForeignKey("WebSpaceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("WebSpace");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.Node", null)
|
||||
@@ -949,13 +908,11 @@ namespace Moonlight.App.Database.Migrations
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b =>
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.HasForeignKey("RecipientId");
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Sender")
|
||||
.WithMany()
|
||||
@@ -975,23 +932,23 @@ namespace Moonlight.App.Database.Migrations
|
||||
b.Navigation("CurrentSubscription");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
|
||||
{
|
||||
b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel")
|
||||
.WithMany()
|
||||
.HasForeignKey("CloudPanelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CloudPanel");
|
||||
b.HasOne("Moonlight.App.Database.Entities.PleskServer", "PleskServer")
|
||||
.WithMany()
|
||||
.HasForeignKey("PleskServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Owner");
|
||||
|
||||
b.Navigation("PleskServer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
|
||||
@@ -1014,11 +971,6 @@ namespace Moonlight.App.Database.Migrations
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
||||
{
|
||||
b.Navigation("Databases");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Moonlight.App.Events;
|
||||
|
||||
public class Subscriber
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public object Action { get; set; }
|
||||
public object Handle { get; set; }
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
|
||||
[Serializable]
|
||||
public class DaemonException : Exception
|
||||
{
|
||||
public int StatusCode { get; set; }
|
||||
public int StatusCode { private get; set; }
|
||||
|
||||
public DaemonException()
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
|
||||
[Serializable]
|
||||
public class PleskException : Exception
|
||||
{
|
||||
public int StatusCode { get; set; }
|
||||
public int StatusCode { private get; set; }
|
||||
|
||||
public PleskException()
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
|
||||
[Serializable]
|
||||
public class WingsException : Exception
|
||||
{
|
||||
public int StatusCode { get; set; }
|
||||
public int StatusCode { private get; set; }
|
||||
|
||||
public WingsException()
|
||||
{
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,15 @@
|
||||
using Logging.Net;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public static class ParseHelper
|
||||
{
|
||||
public static int MinecraftToInt(string raw)
|
||||
{
|
||||
var versionWithoutPre = raw.Split("_")[0];
|
||||
versionWithoutPre = versionWithoutPre.Split("-")[0];
|
||||
|
||||
// Fuck you 1.7.10 ;)
|
||||
versionWithoutPre = versionWithoutPre.Replace("1.7.10", "1.7");
|
||||
var versionWithoutPre = raw.Split("-")[0];
|
||||
|
||||
if (versionWithoutPre.Count(x => x == "."[0]) == 1)
|
||||
versionWithoutPre += ".0";
|
||||
|
||||
var x = versionWithoutPre.Replace(".", "");
|
||||
|
||||
return int.Parse(x);
|
||||
return int.Parse(versionWithoutPre.Replace(".", ""));
|
||||
}
|
||||
|
||||
public static string FirstPartStartingWithNumber(string raw)
|
||||
@@ -37,61 +29,4 @@ public static class ParseHelper
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
220
Moonlight/App/Helpers/PleskApiHelper.cs
Normal file
220
Moonlight/App/Helpers/PleskApiHelper.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System.Text;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class PleskApiHelper
|
||||
{
|
||||
private readonly RestClient Client;
|
||||
|
||||
public PleskApiHelper()
|
||||
{
|
||||
Client = new();
|
||||
}
|
||||
|
||||
public async Task<T> Get<T>(PleskServer server, string resource)
|
||||
{
|
||||
var request = CreateRequest(server, resource);
|
||||
|
||||
request.Method = Method.Get;
|
||||
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new PleskException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(response.Content!)!;
|
||||
}
|
||||
|
||||
public async Task<string> GetRaw(PleskServer server, string resource)
|
||||
{
|
||||
var request = CreateRequest(server, resource);
|
||||
|
||||
request.Method = Method.Get;
|
||||
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new PleskException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return response.Content!;
|
||||
}
|
||||
|
||||
public async Task<T> Post<T>(PleskServer server, string resource, object? body)
|
||||
{
|
||||
var request = CreateRequest(server, resource);
|
||||
|
||||
request.Method = Method.Post;
|
||||
|
||||
request.AddParameter("text/plain",
|
||||
JsonConvert.SerializeObject(body),
|
||||
ParameterType.RequestBody
|
||||
);
|
||||
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new PleskException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject<T>(response.Content!)!;
|
||||
}
|
||||
|
||||
public async Task Post(PleskServer server, string resource, object? body)
|
||||
{
|
||||
var request = CreateRequest(server, resource);
|
||||
|
||||
request.Method = Method.Post;
|
||||
|
||||
if(body != null)
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new PleskException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PostRaw(PleskServer server, string resource, object body)
|
||||
{
|
||||
var request = CreateRequest(server, resource);
|
||||
|
||||
request.Method = Method.Post;
|
||||
|
||||
request.AddParameter("text/plain", body, ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new PleskException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Delete(PleskServer server, string resource, object? body)
|
||||
{
|
||||
var request = CreateRequest(server, resource);
|
||||
|
||||
request.Method = Method.Delete;
|
||||
|
||||
if(body != null)
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new PleskException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Put(PleskServer server, string resource, object? body)
|
||||
{
|
||||
var request = CreateRequest(server, resource);
|
||||
|
||||
request.Method = Method.Put;
|
||||
|
||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||
|
||||
var response = await Client.ExecuteAsync(request);
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
if (response.StatusCode != 0)
|
||||
{
|
||||
throw new PleskException(
|
||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||
(int)response.StatusCode
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RestRequest CreateRequest(PleskServer pleskServer, string resource)
|
||||
{
|
||||
var url = $"{pleskServer.ApiUrl}/" + resource;
|
||||
|
||||
var request = new RestRequest(url);
|
||||
var ba = Convert.ToBase64String(Encoding.UTF8.GetBytes(pleskServer.ApiKey));
|
||||
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
request.AddHeader("Accept", "application/json");
|
||||
request.AddHeader("Authorization", "Basic " + ba);
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -18,19 +18,17 @@ public class OAuth2Controller : Controller
|
||||
private readonly DiscordOAuth2Service DiscordOAuth2Service;
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly UserService UserService;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
|
||||
public OAuth2Controller(
|
||||
GoogleOAuth2Service googleOAuth2Service,
|
||||
UserRepository userRepository,
|
||||
UserService userService,
|
||||
DiscordOAuth2Service discordOAuth2Service, DateTimeService dateTimeService)
|
||||
DiscordOAuth2Service discordOAuth2Service)
|
||||
{
|
||||
GoogleOAuth2Service = googleOAuth2Service;
|
||||
UserRepository = userRepository;
|
||||
UserService = userService;
|
||||
DiscordOAuth2Service = discordOAuth2Service;
|
||||
DateTimeService = dateTimeService;
|
||||
}
|
||||
|
||||
[HttpGet("google")]
|
||||
@@ -65,7 +63,7 @@ public class OAuth2Controller : Controller
|
||||
|
||||
Response.Cookies.Append("token", token, new ()
|
||||
{
|
||||
Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
|
||||
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10))
|
||||
});
|
||||
|
||||
return Redirect("/");
|
||||
@@ -123,7 +121,7 @@ public class OAuth2Controller : Controller
|
||||
|
||||
Response.Cookies.Append("token", token, new ()
|
||||
{
|
||||
Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
|
||||
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10))
|
||||
});
|
||||
|
||||
return Redirect("/");
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Http.Requests.Wings;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
@@ -12,17 +11,17 @@ namespace Moonlight.App.Http.Controllers.Api.Remote;
|
||||
public class BackupController : Controller
|
||||
{
|
||||
private readonly ServerBackupRepository ServerBackupRepository;
|
||||
private readonly EventSystem Event;
|
||||
private readonly MessageService MessageService;
|
||||
private readonly NodeRepository NodeRepository;
|
||||
|
||||
public BackupController(
|
||||
ServerBackupRepository serverBackupRepository,
|
||||
NodeRepository nodeRepository,
|
||||
EventSystem eventSystem)
|
||||
MessageService messageService)
|
||||
{
|
||||
ServerBackupRepository = serverBackupRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
Event = eventSystem;
|
||||
MessageService = messageService;
|
||||
}
|
||||
|
||||
[HttpGet("{uuid}")]
|
||||
@@ -58,11 +57,11 @@ public class BackupController : Controller
|
||||
|
||||
ServerBackupRepository.Update(backup);
|
||||
|
||||
await Event.Emit($"wings.backups.create", backup);
|
||||
await MessageService.Emit($"wings.backups.create", backup);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Event.Emit($"wings.backups.createFailed", backup);
|
||||
await MessageService.Emit($"wings.backups.createfailed", backup);
|
||||
ServerBackupRepository.Delete(backup);
|
||||
}
|
||||
|
||||
@@ -89,7 +88,7 @@ public class BackupController : Controller
|
||||
if (backup == null)
|
||||
return NotFound();
|
||||
|
||||
await Event.Emit($"wings.backups.restore", backup);
|
||||
await MessageService.Emit($"wings.backups.restore", backup);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Logging.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Http.Requests.Daemon;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services;
|
||||
@@ -13,13 +12,13 @@ namespace Moonlight.App.Http.Controllers.Api.Remote;
|
||||
public class DdosController : Controller
|
||||
{
|
||||
private readonly NodeRepository NodeRepository;
|
||||
private readonly EventSystem Event;
|
||||
private readonly MessageService MessageService;
|
||||
private readonly DdosAttackRepository DdosAttackRepository;
|
||||
|
||||
public DdosController(NodeRepository nodeRepository, EventSystem eventSystem, DdosAttackRepository ddosAttackRepository)
|
||||
public DdosController(NodeRepository nodeRepository, MessageService messageService, DdosAttackRepository ddosAttackRepository)
|
||||
{
|
||||
NodeRepository = nodeRepository;
|
||||
Event = eventSystem;
|
||||
MessageService = messageService;
|
||||
DdosAttackRepository = ddosAttackRepository;
|
||||
}
|
||||
|
||||
@@ -48,7 +47,7 @@ public class DdosController : Controller
|
||||
|
||||
ddosAttack = DdosAttackRepository.Add(ddosAttack);
|
||||
|
||||
await Event.Emit("node.ddos", ddosAttack);
|
||||
await MessageService.Emit("node.ddos", ddosAttack);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Http.Resources.Wings;
|
||||
using Moonlight.App.Repositories;
|
||||
@@ -16,18 +15,18 @@ public class ServersController : Controller
|
||||
private readonly WingsServerConverter Converter;
|
||||
private readonly ServerRepository ServerRepository;
|
||||
private readonly NodeRepository NodeRepository;
|
||||
private readonly EventSystem Event;
|
||||
private readonly MessageService MessageService;
|
||||
|
||||
public ServersController(
|
||||
WingsServerConverter converter,
|
||||
ServerRepository serverRepository,
|
||||
NodeRepository nodeRepository,
|
||||
EventSystem eventSystem)
|
||||
MessageService messageService)
|
||||
{
|
||||
Converter = converter;
|
||||
ServerRepository = serverRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
Event = eventSystem;
|
||||
MessageService = messageService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@@ -69,7 +68,7 @@ public class ServersController : Controller
|
||||
totalPages = slice.Length - 1;
|
||||
}
|
||||
|
||||
await Event.Emit($"wings.{node.Id}.serverList", node);
|
||||
await MessageService.Emit($"wings.{node.Id}.serverlist", node);
|
||||
|
||||
//Logger.Debug($"[BRIDGE] Node '{node.Name}' is requesting server list page {page} with {perPage} items per page");
|
||||
|
||||
@@ -98,7 +97,7 @@ public class ServersController : Controller
|
||||
if (token != node.Token)
|
||||
return Unauthorized();
|
||||
|
||||
await Event.Emit($"wings.{node.Id}.stateReset", node);
|
||||
await MessageService.Emit($"wings.{node.Id}.statereset", node);
|
||||
|
||||
foreach (var server in ServerRepository
|
||||
.Get()
|
||||
@@ -137,7 +136,7 @@ public class ServersController : Controller
|
||||
if (server == null)
|
||||
return NotFound();
|
||||
|
||||
await Event.Emit($"wings.{node.Id}.serverFetch", server);
|
||||
await MessageService.Emit($"wings.{node.Id}.serverfetch", server);
|
||||
|
||||
try //TODO: Remove
|
||||
{
|
||||
@@ -170,7 +169,7 @@ public class ServersController : Controller
|
||||
if (server == null)
|
||||
return NotFound();
|
||||
|
||||
await Event.Emit($"wings.{node.Id}.serverInstallFetch", server);
|
||||
await MessageService.Emit($"wings.{node.Id}.serverinstallfetch", server);
|
||||
|
||||
return new WingsServerInstall()
|
||||
{
|
||||
@@ -203,8 +202,8 @@ public class ServersController : Controller
|
||||
server.Installing = false;
|
||||
ServerRepository.Update(server);
|
||||
|
||||
await Event.Emit($"wings.{node.Id}.serverInstallComplete", server);
|
||||
await Event.Emit($"server.{server.Uuid}.installComplete", server);
|
||||
await MessageService.Emit($"wings.{node.Id}.serverinstallcomplete", server);
|
||||
await MessageService.Emit($"server.{server.Uuid}.installcomplete", server);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
83
Moonlight/App/MessageSystem/MessageSender.cs
Normal file
83
Moonlight/App/MessageSystem/MessageSender.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
9
Moonlight/App/MessageSystem/MessageSubscriber.cs
Normal file
9
Moonlight/App/MessageSystem/MessageSubscriber.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
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; }
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@@ -7,7 +7,7 @@ public class DomainOrderDataModel
|
||||
{
|
||||
[Required(ErrorMessage = "You need to specify a name")]
|
||||
[MaxLength(32, ErrorMessage = "The max lenght for the name is 32 characters")]
|
||||
[RegularExpression(@"^[a-z0-9]+$", ErrorMessage = "The name should only consist of lower case characters or numbers")]
|
||||
[RegularExpression(@"^[a-z]+$", ErrorMessage = "The name should only consist of lower case characters")]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "You need to specify a shared domain")]
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
44
Moonlight/App/Repositories/PleskServerRepository.cs
Normal file
44
Moonlight/App/Repositories/PleskServerRepository.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Repositories;
|
||||
|
||||
public class PleskServerRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public PleskServerRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public DbSet<PleskServer> Get()
|
||||
{
|
||||
return DataContext.PleskServers;
|
||||
}
|
||||
|
||||
public PleskServer Add(PleskServer pleskServer)
|
||||
{
|
||||
var x = DataContext.PleskServers.Add(pleskServer);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public void Update(PleskServer pleskServer)
|
||||
{
|
||||
DataContext.PleskServers.Update(pleskServer);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(PleskServer pleskServer)
|
||||
{
|
||||
DataContext.PleskServers.Remove(pleskServer);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Services;
|
||||
|
||||
namespace Moonlight.App.Repositories;
|
||||
|
||||
public class StatisticsRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
|
||||
public StatisticsRepository(DataContext dataContext, DateTimeService dateTimeService)
|
||||
public StatisticsRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
DateTimeService = dateTimeService;
|
||||
}
|
||||
|
||||
public DbSet<StatisticsData> Get()
|
||||
@@ -30,7 +27,7 @@ public class StatisticsRepository : IDisposable
|
||||
|
||||
public StatisticsData Add(string chart, double value)
|
||||
{
|
||||
return Add(new StatisticsData() {Chart = chart, Value = value, Date = DateTimeService.GetCurrent()});
|
||||
return Add(new StatisticsData() {Chart = chart, Value = value, Date = DateTime.Now});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
44
Moonlight/App/Repositories/SupportMessageRepository.cs
Normal file
44
Moonlight/App/Repositories/SupportMessageRepository.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
44
Moonlight/App/Repositories/WebsiteRepository.cs
Normal file
44
Moonlight/App/Repositories/WebsiteRepository.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Repositories;
|
||||
|
||||
public class WebsiteRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public WebsiteRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public DbSet<Website> Get()
|
||||
{
|
||||
return DataContext.Websites;
|
||||
}
|
||||
|
||||
public Website Add(Website website)
|
||||
{
|
||||
var x = DataContext.Websites.Add(website);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public void Update(Website website)
|
||||
{
|
||||
DataContext.Websites.Update(website);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(Website website)
|
||||
{
|
||||
DataContext.Websites.Remove(website);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Moonlight.App.Models.Wings;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Repositories.Servers;
|
||||
using Logging.Net;
|
||||
using Moonlight.App.Events;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
@@ -24,24 +23,21 @@ public class CleanupService
|
||||
#endregion
|
||||
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
private readonly EventSystem Event;
|
||||
private readonly MessageService MessageService;
|
||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||
private readonly PeriodicTimer Timer;
|
||||
|
||||
public CleanupService(
|
||||
ConfigService configService,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
DateTimeService dateTimeService,
|
||||
EventSystem eventSystem)
|
||||
MessageService messageService)
|
||||
{
|
||||
ServiceScopeFactory = serviceScopeFactory;
|
||||
DateTimeService = dateTimeService;
|
||||
MessageService = messageService;
|
||||
ConfigService = configService;
|
||||
Event = eventSystem;
|
||||
|
||||
StartedAt = DateTimeService.GetCurrent();
|
||||
CompletedAt = DateTimeService.GetCurrent();
|
||||
StartedAt = DateTime.Now;
|
||||
CompletedAt = DateTime.Now;
|
||||
IsRunning = false;
|
||||
|
||||
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
||||
@@ -149,7 +145,7 @@ public class CleanupService
|
||||
ServersRunning++;
|
||||
}
|
||||
|
||||
await Event.Emit("cleanup.updated");
|
||||
await MessageService.Emit("cleanup.updated", null);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -179,7 +175,7 @@ public class CleanupService
|
||||
ServersRunning++;
|
||||
}
|
||||
|
||||
await Event.Emit("cleanup.updated");
|
||||
await MessageService.Emit("cleanup.updated", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +196,7 @@ public class CleanupService
|
||||
|
||||
IsRunning = false;
|
||||
CleanupsPerformed++;
|
||||
await Event.Emit("cleanup.updated");
|
||||
await MessageService.Emit("cleanup.updated", null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ public class ConfigService : IConfiguration
|
||||
private IConfiguration Configuration;
|
||||
|
||||
public bool DebugMode { get; private set; } = false;
|
||||
public bool SqlDebugMode { get; private set; } = false;
|
||||
|
||||
public ConfigService(StorageService storageService)
|
||||
{
|
||||
@@ -29,14 +28,6 @@ public class ConfigService : IConfiguration
|
||||
|
||||
if (DebugMode)
|
||||
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()
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,13 @@ public class ClipboardService
|
||||
JsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task CopyToClipboard(string data)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data);
|
||||
}
|
||||
public async Task Copy(string data)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.clipboard.copy", data);
|
||||
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,36 +13,36 @@ public class ToastService
|
||||
|
||||
public async Task Info(string message)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.toasts.info", message);
|
||||
await JsRuntime.InvokeVoidAsync("showInfoToast", message);
|
||||
}
|
||||
|
||||
public async Task Error(string message)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.toasts.error", message);
|
||||
await JsRuntime.InvokeVoidAsync("showErrorToast", message);
|
||||
}
|
||||
|
||||
public async Task Warning(string message)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.toasts.warning", message);
|
||||
await JsRuntime.InvokeVoidAsync("showWarningToast", message);
|
||||
}
|
||||
|
||||
public async Task Success(string message)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.toasts.success", message);
|
||||
await JsRuntime.InvokeVoidAsync("showSuccessToast", message);
|
||||
}
|
||||
|
||||
public async Task CreateProcessToast(string id, string text)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.toasts.create", id, text);
|
||||
await JsRuntime.InvokeVoidAsync("createToast", id, text);
|
||||
}
|
||||
|
||||
public async Task UpdateProcessToast(string id, string text)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.toasts.modify", id, text);
|
||||
await JsRuntime.InvokeVoidAsync("modifyToast", id, text);
|
||||
}
|
||||
|
||||
public async Task RemoveProcessToast(string id)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.toasts.remove", id);
|
||||
await JsRuntime.InvokeVoidAsync("removeToast", id);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
using System.Net;
|
||||
using System.Net.Mail;
|
||||
using Logging.Net;
|
||||
using MimeKit;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
@@ -15,7 +13,6 @@ public class MailService
|
||||
private readonly string Password;
|
||||
private readonly string Email;
|
||||
private readonly int Port;
|
||||
private readonly bool Ssl;
|
||||
|
||||
public MailService(ConfigService configService)
|
||||
{
|
||||
@@ -27,7 +24,6 @@ public class MailService
|
||||
Password = mailConfig.GetValue<string>("Password");
|
||||
Email = mailConfig.GetValue<string>("Email");
|
||||
Port = mailConfig.GetValue<int>("Port");
|
||||
Ssl = mailConfig.GetValue<bool>("Ssl");
|
||||
}
|
||||
|
||||
public async Task SendMail(
|
||||
@@ -58,24 +54,20 @@ public class MailService
|
||||
{
|
||||
using var client = new SmtpClient();
|
||||
|
||||
var mailMessage = new MimeMessage();
|
||||
mailMessage.From.Add(new MailboxAddress(Email, Email));
|
||||
mailMessage.To.Add(new MailboxAddress(user.Email, user.Email));
|
||||
mailMessage.Subject = $"Hey {user.FirstName}, there are news from moonlight";
|
||||
client.Host = Server;
|
||||
client.Port = Port;
|
||||
client.EnableSsl = true;
|
||||
client.Credentials = new NetworkCredential(Email, Password);
|
||||
|
||||
var body = new BodyBuilder
|
||||
await client.SendMailAsync(new MailMessage()
|
||||
{
|
||||
HtmlBody = parsed
|
||||
};
|
||||
mailMessage.Body = body.ToMessageBody();
|
||||
|
||||
using (var smtpClient = new SmtpClient())
|
||||
{
|
||||
await smtpClient.ConnectAsync(Server, Port, Ssl);
|
||||
await smtpClient.AuthenticateAsync(Email, Password);
|
||||
await smtpClient.SendAsync(mailMessage);
|
||||
await smtpClient.DisconnectAsync(true);
|
||||
}
|
||||
From = new MailAddress(Email),
|
||||
Sender = new MailAddress(Email),
|
||||
Body = parsed,
|
||||
IsBodyHtml = true,
|
||||
Subject = $"Hey {user.FirstName}, there are news from moonlight",
|
||||
To = { new MailAddress(user.Email) }
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
11
Moonlight/App/Services/MessageService.cs
Normal file
11
Moonlight/App/Services/MessageService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Moonlight.App.MessageSystem;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class MessageService : MessageSender
|
||||
{
|
||||
public MessageService()
|
||||
{
|
||||
Debug = false;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Helpers.Files;
|
||||
@@ -22,8 +21,8 @@ public class ServerService
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly ImageRepository ImageRepository;
|
||||
private readonly NodeRepository NodeRepository;
|
||||
private readonly NodeAllocationRepository NodeAllocationRepository;
|
||||
private readonly WingsApiHelper WingsApiHelper;
|
||||
private readonly MessageService MessageService;
|
||||
private readonly UserService UserService;
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly WingsJwtHelper WingsJwtHelper;
|
||||
@@ -31,8 +30,6 @@ public class ServerService
|
||||
private readonly AuditLogService AuditLogService;
|
||||
private readonly ErrorLogService ErrorLogService;
|
||||
private readonly NodeService NodeService;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
private readonly EventSystem Event;
|
||||
|
||||
public ServerService(
|
||||
ServerRepository serverRepository,
|
||||
@@ -40,22 +37,21 @@ public class ServerService
|
||||
UserRepository userRepository,
|
||||
ImageRepository imageRepository,
|
||||
NodeRepository nodeRepository,
|
||||
MessageService messageService,
|
||||
UserService userService,
|
||||
ConfigService configService,
|
||||
WingsJwtHelper wingsJwtHelper,
|
||||
SecurityLogService securityLogService,
|
||||
AuditLogService auditLogService,
|
||||
ErrorLogService errorLogService,
|
||||
NodeService nodeService,
|
||||
NodeAllocationRepository nodeAllocationRepository,
|
||||
DateTimeService dateTimeService,
|
||||
EventSystem eventSystem)
|
||||
NodeService nodeService)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
WingsApiHelper = wingsApiHelper;
|
||||
UserRepository = userRepository;
|
||||
ImageRepository = imageRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
MessageService = messageService;
|
||||
UserService = userService;
|
||||
ConfigService = configService;
|
||||
WingsJwtHelper = wingsJwtHelper;
|
||||
@@ -63,9 +59,6 @@ public class ServerService
|
||||
AuditLogService = auditLogService;
|
||||
ErrorLogService = errorLogService;
|
||||
NodeService = nodeService;
|
||||
NodeAllocationRepository = nodeAllocationRepository;
|
||||
DateTimeService = dateTimeService;
|
||||
Event = eventSystem;
|
||||
}
|
||||
|
||||
private Server EnsureNodeData(Server s)
|
||||
@@ -119,9 +112,9 @@ public class ServerService
|
||||
|
||||
var backup = new ServerBackup()
|
||||
{
|
||||
Name = $"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
|
||||
Name = $"Created at {DateTime.Now.ToShortDateString()} {DateTime.Now.ToShortTimeString()}",
|
||||
Uuid = Guid.NewGuid(),
|
||||
CreatedAt = DateTimeService.GetCurrent(),
|
||||
CreatedAt = DateTime.Now,
|
||||
Created = false
|
||||
};
|
||||
|
||||
@@ -193,27 +186,15 @@ public class ServerService
|
||||
.Include(x => x.Backups)
|
||||
.First(x => x.Id == server.Id);
|
||||
|
||||
try
|
||||
{
|
||||
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);
|
||||
serverData.Backups.Remove(backup);
|
||||
|
||||
ServerRepository.Update(serverData);
|
||||
|
||||
await Event.Emit("wings.backups.delete", backup);
|
||||
await MessageService.Emit("wings.backups.delete", backup);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.DeleteBackup,
|
||||
x =>
|
||||
@@ -263,7 +244,7 @@ public class ServerService
|
||||
}
|
||||
|
||||
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null,
|
||||
Action<Server>? modifyDetails = null)
|
||||
Action<Server>? modifyDetails = null, int allocations = 1)
|
||||
{
|
||||
var user = UserRepository
|
||||
.Get()
|
||||
@@ -275,21 +256,32 @@ public class ServerService
|
||||
.Include(x => x.DockerImages)
|
||||
.First(x => x.Id == i.Id);
|
||||
|
||||
var allocations = image.Allocations;
|
||||
Node node;
|
||||
|
||||
Node node = n ?? NodeRepository.Get().First();
|
||||
if (n == null)
|
||||
{
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
// We have sadly no choice to use entity framework to do what the sql call does, there
|
||||
// are only slower ways, so we will use a raw sql call as a exception
|
||||
|
||||
freeAllocations = NodeAllocationRepository
|
||||
.Get()
|
||||
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
|
||||
.ToArray();
|
||||
freeAllocations = node.Allocations
|
||||
.Where(a => !ServerRepository.Get()
|
||||
.SelectMany(s => s.Allocations)
|
||||
.Any(b => b.Id == a.Id))
|
||||
.Take(allocations).ToArray();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -380,7 +372,7 @@ public class ServerService
|
||||
|
||||
var user = await UserService.SftpLogin(id, password);
|
||||
|
||||
if (server.Owner.Id == user.Id || user.Admin)
|
||||
if (server.Owner.Id == user.Id)
|
||||
{
|
||||
return server;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using JWT.Builder;
|
||||
using JWT.Exceptions;
|
||||
using Logging.Net;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.LogServices;
|
||||
@@ -124,9 +123,9 @@ public class IdentityService
|
||||
return null;
|
||||
}
|
||||
|
||||
var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime;
|
||||
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(iat).DateTime;
|
||||
|
||||
if (iatD < user.TokenValidTime)
|
||||
if (issuedAt < user.TokenValidTime.ToUniversalTime())
|
||||
return null;
|
||||
|
||||
UserCache = user;
|
||||
|
||||
@@ -12,7 +12,6 @@ public class SessionService
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly NavigationManager NavigationManager;
|
||||
private readonly AlertService AlertService;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
|
||||
private Session? OwnSession;
|
||||
|
||||
@@ -20,14 +19,12 @@ public class SessionService
|
||||
SessionRepository sessionRepository,
|
||||
IdentityService identityService,
|
||||
NavigationManager navigationManager,
|
||||
AlertService alertService,
|
||||
DateTimeService dateTimeService)
|
||||
AlertService alertService)
|
||||
{
|
||||
SessionRepository = sessionRepository;
|
||||
IdentityService = identityService;
|
||||
NavigationManager = navigationManager;
|
||||
AlertService = alertService;
|
||||
DateTimeService = dateTimeService;
|
||||
}
|
||||
|
||||
public async Task Register()
|
||||
@@ -39,7 +36,7 @@ public class SessionService
|
||||
Ip = IdentityService.GetIp(),
|
||||
Url = NavigationManager.Uri,
|
||||
Device = IdentityService.GetDevice(),
|
||||
CreatedAt = DateTimeService.GetCurrent(),
|
||||
CreatedAt = DateTime.Now,
|
||||
User = user,
|
||||
Navigation = NavigationManager,
|
||||
AlertService = AlertService
|
||||
@@ -67,8 +64,10 @@ public class SessionService
|
||||
{
|
||||
foreach (var session in SessionRepository.Get())
|
||||
{
|
||||
if(session.User != null && session.User.Id == user.Id)
|
||||
if (session.User.Id == user.Id)
|
||||
{
|
||||
session.Navigation.NavigateTo(session.Navigation.Uri, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,20 +6,18 @@ namespace Moonlight.App.Services;
|
||||
public class SmartDeployService
|
||||
{
|
||||
private readonly NodeRepository NodeRepository;
|
||||
private readonly Repository<CloudPanel> CloudPanelRepository;
|
||||
private readonly WebSpaceService WebSpaceService;
|
||||
private readonly PleskServerRepository PleskServerRepository;
|
||||
private readonly WebsiteService WebsiteService;
|
||||
private readonly NodeService NodeService;
|
||||
|
||||
public SmartDeployService(
|
||||
NodeRepository nodeRepository,
|
||||
NodeService nodeService,
|
||||
WebSpaceService webSpaceService,
|
||||
Repository<CloudPanel> cloudPanelRepository)
|
||||
NodeService nodeService, PleskServerRepository pleskServerRepository, WebsiteService websiteService)
|
||||
{
|
||||
NodeRepository = nodeRepository;
|
||||
NodeService = nodeService;
|
||||
WebSpaceService = webSpaceService;
|
||||
CloudPanelRepository = cloudPanelRepository;
|
||||
PleskServerRepository = pleskServerRepository;
|
||||
WebsiteService = websiteService;
|
||||
}
|
||||
|
||||
public async Task<Node?> GetNode()
|
||||
@@ -40,14 +38,16 @@ public class SmartDeployService
|
||||
return data.MaxBy(x => x.Value).Key;
|
||||
}
|
||||
|
||||
public async Task<CloudPanel?> GetCloudPanel()
|
||||
public async Task<PleskServer?> GetPleskServer()
|
||||
{
|
||||
var result = new List<CloudPanel>();
|
||||
var result = new List<PleskServer>();
|
||||
|
||||
foreach (var cloudPanel in CloudPanelRepository.Get().ToArray())
|
||||
foreach (var pleskServer in PleskServerRepository.Get().ToArray())
|
||||
{
|
||||
if (await WebSpaceService.IsHostUp(cloudPanel))
|
||||
result.Add(cloudPanel);
|
||||
if (await WebsiteService.IsHostUp(pleskServer))
|
||||
{
|
||||
result.Add(pleskServer);
|
||||
}
|
||||
}
|
||||
|
||||
return result.FirstOrDefault();
|
||||
|
||||
@@ -1,78 +1,57 @@
|
||||
using Logging.Net;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
|
||||
namespace Moonlight.App.Services.Statistics;
|
||||
|
||||
public class StatisticsCaptureService
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
private readonly ConfigService ConfigService;
|
||||
private readonly StatisticsRepository StatisticsRepository;
|
||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
private readonly PeriodicTimer Timer;
|
||||
private readonly WebsiteService WebsiteService;
|
||||
private readonly PleskServerRepository PleskServerRepository;
|
||||
private PeriodicTimer Timer;
|
||||
|
||||
public StatisticsCaptureService(IServiceScopeFactory serviceScopeFactory, ConfigService configService, DateTimeService dateTimeService)
|
||||
public StatisticsCaptureService(IServiceScopeFactory serviceScopeFactory, ConfigService configService)
|
||||
{
|
||||
ServiceScopeFactory = serviceScopeFactory;
|
||||
DateTimeService = dateTimeService;
|
||||
var provider = ServiceScopeFactory.CreateScope().ServiceProvider;
|
||||
|
||||
var config = configService
|
||||
.GetSection("Moonlight")
|
||||
.GetSection("Statistics");
|
||||
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");
|
||||
if(!config.GetValue<bool>("Enabled"))
|
||||
return;
|
||||
|
||||
var period = TimeSpan.FromMinutes(config.GetValue<int>("Wait"));
|
||||
var _period = config.GetValue<int>("Wait");
|
||||
var period = TimeSpan.FromMinutes(_period);
|
||||
Timer = new(period);
|
||||
|
||||
Logger.Info("Starting statistics system");
|
||||
Task.Run(Run);
|
||||
}
|
||||
|
||||
private async Task Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (await Timer.WaitForNextTickAsync())
|
||||
{
|
||||
Logger.Warn("Creating statistics");
|
||||
StatisticsRepository.Add("statistics.usersCount", DataContext.Users.Count());
|
||||
StatisticsRepository.Add("statistics.serversCount", DataContext.Servers.Count());
|
||||
StatisticsRepository.Add("statistics.domainsCount", DataContext.Domains.Count());
|
||||
StatisticsRepository.Add("statistics.websitesCount", DataContext.Websites.Count());
|
||||
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
int databases = 0;
|
||||
|
||||
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)
|
||||
await foreach (var pleskServer in PleskServerRepository.Get())
|
||||
{
|
||||
statisticsRepo!.Add(new StatisticsData()
|
||||
{
|
||||
Chart = chart,
|
||||
Value = value,
|
||||
Date = DateTimeService.GetCurrent()
|
||||
});
|
||||
databases += (await WebsiteService.GetDefaultDatabaseServer(pleskServer)).DbCount;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Logger.Log("Statistics are weird");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("An unexpected error occured while capturing statistics");
|
||||
Logger.Error(e);
|
||||
StatisticsRepository.Add("statistics.databasesCount", databases);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,15 @@ namespace Moonlight.App.Services.Statistics;
|
||||
public class StatisticsViewService
|
||||
{
|
||||
private readonly StatisticsRepository StatisticsRepository;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
|
||||
public StatisticsViewService(StatisticsRepository statisticsRepository, DateTimeService dateTimeService)
|
||||
public StatisticsViewService(StatisticsRepository statisticsRepository)
|
||||
{
|
||||
StatisticsRepository = statisticsRepository;
|
||||
DateTimeService = dateTimeService;
|
||||
}
|
||||
|
||||
public StatisticsData[] GetData(string chart, StatisticsTimeSpan timeSpan)
|
||||
{
|
||||
var startDate = DateTimeService.GetCurrent() - TimeSpan.FromHours((int)timeSpan);
|
||||
var startDate = DateTime.Now - TimeSpan.FromHours((int)timeSpan);
|
||||
|
||||
var objs = StatisticsRepository.Get().Where(x => x.Date > startDate && x.Chart == chart);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
@@ -15,18 +14,20 @@ public class SubscriptionService
|
||||
private readonly OneTimeJwtService OneTimeJwtService;
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly UserRepository UserRepository;
|
||||
private readonly ConfigService ConfigService;
|
||||
|
||||
public SubscriptionService(
|
||||
SubscriptionRepository subscriptionRepository,
|
||||
OneTimeJwtService oneTimeJwtService,
|
||||
IdentityService identityService,
|
||||
UserRepository userRepository
|
||||
)
|
||||
UserRepository userRepository,
|
||||
ConfigService configService)
|
||||
{
|
||||
SubscriptionRepository = subscriptionRepository;
|
||||
OneTimeJwtService = oneTimeJwtService;
|
||||
IdentityService = identityService;
|
||||
UserRepository = userRepository;
|
||||
ConfigService = configService;
|
||||
}
|
||||
|
||||
public async Task<Subscription?> GetCurrent()
|
||||
@@ -89,15 +90,13 @@ public class SubscriptionService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SubscriptionLimit> GetLimit(string identifier) // Cache, optimize sql code
|
||||
public async Task<SubscriptionLimit> GetLimit(string identifier)
|
||||
{
|
||||
var subscription = await GetCurrent();
|
||||
var defaultLimits = await GetDefaultLimits();
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
// If the default subscription limit with identifier is found, return it. if not, return empty
|
||||
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
||||
return new()
|
||||
{
|
||||
Identifier = identifier,
|
||||
Amount = 0
|
||||
@@ -113,8 +112,7 @@ public class SubscriptionService
|
||||
if (foundLimit != null)
|
||||
return foundLimit;
|
||||
|
||||
// If the default subscription limit with identifier is found, return it. if not, return empty
|
||||
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
||||
return new()
|
||||
{
|
||||
Identifier = identifier,
|
||||
Amount = 0
|
||||
@@ -135,17 +133,4 @@ public class SubscriptionService
|
||||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
132
Moonlight/App/Services/Support/SupportAdminService.cs
Normal file
132
Moonlight/App/Services/Support/SupportAdminService.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
124
Moonlight/App/Services/Support/SupportClientService.cs
Normal file
124
Moonlight/App/Services/Support/SupportClientService.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
138
Moonlight/App/Services/Support/SupportServerService.cs
Normal file
138
Moonlight/App/Services/Support/SupportServerService.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ public class UserService
|
||||
private readonly MailService MailService;
|
||||
private readonly IdentityService IdentityService;
|
||||
private readonly IpLocateService IpLocateService;
|
||||
private readonly DateTimeService DateTimeService;
|
||||
|
||||
private readonly string JwtSecret;
|
||||
|
||||
@@ -30,9 +29,7 @@ public class UserService
|
||||
SecurityLogService securityLogService,
|
||||
AuditLogService auditLogService,
|
||||
MailService mailService,
|
||||
IdentityService identityService,
|
||||
IpLocateService ipLocateService,
|
||||
DateTimeService dateTimeService)
|
||||
IdentityService identityService, IpLocateService ipLocateService)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
TotpService = totpService;
|
||||
@@ -41,7 +38,6 @@ public class UserService
|
||||
MailService = mailService;
|
||||
IdentityService = identityService;
|
||||
IpLocateService = ipLocateService;
|
||||
DateTimeService = dateTimeService;
|
||||
|
||||
JwtSecret = configService
|
||||
.GetSection("Moonlight")
|
||||
@@ -74,12 +70,12 @@ public class UserService
|
||||
LastName = lastname,
|
||||
State = "",
|
||||
Status = UserStatus.Unverified,
|
||||
CreatedAt = DateTimeService.GetCurrent(),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
DiscordId = 0,
|
||||
TotpEnabled = false,
|
||||
TotpSecret = "",
|
||||
UpdatedAt = DateTimeService.GetCurrent(),
|
||||
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5)
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
TokenValidTime = DateTime.Now.AddDays(-5)
|
||||
});
|
||||
|
||||
await MailService.SendMail(user!, "register", values => {});
|
||||
@@ -172,7 +168,7 @@ public class UserService
|
||||
public async Task ChangePassword(User user, string password, bool isSystemAction = false)
|
||||
{
|
||||
user.Password = BCrypt.Net.BCrypt.HashPassword(password);
|
||||
user.TokenValidTime = DateTimeService.GetCurrent();
|
||||
user.TokenValidTime = DateTime.Now;
|
||||
UserRepository.Update(user);
|
||||
|
||||
if (isSystemAction)
|
||||
@@ -248,8 +244,8 @@ public class UserService
|
||||
var token = JwtBuilder.Create()
|
||||
.WithAlgorithm(new HMACSHA256Algorithm())
|
||||
.WithSecret(JwtSecret)
|
||||
.AddClaim("exp", new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10)).ToUnixTimeSeconds())
|
||||
.AddClaim("iat", DateTimeService.GetCurrentUnixSeconds())
|
||||
.AddClaim("exp", DateTimeOffset.UtcNow.AddDays(10).ToUnixTimeSeconds())
|
||||
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
||||
.AddClaim("userid", user.Id)
|
||||
.Encode();
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
383
Moonlight/App/Services/WebsiteService.cs
Normal file
383
Moonlight/App/Services/WebsiteService.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
using Logging.Net;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Helpers.Files;
|
||||
using Moonlight.App.Models.Plesk.Requests;
|
||||
using Moonlight.App.Models.Plesk.Resources;
|
||||
using Moonlight.App.Repositories;
|
||||
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class WebsiteService
|
||||
{
|
||||
private readonly WebsiteRepository WebsiteRepository;
|
||||
private readonly PleskServerRepository PleskServerRepository;
|
||||
private readonly PleskApiHelper PleskApiHelper;
|
||||
private readonly UserRepository UserRepository;
|
||||
|
||||
public WebsiteService(WebsiteRepository websiteRepository, PleskApiHelper pleskApiHelper, PleskServerRepository pleskServerRepository, UserRepository userRepository)
|
||||
{
|
||||
WebsiteRepository = websiteRepository;
|
||||
PleskApiHelper = pleskApiHelper;
|
||||
PleskServerRepository = pleskServerRepository;
|
||||
UserRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task<Website> Create(string baseDomain, User owner, PleskServer? ps = null)
|
||||
{
|
||||
if (WebsiteRepository.Get().Any(x => x.BaseDomain == baseDomain))
|
||||
throw new DisplayException("A website with this domain does already exist");
|
||||
|
||||
var pleskServer = ps ?? PleskServerRepository.Get().First();
|
||||
|
||||
var ftpLogin = baseDomain;
|
||||
var ftpPassword = StringHelper.GenerateString(16);
|
||||
|
||||
var w = new Website()
|
||||
{
|
||||
PleskServer = pleskServer,
|
||||
Owner = owner,
|
||||
BaseDomain = baseDomain,
|
||||
PleskId = 0,
|
||||
FtpPassword = ftpPassword,
|
||||
FtpLogin = ftpLogin
|
||||
};
|
||||
|
||||
var website = WebsiteRepository.Add(w);
|
||||
|
||||
try
|
||||
{
|
||||
var id = await GetAdminAccount(pleskServer);
|
||||
|
||||
var result = await PleskApiHelper.Post<CreateResult>(pleskServer, "domains", new CreateDomain()
|
||||
{
|
||||
Description = $"moonlight website {website.Id}",
|
||||
Name = baseDomain,
|
||||
HostingType = "virtual",
|
||||
Plan = new()
|
||||
{
|
||||
Name = "Unlimited"
|
||||
},
|
||||
HostingSettings = new()
|
||||
{
|
||||
FtpLogin = ftpLogin,
|
||||
FtpPassword = ftpPassword
|
||||
},
|
||||
OwnerClient = new()
|
||||
{
|
||||
Id = id
|
||||
}
|
||||
});
|
||||
|
||||
website.PleskId = result.Id;
|
||||
|
||||
WebsiteRepository.Update(website);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
WebsiteRepository.Delete(website);
|
||||
throw;
|
||||
}
|
||||
|
||||
return website;
|
||||
}
|
||||
|
||||
public async Task Delete(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
await PleskApiHelper.Delete(website.PleskServer, $"domains/{w.PleskId}", null);
|
||||
|
||||
WebsiteRepository.Delete(website);
|
||||
}
|
||||
|
||||
public async Task<bool> IsHostUp(PleskServer pleskServer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = await PleskApiHelper.Get<ServerStatus>(pleskServer, "server");
|
||||
|
||||
if (res != null)
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> IsHostUp(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
try
|
||||
{
|
||||
var res = await PleskApiHelper.Get<ServerStatus>(website.PleskServer, "server");
|
||||
|
||||
if (res != null)
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Get host
|
||||
|
||||
public async Task<string> GetHost(PleskServer pleskServer)
|
||||
{
|
||||
return (await PleskApiHelper.Get<ServerStatus>(pleskServer, "server")).Hostname;
|
||||
}
|
||||
|
||||
public async Task<string> GetHost(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
return await GetHost(website.PleskServer);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task<int> GetAdminAccount(PleskServer pleskServer)
|
||||
{
|
||||
var users = await PleskApiHelper.Get<Client[]>(pleskServer, "clients");
|
||||
|
||||
var user = users.FirstOrDefault(x => x.Type == "admin");
|
||||
|
||||
if (user == null)
|
||||
throw new DisplayException("No admin account in plesk found");
|
||||
|
||||
return user.Id;
|
||||
}
|
||||
|
||||
#region SSL
|
||||
public async Task<string[]> GetSslCertificates(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
var certs = new List<string>();
|
||||
|
||||
var data = await ExecuteCli(website.PleskServer, "certificate", p =>
|
||||
{
|
||||
p.Add("-l");
|
||||
p.Add("-domain");
|
||||
p.Add(w.BaseDomain);
|
||||
});
|
||||
|
||||
string[] lines = data.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (line.Contains("Lets Encrypt"))
|
||||
{
|
||||
string[] parts = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if(parts.Length > 6)
|
||||
certs.Add($"{parts[4]} {parts[5]} {parts[6]}");
|
||||
}
|
||||
else if (line.Contains("Listing of SSL/TLS certificates repository was successful"))
|
||||
{
|
||||
// This line indicates the end of the certificate listing, so we can stop parsing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return certs.ToArray();
|
||||
}
|
||||
|
||||
public async Task CreateSslCertificate(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
await ExecuteCli(website.PleskServer, "extension", p =>
|
||||
{
|
||||
p.Add("--exec");
|
||||
p.Add("letsencrypt");
|
||||
p.Add("cli.php");
|
||||
p.Add("-d");
|
||||
p.Add(website.BaseDomain);
|
||||
p.Add("-m");
|
||||
p.Add(website.Owner.Email);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task DeleteSslCertificate(Website w, string name)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteCli(website.PleskServer, "site", p =>
|
||||
{
|
||||
p.Add("-u");
|
||||
p.Add(website.BaseDomain);
|
||||
p.Add("-ssl");
|
||||
p.Add("false");
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteCli(website.PleskServer, "certificate", p =>
|
||||
{
|
||||
p.Add("--remove");
|
||||
p.Add(name);
|
||||
p.Add("-domain");
|
||||
p.Add(website.BaseDomain);
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error removing ssl certificate");
|
||||
Logger.Warn(e);
|
||||
|
||||
throw new DisplayException("An unknown error occured while removing ssl certificate");
|
||||
}
|
||||
}
|
||||
catch (DisplayException)
|
||||
{
|
||||
// Redirect all display exception to soft error handler
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error disabling ssl certificate");
|
||||
Logger.Warn(e);
|
||||
|
||||
throw new DisplayException("An unknown error occured while disabling ssl certificate");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Databases
|
||||
|
||||
public async Task<Models.Plesk.Resources.Database[]> GetDatabases(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
var dbs = await PleskApiHelper.Get<Models.Plesk.Resources.Database[]>(
|
||||
website.PleskServer,
|
||||
$"databases?domain={w.BaseDomain}"
|
||||
);
|
||||
|
||||
return dbs;
|
||||
}
|
||||
|
||||
public async Task CreateDatabase(Website w, string name, string password)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
var server = await GetDefaultDatabaseServer(website);
|
||||
|
||||
if (server == null)
|
||||
throw new DisplayException("No database server marked as default found");
|
||||
|
||||
var dbReq = new CreateDatabase()
|
||||
{
|
||||
Name = name,
|
||||
Type = "mysql",
|
||||
ParentDomain = new()
|
||||
{
|
||||
Name = website.BaseDomain
|
||||
},
|
||||
ServerId = server.Id
|
||||
};
|
||||
|
||||
var db = await PleskApiHelper.Post<Models.Plesk.Resources.Database>(website.PleskServer, "databases", dbReq);
|
||||
|
||||
if (db == null)
|
||||
throw new DisplayException("Unable to create database via api");
|
||||
|
||||
var dbUserReq = new CreateDatabaseUser()
|
||||
{
|
||||
DatabaseId = db.Id,
|
||||
Login = name,
|
||||
Password = password
|
||||
};
|
||||
|
||||
await PleskApiHelper.Post(website.PleskServer, "dbusers", dbUserReq);
|
||||
}
|
||||
|
||||
public async Task DeleteDatabase(Website w, Models.Plesk.Resources.Database database)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
var dbUsers = await PleskApiHelper.Get<DatabaseUser[]>(
|
||||
website.PleskServer,
|
||||
$"dbusers?dbId={database.Id}"
|
||||
);
|
||||
|
||||
foreach (var dbUser in dbUsers)
|
||||
{
|
||||
await PleskApiHelper.Delete(website.PleskServer, $"dbusers/{dbUser.Id}", null);
|
||||
}
|
||||
|
||||
await PleskApiHelper.Delete(website.PleskServer, $"databases/{database.Id}", null);
|
||||
}
|
||||
|
||||
public async Task<DatabaseServer?> GetDefaultDatabaseServer(PleskServer pleskServer)
|
||||
{
|
||||
var dbServers = await PleskApiHelper.Get<DatabaseServer[]>(pleskServer, "dbservers");
|
||||
|
||||
return dbServers.FirstOrDefault(x => x.IsDefault);
|
||||
}
|
||||
|
||||
public async Task<DatabaseServer?> GetDefaultDatabaseServer(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
|
||||
return await GetDefaultDatabaseServer(website.PleskServer);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async Task<FileAccess> CreateFileAccess(Website w)
|
||||
{
|
||||
var website = EnsureData(w);
|
||||
var host = await GetHost(website.PleskServer);
|
||||
|
||||
return new FtpFileAccess(host, 21, website.FtpLogin, website.FtpPassword);
|
||||
}
|
||||
|
||||
private async Task<string> ExecuteCli(
|
||||
PleskServer server,
|
||||
string cli, Action<List<string>>? parameters = null,
|
||||
Action<Dictionary<string, string>>? variables = null
|
||||
)
|
||||
{
|
||||
var p = new List<string>();
|
||||
var v = new Dictionary<string, string>();
|
||||
|
||||
parameters?.Invoke(p);
|
||||
variables?.Invoke(v);
|
||||
|
||||
var req = new CliCall()
|
||||
{
|
||||
Env = v,
|
||||
Params = p
|
||||
};
|
||||
|
||||
var res = await PleskApiHelper.Post<CliResult>(server, $"cli/{cli}/call", req);
|
||||
|
||||
return res.Stdout;
|
||||
}
|
||||
|
||||
private Website EnsureData(Website website)
|
||||
{
|
||||
if (website.PleskServer == null || website.Owner == null)
|
||||
return WebsiteRepository
|
||||
.Get()
|
||||
.Include(x => x.PleskServer)
|
||||
.Include(x => x.Owner)
|
||||
.First(x => x.Id == website.Id);
|
||||
|
||||
return website;
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,10 @@
|
||||
<PackageReference Include="CloudFlare.Client" Version="6.1.4" />
|
||||
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.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="GravatarSharp.Core" Version="1.0.1.2" />
|
||||
<PackageReference Include="JWT" Version="10.0.2" />
|
||||
<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="Markdig" Version="0.31.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
|
||||
@@ -43,7 +41,6 @@
|
||||
<PackageReference Include="PteroConsole.NET" Version="1.0.4" />
|
||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||
<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="XtermBlazor" Version="1.6.1" />
|
||||
</ItemGroup>
|
||||
@@ -70,7 +67,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
|
||||
<Folder Include="App\Http\Middleware" />
|
||||
<Folder Include="App\Models\Daemon\Requests" />
|
||||
<Folder Include="App\Models\Google\Resources" />
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
@page "/"
|
||||
@using Moonlight.App.Services
|
||||
@namespace Moonlight.Pages
|
||||
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
@@ -13,42 +9,21 @@
|
||||
|
||||
<div id="components-reconnect-modal" class="my-reconnect-modal components-reconnect-hide">
|
||||
<div class="show">
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<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>
|
||||
<p>
|
||||
<br/>
|
||||
Connecting to moonlight servers
|
||||
</p>
|
||||
</div>
|
||||
<div class="failed">
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<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>
|
||||
<p>
|
||||
<br />
|
||||
Connection to moonlight servers failed
|
||||
</p>
|
||||
</div>
|
||||
<div class="rejected">
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<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>
|
||||
<p>
|
||||
<br />
|
||||
Connection to moonlight servers rejected
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,12 +1,9 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Moonlight.App.Extensions
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
@namespace Moonlight.Pages
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
||||
@inject ConfigService ConfigService
|
||||
@inject LoadingMessageRepository LoadingMessageRepository
|
||||
|
||||
@{
|
||||
var headerConfig = ConfigService
|
||||
@@ -76,13 +73,9 @@
|
||||
<div class="app-page-loader flex-column">
|
||||
<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">
|
||||
<span class="spinner-border text-primary" role="status"></span>
|
||||
<span class="text-muted fs-6 fw-semibold ms-5">@(loadingMessage.Message)</span>
|
||||
<span class="text-muted fs-6 fw-semibold ms-5">CHANGEME</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -100,18 +93,25 @@
|
||||
<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="/assets/js/xtermAddons.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 src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.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/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>
|
||||
moonlight.loading.registerXterm();
|
||||
</script>
|
||||
|
||||
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
|
||||
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -2,9 +2,7 @@ using BlazorDownloadFile;
|
||||
using BlazorTable;
|
||||
using CurrieTechnologies.Razor.SweetAlert2;
|
||||
using Logging.Net;
|
||||
using Moonlight.App.ApiClients.CloudPanel;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Events;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.LogMigrator;
|
||||
using Moonlight.App.Repositories;
|
||||
@@ -19,7 +17,7 @@ using Moonlight.App.Services.Notifications;
|
||||
using Moonlight.App.Services.OAuth2;
|
||||
using Moonlight.App.Services.Sessions;
|
||||
using Moonlight.App.Services.Statistics;
|
||||
using Moonlight.App.Services.SupportChat;
|
||||
using Moonlight.App.Services.Support;
|
||||
|
||||
namespace Moonlight
|
||||
{
|
||||
@@ -43,13 +41,7 @@ namespace Moonlight
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorPages();
|
||||
builder.Services.AddServerSideBlazor()
|
||||
.AddHubOptions(options =>
|
||||
{
|
||||
options.MaximumReceiveMessageSize = 10000000;
|
||||
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
|
||||
options.HandshakeTimeout = TimeSpan.FromSeconds(10);
|
||||
});
|
||||
builder.Services.AddServerSideBlazor();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
// Databases
|
||||
@@ -62,20 +54,23 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<ServerRepository>();
|
||||
builder.Services.AddScoped<ServerBackupRepository>();
|
||||
builder.Services.AddScoped<ImageRepository>();
|
||||
builder.Services.AddScoped<SupportMessageRepository>();
|
||||
builder.Services.AddScoped<DomainRepository>();
|
||||
builder.Services.AddScoped<SharedDomainRepository>();
|
||||
builder.Services.AddScoped<RevokeRepository>();
|
||||
builder.Services.AddScoped<NotificationRepository>();
|
||||
builder.Services.AddScoped<DdosAttackRepository>();
|
||||
builder.Services.AddScoped<SubscriptionRepository>();
|
||||
builder.Services.AddScoped<PleskServerRepository>();
|
||||
builder.Services.AddScoped<WebsiteRepository>();
|
||||
builder.Services.AddScoped<LoadingMessageRepository>();
|
||||
builder.Services.AddScoped<NewsEntryRepository>();
|
||||
builder.Services.AddScoped<NodeAllocationRepository>();
|
||||
|
||||
builder.Services.AddScoped<StatisticsRepository>();
|
||||
|
||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
||||
builder.Services.AddScoped<SecurityLogEntryRepository>();
|
||||
builder.Services.AddScoped(typeof(Repository<>));
|
||||
|
||||
// Services
|
||||
builder.Services.AddSingleton<ConfigService>();
|
||||
@@ -90,6 +85,7 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<TotpService>();
|
||||
builder.Services.AddScoped<ToastService>();
|
||||
builder.Services.AddScoped<NodeService>();
|
||||
builder.Services.AddSingleton<MessageService>();
|
||||
builder.Services.AddScoped<ServerService>();
|
||||
builder.Services.AddSingleton<PaperService>();
|
||||
builder.Services.AddScoped<ClipboardService>();
|
||||
@@ -101,13 +97,8 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<NotificationClientService>();
|
||||
builder.Services.AddScoped<ModalService>();
|
||||
builder.Services.AddScoped<SmartDeployService>();
|
||||
builder.Services.AddScoped<WebSpaceService>();
|
||||
builder.Services.AddScoped<WebsiteService>();
|
||||
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<DiscordOAuth2Service>();
|
||||
@@ -115,6 +106,8 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<SubscriptionService>();
|
||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
||||
|
||||
builder.Services.AddSingleton<CleanupService>();
|
||||
|
||||
// Loggers
|
||||
builder.Services.AddScoped<SecurityLogService>();
|
||||
builder.Services.AddScoped<AuditLogService>();
|
||||
@@ -123,10 +116,10 @@ namespace Moonlight
|
||||
builder.Services.AddScoped<MailService>();
|
||||
builder.Services.AddSingleton<TrashMailDetectorService>();
|
||||
|
||||
// Support chat
|
||||
builder.Services.AddSingleton<SupportChatServerService>();
|
||||
builder.Services.AddScoped<SupportChatClientService>();
|
||||
builder.Services.AddScoped<SupportChatAdminService>();
|
||||
// Support
|
||||
builder.Services.AddSingleton<SupportServerService>();
|
||||
builder.Services.AddScoped<SupportAdminService>();
|
||||
builder.Services.AddScoped<SupportClientService>();
|
||||
|
||||
// Helpers
|
||||
builder.Services.AddSingleton<SmartTranslateHelper>();
|
||||
@@ -137,13 +130,11 @@ namespace Moonlight
|
||||
builder.Services.AddSingleton<PaperApiHelper>();
|
||||
builder.Services.AddSingleton<HostSystemHelper>();
|
||||
builder.Services.AddScoped<DaemonApiHelper>();
|
||||
builder.Services.AddScoped<CloudPanelApiHelper>();
|
||||
builder.Services.AddScoped<PleskApiHelper>();
|
||||
|
||||
// Background services
|
||||
builder.Services.AddSingleton<DiscordBotService>();
|
||||
builder.Services.AddSingleton<StatisticsCaptureService>();
|
||||
builder.Services.AddSingleton<DiscordNotificationService>();
|
||||
builder.Services.AddSingleton<CleanupService>();
|
||||
|
||||
// Third party services
|
||||
builder.Services.AddBlazorTable();
|
||||
@@ -170,11 +161,13 @@ namespace Moonlight
|
||||
app.MapBlazorHub();
|
||||
app.MapFallbackToPage("/_Host");
|
||||
|
||||
// Support service
|
||||
var supportServerService = app.Services.GetRequiredService<SupportServerService>();
|
||||
|
||||
// AutoStart services
|
||||
_ = app.Services.GetRequiredService<CleanupService>();
|
||||
_ = app.Services.GetRequiredService<DiscordBotService>();
|
||||
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
||||
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
||||
|
||||
// Discord bot service
|
||||
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
|
||||
|
||||
@@ -17,15 +17,12 @@
|
||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||
"dotnetRunMessages": true
|
||||
},
|
||||
"Sql Debug": {
|
||||
"commandName": "Project",
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ML_SQL_DEBUG": "true"
|
||||
},
|
||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||
"dotnetRunMessages": true
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
@using Moonlight.App.Exceptions
|
||||
@using Moonlight.App.Services
|
||||
@using Logging.Net
|
||||
@using Moonlight.App.ApiClients.CloudPanel
|
||||
@inherits ErrorBoundaryBase
|
||||
|
||||
@inject AlertService AlertService
|
||||
@@ -58,16 +57,12 @@ else
|
||||
SmartTranslateService.Translate("Error from daemon"),
|
||||
wingsException.Message
|
||||
);
|
||||
|
||||
//TODO: Error log service
|
||||
|
||||
Logger.Warn($"Wings exception status code: {wingsException.StatusCode}");
|
||||
}
|
||||
else if (exception is CloudPanelException cloudPanelException)
|
||||
else if (exception is PleskException pleskException)
|
||||
{
|
||||
await AlertService.Error(
|
||||
SmartTranslateService.Translate("Error from cloud panel"),
|
||||
cloudPanelException.Message
|
||||
SmartTranslateService.Translate("Error from plesk"),
|
||||
pleskException.Message
|
||||
);
|
||||
}
|
||||
else if (exception is NotImplementedException)
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.loading.loadMonaco");
|
||||
await JsRuntime.InvokeVoidAsync("initMonacoTheme");
|
||||
|
||||
Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () =>
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@using Moonlight.App.Helpers.Files
|
||||
@using Logging.Net
|
||||
|
||||
<div class="badge badge-lg badge-light-primary">
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
<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">
|
||||
<LazyLoader Load="Load">
|
||||
<ContentBlock @ref="ContentBlock" AllowContentOverride="true">
|
||||
<tr class="even">
|
||||
<td class="w-10px">
|
||||
</td>
|
||||
@@ -118,7 +117,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</ContentBlock>
|
||||
</LazyLoader>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -140,7 +138,6 @@
|
||||
|
||||
@code
|
||||
{
|
||||
|
||||
[Parameter]
|
||||
public FileAccess Access { get; set; }
|
||||
|
||||
@@ -172,12 +169,8 @@
|
||||
private Dictionary<FileData, bool> ToggleStatusCache = new();
|
||||
private bool AllToggled = false;
|
||||
|
||||
private ContentBlock ContentBlock;
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
ContentBlock?.SetBlocking(true);
|
||||
|
||||
var list = new List<FileData>();
|
||||
|
||||
foreach (var fileData in await Access.Ls())
|
||||
@@ -203,8 +196,6 @@
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
OnSelectionChanged?.Invoke();
|
||||
|
||||
ContentBlock?.SetBlocking(false);
|
||||
}
|
||||
|
||||
private async Task Load(LazyLoader arg)
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<div class="card-body pt-0 pb-0">
|
||||
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/webspaces">
|
||||
<TL>Webspaces</TL>
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/websites">
|
||||
<TL>Websites</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/webspaces/servers">
|
||||
<TL>Cloud panels</TL>
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/websites/servers">
|
||||
<TL>Plesk servers</TL>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,52 +0,0 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject CookieService CookieService
|
||||
|
||||
@if (User != null)
|
||||
{
|
||||
<div class="menu menu-column justify-content-center"
|
||||
data-kt-menu="true">
|
||||
<div class="menu-item">
|
||||
@@ -30,15 +28,14 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item py-2" href="/webspaces/create">
|
||||
<TL>Webspace</TL>
|
||||
<a class="dropdown-item py-2" href="/websites/create">
|
||||
<TL>Website</TL>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="app-navbar flex-shrink-0">
|
||||
<div class="app-navbar-item ms-1 ms-lg-3">
|
||||
|
||||
@@ -45,11 +45,11 @@ else
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/webspaces">
|
||||
<a class="menu-link" href="/websites">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-globe"></i>
|
||||
</span>
|
||||
<span class="menu-title"><TL>Webspaces</TL></span>
|
||||
<span class="menu-title"><TL>Websites</TL></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
@@ -148,11 +148,11 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/admin/webspaces">
|
||||
<a class="menu-link" href="/admin/websites">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-globe"></i>
|
||||
</span>
|
||||
<span class="menu-title"><TL>Webspaces</TL></span>
|
||||
<span class="menu-title"><TL>Websites</TL></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
|
||||
@@ -47,6 +47,6 @@
|
||||
|
||||
private async void TriggerFlashbang()
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("moonlight.flashbang.run");
|
||||
await JsRuntime.InvokeVoidAsync("flashbang");
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
@using Logging.Net
|
||||
@using BlazorContextMenu
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Events
|
||||
@using Moonlight.App.Services.Interop
|
||||
|
||||
@inject ServerService ServerService
|
||||
@@ -13,7 +12,7 @@
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@inject ClipboardService ClipboardService
|
||||
@inject EventSystem Event
|
||||
@inject MessageService MessageService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
@implements IDisposable
|
||||
@@ -113,51 +112,64 @@
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Event.On<ServerBackup>("wings.backups.create", this, async (backup) =>
|
||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.create", this, (backup) =>
|
||||
{
|
||||
if (AllBackups == null)
|
||||
return;
|
||||
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;
|
||||
});
|
||||
|
||||
Event.On<ServerBackup>("wings.backups.createFailed", this, async (backup) =>
|
||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.createfailed", this, (backup) =>
|
||||
{
|
||||
if (AllBackups == null)
|
||||
return;
|
||||
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;
|
||||
});
|
||||
|
||||
Event.On<ServerBackup>("wings.backups.delete", this, async (backup) =>
|
||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.delete", this, async (backup) =>
|
||||
{
|
||||
if (AllBackups == null)
|
||||
return;
|
||||
|
||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await ToastService.Success(SmartTranslateService.Translate("Backup successfully deleted"));
|
||||
await LazyLoader.Reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Event.On<ServerBackup>("wings.backups.restore", this, async (backup) =>
|
||||
MessageService.Subscribe<ServerBackups, 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"));
|
||||
Task.Run(async () => { await ToastService.Success(SmartTranslateService.Translate("Backup successfully restored")); });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -173,103 +185,96 @@
|
||||
}
|
||||
|
||||
private async Task Download(ServerBackup serverBackup)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
||||
|
||||
NavigationManager.NavigateTo(url);
|
||||
await ToastService.Success(SmartTranslateService.Translate("Backup download successfully started"));
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error starting backup download");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("Backup download failed"));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyUrl(ServerBackup serverBackup)
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
||||
|
||||
await ClipboardService.Copy(url);
|
||||
await ClipboardService.CopyToClipboard(url);
|
||||
await AlertService.Success(
|
||||
SmartTranslateService.Translate("Success"),
|
||||
SmartTranslateService.Translate("Backup URL successfully copied to your clipboard"));
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error copying backup url");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while generating backup url"));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Delete(ServerBackup serverBackup)
|
||||
{
|
||||
await ToastService.Info(SmartTranslateService.Translate("Backup deletion started"));
|
||||
await ServerService.DeleteBackup(CurrentServer, serverBackup);
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
|
||||
await ToastService.Info(SmartTranslateService.Translate("Backup deletion started"));
|
||||
await ServerService.DeleteBackup(CurrentServer, serverBackup);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error deleting backup");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while starting backup deletion"));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Restore(ServerBackup serverBackup)
|
||||
{
|
||||
await ServerService.RestoreBackup(CurrentServer, serverBackup);
|
||||
await ToastService.Info(SmartTranslateService.Translate("Backup restore started"));
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
await ServerService.RestoreBackup(CurrentServer, serverBackup);
|
||||
|
||||
await ToastService.Info(SmartTranslateService.Translate("Backup restore started"));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error restoring backup");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while restoring a backup"));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Create()
|
||||
{
|
||||
await ToastService.Info(SmartTranslateService.Translate("Started backup creation"));
|
||||
await ServerService.CreateBackup(CurrentServer);
|
||||
await LazyLoader.Reload();
|
||||
|
||||
/*
|
||||
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
|
||||
//var list = AllBackups!.ToList();
|
||||
//list.Add(backup);
|
||||
//AllBackups = list.ToArray();
|
||||
var list = AllBackups!.ToList();
|
||||
list.Add(backup);
|
||||
AllBackups = list.ToArray();
|
||||
|
||||
*/
|
||||
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Error creating backup");
|
||||
Logger.Warn(e);
|
||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error has occured while creating a backup"));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnContextMenuClick(ItemClickEventArgs args)
|
||||
@@ -295,11 +300,11 @@
|
||||
await Refresh(LazyLoader);
|
||||
}
|
||||
|
||||
public async void Dispose()
|
||||
public void Dispose()
|
||||
{
|
||||
await Event.Off("wings.backups.create", this);
|
||||
await Event.Off("wings.backups.createFailed", this);
|
||||
await Event.Off("wings.backups.restore", this);
|
||||
await Event.Off("wings.backups.delete", this);
|
||||
MessageService.Unsubscribe("wings.backups.create", this);
|
||||
MessageService.Unsubscribe("wings.backups.createfailed", this);
|
||||
MessageService.Unsubscribe("wings.backups.restore", this);
|
||||
MessageService.Unsubscribe("wings.backups.delete", this);
|
||||
}
|
||||
}
|
||||
@@ -55,16 +55,7 @@
|
||||
</div>
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
||||
<span class="ms-1 text-muted">
|
||||
@if (User.Admin)
|
||||
{
|
||||
<a href="/admin/servers/edit/@(CurrentServer.Id)">@(CurrentServer.Id)</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@(CurrentServer.Id)
|
||||
}
|
||||
</span>
|
||||
<span class="ms-1 text-muted">@(CurrentServer.Id)</span>
|
||||
</div>
|
||||
<div class="col fs-5">
|
||||
<span class="fw-bold"><TL>Status</TL>:</span>
|
||||
@@ -174,9 +165,6 @@
|
||||
[CascadingParameter]
|
||||
public Server CurrentServer { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public User User { get; set; }
|
||||
|
||||
[CascadingParameter]
|
||||
public PteroConsole Console { get; set; }
|
||||
|
||||
|
||||
@@ -37,12 +37,6 @@
|
||||
if(Tags.Contains("paperversion"))
|
||||
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"))
|
||||
Settings.Add("Join2Start", typeof(Join2StartSetting));
|
||||
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
@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"));
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject WebSpaceService WebSpaceService
|
||||
|
||||
<div class="card card-flush h-xl-100">
|
||||
<div class="card-body pt-2">
|
||||
<div class="mt-7 row fv-row mb-7">
|
||||
<div class="col-md-3 text-md-start">
|
||||
<label class="fs-6 fw-semibold form-label mt-3">
|
||||
<TL>Sftp Host</TL>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.CloudPanel.Host)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row fv-row mb-7">
|
||||
<div class="col-md-3 text-md-start">
|
||||
<label class="fs-6 fw-semibold form-label mt-3">
|
||||
<TL>Sftp Port</TL>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="21">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row fv-row mb-7">
|
||||
<div class="col-md-3 text-md-start">
|
||||
<label class="fs-6 fw-semibold form-label mt-3">
|
||||
<TL>Sftp Username</TL>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.UserName)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row fv-row mb-7">
|
||||
<div class="col-md-3 text-md-start">
|
||||
<label class="fs-6 fw-semibold form-label mt-3">
|
||||
<TL>Sftp Password</TL>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control form-control-solid disabled blur-unless-hover" disabled="disabled" value="@(CurrentWebSpace.Password)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public WebSpace CurrentWebSpace { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject WebsiteService WebsiteService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<div class="row gy-5 g-xl-10">
|
||||
<div class="col-xl-4 mb-xl-10">
|
||||
<div class="card h-md-100">
|
||||
<div class="card-body d-flex flex-column flex-center">
|
||||
<img class="img-fluid" src="https://image.thum.io/get/http://@(CurrentWebsite.BaseDomain)" alt="Website screenshot"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-8 mb-5 mb-xl-10">
|
||||
<div class="card card-flush h-xl-100">
|
||||
<div class="card-body pt-2">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="row mt-5">
|
||||
<div class="card border">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>SSL certificates</TL>
|
||||
</span>
|
||||
<div class="card-toolbar">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Issue certificate"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="CreateCertificate">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (Certs.Any())
|
||||
{
|
||||
<table class="table align-middle gs-0 gy-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="p-0 w-50px"></th>
|
||||
<th class="p-0 min-w-150px"></th>
|
||||
<th class="p-0 min-w-120px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var cert in Certs)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<div class="symbol symbol-50px me-2">
|
||||
<span class="symbol-label">
|
||||
<i class="bx bx-md bx-receipt text-dark"></i>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-dark fw-bold fs-6">@(cert)</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||
CssClasses="btn btn-danger"
|
||||
OnClick="() => DeleteCertificate(cert)">
|
||||
</WButton>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
<TL>No SSL certificates found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter]
|
||||
public Website CurrentWebsite { get; set; }
|
||||
|
||||
private string[] Certs;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
await lazyLoader.SetText("Loading certificates");
|
||||
Certs = await WebsiteService.GetSslCertificates(CurrentWebsite);
|
||||
}
|
||||
|
||||
private async Task CreateCertificate()
|
||||
{
|
||||
await WebsiteService.CreateSslCertificate(CurrentWebsite);
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
|
||||
private async Task DeleteCertificate(string name)
|
||||
{
|
||||
await WebsiteService.DeleteSslCertificate(CurrentWebsite, name);
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user