Merge pull request #104 from Moonlight-Panel/main
update my DiscordBot branch
This commit is contained in:
83
Moonlight/App/ApiClients/CloudPanel/CloudPanelApiHelper.cs
Normal file
83
Moonlight/App/ApiClients/CloudPanel/CloudPanelApiHelper.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Models.Plesk.Resources;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace Moonlight.App.ApiClients.CloudPanel;
|
||||||
|
|
||||||
|
public class CloudPanelApiHelper
|
||||||
|
{
|
||||||
|
private readonly RestClient Client;
|
||||||
|
|
||||||
|
public CloudPanelApiHelper()
|
||||||
|
{
|
||||||
|
Client = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Post(Database.Entities.CloudPanel cloudPanel, string resource, object? body)
|
||||||
|
{
|
||||||
|
var request = CreateRequest(cloudPanel, resource);
|
||||||
|
|
||||||
|
request.Method = Method.Post;
|
||||||
|
|
||||||
|
if(body != null)
|
||||||
|
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||||
|
|
||||||
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
if (response.StatusCode != 0)
|
||||||
|
{
|
||||||
|
throw new CloudPanelException(
|
||||||
|
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||||
|
(int)response.StatusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(Database.Entities.CloudPanel cloudPanel, string resource, object? body)
|
||||||
|
{
|
||||||
|
var request = CreateRequest(cloudPanel, resource);
|
||||||
|
|
||||||
|
request.Method = Method.Delete;
|
||||||
|
|
||||||
|
if(body != null)
|
||||||
|
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
||||||
|
|
||||||
|
var response = await Client.ExecuteAsync(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
if (response.StatusCode != 0)
|
||||||
|
{
|
||||||
|
throw new CloudPanelException(
|
||||||
|
$"An error occured: ({response.StatusCode}) {response.Content}",
|
||||||
|
(int)response.StatusCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestRequest CreateRequest(Database.Entities.CloudPanel cloudPanel, string resource)
|
||||||
|
{
|
||||||
|
var url = $"{cloudPanel.ApiUrl}/" + resource;
|
||||||
|
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
|
||||||
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
request.AddHeader("Accept", "application/json");
|
||||||
|
request.AddHeader("Authorization", "Bearer " + cloudPanel.ApiKey);
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Moonlight/App/ApiClients/CloudPanel/CloudPanelException.cs
Normal file
32
Moonlight/App/ApiClients/CloudPanel/CloudPanelException.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace Moonlight.App.ApiClients.CloudPanel;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class CloudPanelException : Exception
|
||||||
|
{
|
||||||
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
|
public CloudPanelException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloudPanelException(string message, int statusCode) : base(message)
|
||||||
|
{
|
||||||
|
StatusCode = statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloudPanelException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloudPanelException(string message, Exception inner) : base(message, inner)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CloudPanelException(
|
||||||
|
SerializationInfo info,
|
||||||
|
StreamingContext context) : base(info, context)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Moonlight/App/ApiClients/CloudPanel/Requests/AddDatabase.cs
Normal file
18
Moonlight/App/ApiClients/CloudPanel/Requests/AddDatabase.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Moonlight.App.ApiClients.CloudPanel.Requests;
|
||||||
|
|
||||||
|
public class AddDatabase
|
||||||
|
{
|
||||||
|
[JsonProperty("domainName")]
|
||||||
|
public string DomainName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("databaseName")]
|
||||||
|
public string DatabaseName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("databaseUserName")]
|
||||||
|
public string DatabaseUserName { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("databaseUserPassword")]
|
||||||
|
public string DatabaseUserPassword { get; set; }
|
||||||
|
}
|
||||||
16
Moonlight/App/ApiClients/CloudPanel/Requests/AddPhpSite.cs
Normal file
16
Moonlight/App/ApiClients/CloudPanel/Requests/AddPhpSite.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Moonlight.App.ApiClients.CloudPanel.Requests;
|
||||||
|
|
||||||
|
public class AddPhpSite
|
||||||
|
{
|
||||||
|
[JsonProperty("domainName")] public string DomainName { get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("siteUser")] public string SiteUser { get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("siteUserPassword")] public string SiteUserPassword { get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("vHostTemplate")] public string VHostTemplate { get; set; } = "";
|
||||||
|
|
||||||
|
[JsonProperty("phpVersion")] public string PhpVersion { get; set; } = "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Moonlight.App.ApiClients.CloudPanel.Requests;
|
||||||
|
|
||||||
|
public class InstallLetsEncrypt
|
||||||
|
{
|
||||||
|
[JsonProperty("domainName")]
|
||||||
|
public string DomainName { get; set; }
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
using Moonlight.App.Database.Entities.LogsEntries;
|
||||||
using Moonlight.App.Database.Entities.Notification;
|
using Moonlight.App.Database.Entities.Notification;
|
||||||
|
using Moonlight.App.Database.Interceptors;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
@@ -29,8 +30,7 @@ public class DataContext : DbContext
|
|||||||
public DbSet<AuditLogEntry> AuditLog { get; set; }
|
public DbSet<AuditLogEntry> AuditLog { get; set; }
|
||||||
public DbSet<ErrorLogEntry> ErrorLog { get; set; }
|
public DbSet<ErrorLogEntry> ErrorLog { get; set; }
|
||||||
public DbSet<SecurityLogEntry> SecurityLog { get; set; }
|
public DbSet<SecurityLogEntry> SecurityLog { get; set; }
|
||||||
public DbSet<SupportMessage> SupportMessages { get; set; }
|
|
||||||
|
|
||||||
public DbSet<SharedDomain> SharedDomains { get; set; }
|
public DbSet<SharedDomain> SharedDomains { get; set; }
|
||||||
public DbSet<Domain> Domains { get; set; }
|
public DbSet<Domain> Domains { get; set; }
|
||||||
public DbSet<Revoke> Revokes { get; set; }
|
public DbSet<Revoke> Revokes { get; set; }
|
||||||
@@ -38,10 +38,13 @@ public class DataContext : DbContext
|
|||||||
public DbSet<NotificationAction> NotificationActions { get; set; }
|
public DbSet<NotificationAction> NotificationActions { get; set; }
|
||||||
public DbSet<DdosAttack> DdosAttacks { get; set; }
|
public DbSet<DdosAttack> DdosAttacks { get; set; }
|
||||||
public DbSet<Subscription> Subscriptions { get; set; }
|
public DbSet<Subscription> Subscriptions { get; set; }
|
||||||
public DbSet<PleskServer> PleskServers { get; set; }
|
|
||||||
public DbSet<Website> Websites { get; set; }
|
|
||||||
public DbSet<StatisticsData> Statistics { get; set; }
|
public DbSet<StatisticsData> Statistics { get; set; }
|
||||||
public DbSet<NewsEntry> NewsEntries { get; set; }
|
public DbSet<NewsEntry> NewsEntries { get; set; }
|
||||||
|
|
||||||
|
public DbSet<CloudPanel> CloudPanels { get; set; }
|
||||||
|
public DbSet<MySqlDatabase> Databases { get; set; }
|
||||||
|
public DbSet<WebSpace> WebSpaces { get; set; }
|
||||||
|
public DbSet<SupportChatMessage> SupportChatMessages { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
@@ -65,6 +68,9 @@ public class DataContext : DbContext
|
|||||||
builder.EnableRetryOnFailure(5);
|
builder.EnableRetryOnFailure(5);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(ConfigService.SqlDebugMode)
|
||||||
|
optionsBuilder.AddInterceptors(new SqlLoggingInterceptor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace Moonlight.App.Database.Entities;
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
public class PleskServer
|
public class CloudPanel
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
public string ApiUrl { get; set; } = "";
|
public string ApiUrl { get; set; } = "";
|
||||||
public string ApiKey { get; set; } = "";
|
public string ApiKey { get; set; } = "";
|
||||||
|
public string Host { get; set; } = "";
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
namespace Moonlight.App.Database.Entities;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
public class DockerImage
|
public class DockerImage
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public bool Default { get; set; } = false;
|
public bool Default { get; set; } = false;
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
namespace Moonlight.App.Database.Entities;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
public class Image
|
public class Image
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public Guid Uuid { get; set; }
|
public Guid Uuid { get; set; }
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
namespace Moonlight.App.Database.Entities;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
public class ImageVariable
|
public class ImageVariable
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Key { get; set; } = "";
|
public string Key { get; set; } = "";
|
||||||
public string DefaultValue { get; set; } = "";
|
public string DefaultValue { get; set; } = "";
|
||||||
|
|||||||
9
Moonlight/App/Database/Entities/MySqlDatabase.cs
Normal file
9
Moonlight/App/Database/Entities/MySqlDatabase.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
public class MySqlDatabase
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public WebSpace WebSpace { get; set; }
|
||||||
|
public string UserName { get; set; } = "";
|
||||||
|
public string Password { get; set; } = "";
|
||||||
|
}
|
||||||
21
Moonlight/App/Database/Entities/SupportChatMessage.cs
Normal file
21
Moonlight/App/Database/Entities/SupportChatMessage.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
public class SupportChatMessage
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Content { get; set; } = "";
|
||||||
|
public string Attachment { get; set; } = "";
|
||||||
|
|
||||||
|
public User? Sender { get; set; }
|
||||||
|
public User Recipient { get; set; }
|
||||||
|
|
||||||
|
public bool IsQuestion { get; set; } = false;
|
||||||
|
public QuestionType QuestionType { get; set; }
|
||||||
|
public string Answer { get; set; } = "";
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Database.Entities;
|
|
||||||
|
|
||||||
public class SupportMessage
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Message { get; set; } = "";
|
|
||||||
public User? Sender { get; set; } = null;
|
|
||||||
public User? Recipient { get; set; } = null;
|
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
||||||
public bool IsQuestion { get; set; } = false;
|
|
||||||
public QuestionType Type { get; set; }
|
|
||||||
public string Answer { get; set; } = "";
|
|
||||||
public bool IsSystem { get; set; } = false;
|
|
||||||
public bool IsSupport { get; set; } = false;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Database.Entities;
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ public class User
|
|||||||
// Security
|
// Security
|
||||||
public bool TotpEnabled { get; set; } = false;
|
public bool TotpEnabled { get; set; } = false;
|
||||||
public string TotpSecret { get; set; } = "";
|
public string TotpSecret { get; set; } = "";
|
||||||
public DateTime TokenValidTime { get; set; } = DateTime.Now;
|
public DateTime TokenValidTime { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
// Discord
|
// Discord
|
||||||
public ulong DiscordId { get; set; }
|
public ulong DiscordId { get; set; }
|
||||||
|
|||||||
13
Moonlight/App/Database/Entities/WebSpace.cs
Normal file
13
Moonlight/App/Database/Entities/WebSpace.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
public class WebSpace
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Domain { get; set; } = "";
|
||||||
|
public string UserName { get; set; } = "";
|
||||||
|
public string Password { get; set; } = "";
|
||||||
|
public string VHostTemplate { get; set; } = "";
|
||||||
|
public User Owner { get; set; }
|
||||||
|
public List<MySqlDatabase> Databases { get; set; } = new();
|
||||||
|
public CloudPanel CloudPanel { get; set; }
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace Moonlight.App.Database.Entities;
|
|
||||||
|
|
||||||
public class Website
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string BaseDomain { get; set; } = "";
|
|
||||||
public int PleskId { get; set; }
|
|
||||||
public PleskServer PleskServer { get; set; }
|
|
||||||
public User Owner { get; set; }
|
|
||||||
public string FtpLogin { get; set; } = "";
|
|
||||||
public string FtpPassword { get; set; } = "";
|
|
||||||
}
|
|
||||||
40
Moonlight/App/Database/Interceptors/SqlLoggingInterceptor.cs
Normal file
40
Moonlight/App/Database/Interceptors/SqlLoggingInterceptor.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Data.Common;
|
||||||
|
using Logging.Net;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Interceptors;
|
||||||
|
|
||||||
|
public class SqlLoggingInterceptor : DbCommandInterceptor
|
||||||
|
{
|
||||||
|
public override InterceptionResult<DbDataReader> ReaderExecuting(
|
||||||
|
DbCommand command,
|
||||||
|
CommandEventData eventData,
|
||||||
|
InterceptionResult<DbDataReader> result)
|
||||||
|
{
|
||||||
|
LogSql(command.CommandText);
|
||||||
|
return base.ReaderExecuting(command, eventData, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override InterceptionResult<object> ScalarExecuting(
|
||||||
|
DbCommand command,
|
||||||
|
CommandEventData eventData,
|
||||||
|
InterceptionResult<object> result)
|
||||||
|
{
|
||||||
|
LogSql(command.CommandText);
|
||||||
|
return base.ScalarExecuting(command, eventData, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override InterceptionResult<int> NonQueryExecuting(
|
||||||
|
DbCommand command,
|
||||||
|
CommandEventData eventData,
|
||||||
|
InterceptionResult<int> result)
|
||||||
|
{
|
||||||
|
LogSql(command.CommandText);
|
||||||
|
return base.NonQueryExecuting(command, eventData, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogSql(string sql)
|
||||||
|
{
|
||||||
|
Logger.Info($"[SQL DEBUG] {sql.Replace("\n", "")}");
|
||||||
|
}
|
||||||
|
}
|
||||||
1099
Moonlight/App/Database/Migrations/20230419120719_AddedCloudPanelModels.Designer.cs
generated
Normal file
1099
Moonlight/App/Database/Migrations/20230419120719_AddedCloudPanelModels.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,121 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedCloudPanelModels : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CloudPanels",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
ApiUrl = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
ApiKey = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CloudPanels", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "WebSpaces",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Domain = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
UserName = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Password = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
VHostTemplate = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
OwnerId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
CloudPanelId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_WebSpaces", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_WebSpaces_CloudPanels_CloudPanelId",
|
||||||
|
column: x => x.CloudPanelId,
|
||||||
|
principalTable: "CloudPanels",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_WebSpaces_Users_OwnerId",
|
||||||
|
column: x => x.OwnerId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Databases",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
WebSpaceId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
UserName = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Password = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Databases", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Databases_WebSpaces_WebSpaceId",
|
||||||
|
column: x => x.WebSpaceId,
|
||||||
|
principalTable: "WebSpaces",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Databases_WebSpaceId",
|
||||||
|
table: "Databases",
|
||||||
|
column: "WebSpaceId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_WebSpaces_CloudPanelId",
|
||||||
|
table: "WebSpaces",
|
||||||
|
column: "CloudPanelId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_WebSpaces_OwnerId",
|
||||||
|
table: "WebSpaces",
|
||||||
|
column: "OwnerId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Databases");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "WebSpaces");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CloudPanels");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1103
Moonlight/App/Database/Migrations/20230419125155_AddedHostFieldToCloudPanelModel.Designer.cs
generated
Normal file
1103
Moonlight/App/Database/Migrations/20230419125155_AddedHostFieldToCloudPanelModel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedHostFieldToCloudPanelModel : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Host",
|
||||||
|
table: "CloudPanels",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Host",
|
||||||
|
table: "CloudPanels");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1087
Moonlight/App/Database/Migrations/20230420213846_AddedNewSupportChatModels.Designer.cs
generated
Normal file
1087
Moonlight/App/Database/Migrations/20230420213846_AddedNewSupportChatModels.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedNewSupportChatModels : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Websites");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PleskServers");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "SupportChatMessages",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Content = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Attachment = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
SenderId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
RecipientId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
IsQuestion = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
QuestionType = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Answer = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_SupportChatMessages", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupportChatMessages_Users_RecipientId",
|
||||||
|
column: x => x.RecipientId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupportChatMessages_Users_SenderId",
|
||||||
|
column: x => x.SenderId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id");
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupportChatMessages_RecipientId",
|
||||||
|
table: "SupportChatMessages",
|
||||||
|
column: "RecipientId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupportChatMessages_SenderId",
|
||||||
|
table: "SupportChatMessages",
|
||||||
|
column: "SenderId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SupportChatMessages");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PleskServers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
ApiKey = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
ApiUrl = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PleskServers", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Websites",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
OwnerId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
PleskServerId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
BaseDomain = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
FtpLogin = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
FtpPassword = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
PleskId = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Websites", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Websites_PleskServers_PleskServerId",
|
||||||
|
column: x => x.PleskServerId,
|
||||||
|
principalTable: "PleskServers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Websites_Users_OwnerId",
|
||||||
|
column: x => x.OwnerId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Websites_OwnerId",
|
||||||
|
table: "Websites",
|
||||||
|
column: "OwnerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Websites_PleskServerId",
|
||||||
|
table: "Websites",
|
||||||
|
column: "PleskServerId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1028
Moonlight/App/Database/Migrations/20230421143556_RemovedOldSupportChatModel.Designer.cs
generated
Normal file
1028
Moonlight/App/Database/Migrations/20230421143556_RemovedOldSupportChatModel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RemovedOldSupportChatModel : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SupportMessages");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "SupportMessages",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
RecipientId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
SenderId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
Answer = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
IsQuestion = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
IsSupport = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
IsSystem = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
Message = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Type = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_SupportMessages", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupportMessages_Users_RecipientId",
|
||||||
|
column: x => x.RecipientId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SupportMessages_Users_SenderId",
|
||||||
|
column: x => x.SenderId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id");
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupportMessages_RecipientId",
|
||||||
|
table: "SupportMessages",
|
||||||
|
column: "RecipientId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SupportMessages_SenderId",
|
||||||
|
table: "SupportMessages",
|
||||||
|
column: "SenderId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,33 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.HasAnnotation("ProductVersion", "7.0.3")
|
.HasAnnotation("ProductVersion", "7.0.3")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ApiKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("ApiUrl")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Host")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("CloudPanels");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -281,6 +308,30 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("SecurityLog");
|
b.ToTable("SecurityLog");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("WebSpaceId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("WebSpaceId");
|
||||||
|
|
||||||
|
b.ToTable("Databases");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -402,29 +453,6 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("NotificationClients");
|
b.ToTable("NotificationClients");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.PleskServer", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("ApiKey")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("ApiUrl")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("PleskServers");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -622,7 +650,7 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("Subscriptions");
|
b.ToTable("Subscriptions");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
@@ -632,30 +660,31 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Attachment")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Content")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
b.Property<DateTime>("CreatedAt")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
b.Property<bool>("IsQuestion")
|
b.Property<bool>("IsQuestion")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
b.Property<bool>("IsSupport")
|
b.Property<int>("QuestionType")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<bool>("IsSystem")
|
b.Property<int>("RecipientId")
|
||||||
.HasColumnType("tinyint(1)");
|
|
||||||
|
|
||||||
b.Property<string>("Message")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<int?>("RecipientId")
|
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int?>("SenderId")
|
b.Property<int?>("SenderId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("Type")
|
b.Property<DateTime>("UpdatedAt")
|
||||||
.HasColumnType("int");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -663,7 +692,7 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("SenderId");
|
b.HasIndex("SenderId");
|
||||||
|
|
||||||
b.ToTable("SupportMessages");
|
b.ToTable("SupportChatMessages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.User", b =>
|
||||||
@@ -748,40 +777,41 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("Users");
|
b.ToTable("Users");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("BaseDomain")
|
b.Property<int>("CloudPanelId")
|
||||||
.IsRequired()
|
.HasColumnType("int");
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("FtpLogin")
|
b.Property<string>("Domain")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("FtpPassword")
|
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<int>("OwnerId")
|
b.Property<int>("OwnerId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("PleskId")
|
b.Property<string>("Password")
|
||||||
.HasColumnType("int");
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.Property<int>("PleskServerId")
|
b.Property<string>("UserName")
|
||||||
.HasColumnType("int");
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("VHostTemplate")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CloudPanelId");
|
||||||
|
|
||||||
b.HasIndex("OwnerId");
|
b.HasIndex("OwnerId");
|
||||||
|
|
||||||
b.HasIndex("PleskServerId");
|
b.ToTable("WebSpaces");
|
||||||
|
|
||||||
b.ToTable("Websites");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
|
||||||
@@ -828,6 +858,17 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.HasForeignKey("ImageId");
|
.HasForeignKey("ImageId");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace")
|
||||||
|
.WithMany("Databases")
|
||||||
|
.HasForeignKey("WebSpaceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("WebSpace");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Moonlight.App.Database.Entities.Node", null)
|
b.HasOne("Moonlight.App.Database.Entities.Node", null)
|
||||||
@@ -908,11 +949,13 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.HasForeignKey("ServerId");
|
.HasForeignKey("ServerId");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportMessage", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
|
b.HasOne("Moonlight.App.Database.Entities.User", "Recipient")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("RecipientId");
|
.HasForeignKey("RecipientId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Moonlight.App.Database.Entities.User", "Sender")
|
b.HasOne("Moonlight.App.Database.Entities.User", "Sender")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
@@ -932,23 +975,23 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Navigation("CurrentSubscription");
|
b.Navigation("CurrentSubscription");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
||||||
{
|
{
|
||||||
|
b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CloudPanelId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("OwnerId")
|
.HasForeignKey("OwnerId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Moonlight.App.Database.Entities.PleskServer", "PleskServer")
|
b.Navigation("CloudPanel");
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("PleskServerId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Owner");
|
b.Navigation("Owner");
|
||||||
|
|
||||||
b.Navigation("PleskServer");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b =>
|
||||||
@@ -971,6 +1014,11 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
|
|
||||||
b.Navigation("Variables");
|
b.Navigation("Variables");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Databases");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
142
Moonlight/App/Events/EventSystem.cs
Normal file
142
Moonlight/App/Events/EventSystem.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Logging.Net;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Events;
|
||||||
|
|
||||||
|
public class EventSystem
|
||||||
|
{
|
||||||
|
private Dictionary<int, object> Storage = new();
|
||||||
|
private List<Subscriber> Subscribers = new();
|
||||||
|
|
||||||
|
private readonly bool Debug = false;
|
||||||
|
private readonly bool DisableWarning = false;
|
||||||
|
private readonly TimeSpan TookToLongTime = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
public Task On<T>(string id, object handle, Func<T, Task> action)
|
||||||
|
{
|
||||||
|
if(Debug)
|
||||||
|
Logger.Debug($"{handle} subscribed to '{id}'");
|
||||||
|
|
||||||
|
lock (Subscribers)
|
||||||
|
{
|
||||||
|
if (!Subscribers.Any(x => x.Id == id && x.Handle == handle))
|
||||||
|
{
|
||||||
|
Subscribers.Add(new ()
|
||||||
|
{
|
||||||
|
Action = action,
|
||||||
|
Handle = handle,
|
||||||
|
Id = id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Emit(string id, object? d = null)
|
||||||
|
{
|
||||||
|
int hashCode = -1;
|
||||||
|
|
||||||
|
if (d != null)
|
||||||
|
{
|
||||||
|
hashCode = d.GetHashCode();
|
||||||
|
Storage.TryAdd(hashCode, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
Subscriber[] subscribers;
|
||||||
|
|
||||||
|
lock (Subscribers)
|
||||||
|
{
|
||||||
|
subscribers = Subscribers
|
||||||
|
.Where(x => x.Id == id)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
|
foreach (var subscriber in subscribers)
|
||||||
|
{
|
||||||
|
tasks.Add(new Task(() =>
|
||||||
|
{
|
||||||
|
int storageId = hashCode + 0; // To create a copy of the hash code
|
||||||
|
|
||||||
|
object? data = null;
|
||||||
|
|
||||||
|
if (storageId != -1)
|
||||||
|
{
|
||||||
|
if (Storage.TryGetValue(storageId, out var value))
|
||||||
|
{
|
||||||
|
data = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Warn($"Object with the hash '{storageId}' was not present in the storage");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stopWatch = new Stopwatch();
|
||||||
|
stopWatch.Start();
|
||||||
|
|
||||||
|
var del = (Delegate)subscriber.Action;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
((Task)del.DynamicInvoke(data)!).Wait();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Warn($"Error emitting '{subscriber.Id} on {subscriber.Handle}'");
|
||||||
|
Logger.Warn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
|
||||||
|
if (!DisableWarning)
|
||||||
|
{
|
||||||
|
if (stopWatch.Elapsed.TotalMilliseconds > TookToLongTime.TotalMilliseconds)
|
||||||
|
{
|
||||||
|
Logger.Warn($"Subscriber {subscriber.Handle} for event '{subscriber.Id}' took long to process. {stopWatch.Elapsed.TotalMilliseconds}ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Debug)
|
||||||
|
{
|
||||||
|
Logger.Debug($"Subscriber {subscriber.Handle} for event '{subscriber.Id}' took {stopWatch.Elapsed.TotalMilliseconds}ms");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var task in tasks)
|
||||||
|
{
|
||||||
|
task.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
Task.WaitAll(tasks.ToArray());
|
||||||
|
Storage.Remove(hashCode);
|
||||||
|
|
||||||
|
if(Debug)
|
||||||
|
Logger.Debug($"Completed all event tasks for '{id}' and removed object from storage");
|
||||||
|
});
|
||||||
|
|
||||||
|
if(Debug)
|
||||||
|
Logger.Debug($"Completed event emit '{id}'");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Off(string id, object handle)
|
||||||
|
{
|
||||||
|
if(Debug)
|
||||||
|
Logger.Debug($"{handle} unsubscribed to '{id}'");
|
||||||
|
|
||||||
|
lock (Subscribers)
|
||||||
|
{
|
||||||
|
Subscribers.RemoveAll(x => x.Id == id && x.Handle == handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Moonlight/App/Events/Subscriber.cs
Normal file
8
Moonlight/App/Events/Subscriber.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Moonlight.App.Events;
|
||||||
|
|
||||||
|
public class Subscriber
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public object Action { get; set; }
|
||||||
|
public object Handle { get; set; }
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class DaemonException : Exception
|
public class DaemonException : Exception
|
||||||
{
|
{
|
||||||
public int StatusCode { private get; set; }
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
public DaemonException()
|
public DaemonException()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class PleskException : Exception
|
public class PleskException : Exception
|
||||||
{
|
{
|
||||||
public int StatusCode { private get; set; }
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
public PleskException()
|
public PleskException()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Moonlight.App.Exceptions;
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class WingsException : Exception
|
public class WingsException : Exception
|
||||||
{
|
{
|
||||||
public int StatusCode { private get; set; }
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
public WingsException()
|
public WingsException()
|
||||||
{
|
{
|
||||||
|
|||||||
14
Moonlight/App/Extensions/DbSetExtensions.cs
Normal file
14
Moonlight/App/Extensions/DbSetExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Extensions;
|
||||||
|
|
||||||
|
public static class DbSetExtensions
|
||||||
|
{
|
||||||
|
public static T Random<T>(this DbSet<T> repo) where T : class
|
||||||
|
{
|
||||||
|
Random rand = new Random();
|
||||||
|
int toSkip = rand.Next(0, repo.Count());
|
||||||
|
|
||||||
|
return repo.Skip(toSkip).Take(1).First();
|
||||||
|
}
|
||||||
|
}
|
||||||
206
Moonlight/App/Helpers/Files/SftpFileAccess.cs
Normal file
206
Moonlight/App/Helpers/Files/SftpFileAccess.cs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
using Logging.Net;
|
||||||
|
using Renci.SshNet;
|
||||||
|
using ConnectionInfo = Renci.SshNet.ConnectionInfo;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers.Files;
|
||||||
|
|
||||||
|
public class SftpFileAccess : FileAccess
|
||||||
|
{
|
||||||
|
private readonly string SftpHost;
|
||||||
|
private readonly string SftpUser;
|
||||||
|
private readonly string SftpPassword;
|
||||||
|
private readonly int SftpPort;
|
||||||
|
private readonly bool ForceUserDir;
|
||||||
|
|
||||||
|
private readonly SftpClient Client;
|
||||||
|
|
||||||
|
private string InternalPath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (ForceUserDir)
|
||||||
|
return $"/home/{SftpUser}{CurrentPath}";
|
||||||
|
|
||||||
|
return InternalPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SftpFileAccess(string sftpHost, string sftpUser, string sftpPassword, int sftpPort,
|
||||||
|
bool forceUserDir = false)
|
||||||
|
{
|
||||||
|
SftpHost = sftpHost;
|
||||||
|
SftpUser = sftpUser;
|
||||||
|
SftpPassword = sftpPassword;
|
||||||
|
SftpPort = sftpPort;
|
||||||
|
ForceUserDir = forceUserDir;
|
||||||
|
|
||||||
|
Client = new(
|
||||||
|
new ConnectionInfo(
|
||||||
|
SftpHost,
|
||||||
|
SftpPort,
|
||||||
|
SftpUser,
|
||||||
|
new PasswordAuthenticationMethod(
|
||||||
|
SftpUser,
|
||||||
|
SftpPassword
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureConnect()
|
||||||
|
{
|
||||||
|
if (!Client.IsConnected)
|
||||||
|
Client.Connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override Task<FileData[]> Ls()
|
||||||
|
{
|
||||||
|
EnsureConnect();
|
||||||
|
|
||||||
|
var x = new List<FileData>();
|
||||||
|
|
||||||
|
foreach (var file in Client.ListDirectory(InternalPath))
|
||||||
|
{
|
||||||
|
if (file.Name != "." && file.Name != "..")
|
||||||
|
{
|
||||||
|
x.Add(new()
|
||||||
|
{
|
||||||
|
Name = file.Name,
|
||||||
|
Size = file.Attributes.Size,
|
||||||
|
IsFile = !file.IsDirectory
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(x.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Cd(string dir)
|
||||||
|
{
|
||||||
|
var x = Path.Combine(CurrentPath, dir).Replace("\\", "/") + "/";
|
||||||
|
x = x.Replace("//", "/");
|
||||||
|
CurrentPath = x;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Up()
|
||||||
|
{
|
||||||
|
CurrentPath = Path.GetFullPath(Path.Combine(CurrentPath, "..")).Replace("\\", "/").Replace("C:", "");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task SetDir(string dir)
|
||||||
|
{
|
||||||
|
CurrentPath = dir;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<string> Read(FileData fileData)
|
||||||
|
{
|
||||||
|
EnsureConnect();
|
||||||
|
|
||||||
|
var textStream = Client.Open(InternalPath.TrimEnd('/') + "/" + fileData.Name, FileMode.Open);
|
||||||
|
|
||||||
|
if (textStream == null)
|
||||||
|
return Task.FromResult("");
|
||||||
|
|
||||||
|
var streamReader = new StreamReader(textStream);
|
||||||
|
|
||||||
|
var text = streamReader.ReadToEnd();
|
||||||
|
|
||||||
|
streamReader.Close();
|
||||||
|
textStream.Close();
|
||||||
|
|
||||||
|
return Task.FromResult(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Write(FileData fileData, string content)
|
||||||
|
{
|
||||||
|
EnsureConnect();
|
||||||
|
|
||||||
|
var textStream = Client.Open(InternalPath.TrimEnd('/') + "/" + fileData.Name, FileMode.Create);
|
||||||
|
|
||||||
|
var streamWriter = new StreamWriter(textStream);
|
||||||
|
streamWriter.Write(content);
|
||||||
|
|
||||||
|
streamWriter.Flush();
|
||||||
|
textStream.Flush();
|
||||||
|
|
||||||
|
streamWriter.Close();
|
||||||
|
textStream.Close();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task Upload(string name, Stream stream, Action<int>? progressUpdated = null)
|
||||||
|
{
|
||||||
|
var dataStream = new SyncStreamAdapter(stream);
|
||||||
|
|
||||||
|
await Task.Factory.FromAsync((x, _) => Client.BeginUploadFile(dataStream, InternalPath + name, x, null, u =>
|
||||||
|
{
|
||||||
|
progressUpdated?.Invoke((int)((long)u / stream.Length));
|
||||||
|
}),
|
||||||
|
Client.EndUploadFile, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task MkDir(string name)
|
||||||
|
{
|
||||||
|
Client.CreateDirectory(InternalPath + name);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<string> Pwd()
|
||||||
|
{
|
||||||
|
return Task.FromResult(CurrentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<string> DownloadUrl(FileData fileData)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<Stream> DownloadStream(FileData fileData)
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream(100 * 1024 * 1024);
|
||||||
|
Client.DownloadFile(InternalPath + fileData.Name, stream);
|
||||||
|
|
||||||
|
return Task.FromResult<Stream>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Delete(FileData fileData)
|
||||||
|
{
|
||||||
|
Client.Delete(InternalPath + fileData.Name);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Move(FileData fileData, string newPath)
|
||||||
|
{
|
||||||
|
Client.RenameFile(InternalPath + fileData.Name, InternalPath + newPath);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Compress(params FileData[] files)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task Decompress(FileData fileData)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<string> GetLaunchUrl()
|
||||||
|
{
|
||||||
|
return Task.FromResult($"sftp://{SftpUser}@{SftpHost}:{SftpPort}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object Clone()
|
||||||
|
{
|
||||||
|
return new SftpFileAccess(SftpHost, SftpUser, SftpPassword, SftpPort, ForceUserDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,23 @@
|
|||||||
namespace Moonlight.App.Helpers;
|
using Logging.Net;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
public static class ParseHelper
|
public static class ParseHelper
|
||||||
{
|
{
|
||||||
public static int MinecraftToInt(string raw)
|
public static int MinecraftToInt(string raw)
|
||||||
{
|
{
|
||||||
var versionWithoutPre = raw.Split("-")[0];
|
var versionWithoutPre = raw.Split("_")[0];
|
||||||
|
versionWithoutPre = versionWithoutPre.Split("-")[0];
|
||||||
|
|
||||||
|
// Fuck you 1.7.10 ;)
|
||||||
|
versionWithoutPre = versionWithoutPre.Replace("1.7.10", "1.7");
|
||||||
|
|
||||||
if (versionWithoutPre.Count(x => x == "."[0]) == 1)
|
if (versionWithoutPre.Count(x => x == "."[0]) == 1)
|
||||||
versionWithoutPre += ".0";
|
versionWithoutPre += ".0";
|
||||||
|
|
||||||
return int.Parse(versionWithoutPre.Replace(".", ""));
|
var x = versionWithoutPre.Replace(".", "");
|
||||||
|
|
||||||
|
return int.Parse(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string FirstPartStartingWithNumber(string raw)
|
public static string FirstPartStartingWithNumber(string raw)
|
||||||
@@ -29,4 +37,61 @@ public static class ParseHelper
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetHighestVersion(string[] versions)
|
||||||
|
{
|
||||||
|
// Initialize the highest version to the first version in the array
|
||||||
|
string highestVersion = versions[0];
|
||||||
|
|
||||||
|
// Loop through the remaining versions in the array
|
||||||
|
for (int i = 1; i < versions.Length; i++)
|
||||||
|
{
|
||||||
|
// Compare the current version to the highest version
|
||||||
|
if (CompareVersions(versions[i], highestVersion) > 0)
|
||||||
|
{
|
||||||
|
// If the current version is higher, update the highest version
|
||||||
|
highestVersion = versions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return highestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CompareVersions(string version1, string version2)
|
||||||
|
{
|
||||||
|
// Split the versions into their component parts
|
||||||
|
string[] version1Parts = version1.Split('.');
|
||||||
|
string[] version2Parts = version2.Split('.');
|
||||||
|
|
||||||
|
// Compare each component part in turn
|
||||||
|
for (int i = 0; i < version1Parts.Length && i < version2Parts.Length; i++)
|
||||||
|
{
|
||||||
|
int part1 = int.Parse(version1Parts[i]);
|
||||||
|
int part2 = int.Parse(version2Parts[i]);
|
||||||
|
|
||||||
|
if (part1 < part2)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (part1 > part2)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, the versions are equal up to the length of the shorter one.
|
||||||
|
// If one version has more parts than the other, the longer one is considered higher.
|
||||||
|
if (version1Parts.Length < version2Parts.Length)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (version1Parts.Length > version2Parts.Length)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Exceptions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using RestSharp;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
|
||||||
|
|
||||||
public class PleskApiHelper
|
|
||||||
{
|
|
||||||
private readonly RestClient Client;
|
|
||||||
|
|
||||||
public PleskApiHelper()
|
|
||||||
{
|
|
||||||
Client = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T> Get<T>(PleskServer server, string resource)
|
|
||||||
{
|
|
||||||
var request = CreateRequest(server, resource);
|
|
||||||
|
|
||||||
request.Method = Method.Get;
|
|
||||||
|
|
||||||
var response = await Client.ExecuteAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
|
||||||
{
|
|
||||||
if (response.StatusCode != 0)
|
|
||||||
{
|
|
||||||
throw new PleskException(
|
|
||||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
|
||||||
(int)response.StatusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<T>(response.Content!)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetRaw(PleskServer server, string resource)
|
|
||||||
{
|
|
||||||
var request = CreateRequest(server, resource);
|
|
||||||
|
|
||||||
request.Method = Method.Get;
|
|
||||||
|
|
||||||
var response = await Client.ExecuteAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
|
||||||
{
|
|
||||||
if (response.StatusCode != 0)
|
|
||||||
{
|
|
||||||
throw new PleskException(
|
|
||||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
|
||||||
(int)response.StatusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.Content!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<T> Post<T>(PleskServer server, string resource, object? body)
|
|
||||||
{
|
|
||||||
var request = CreateRequest(server, resource);
|
|
||||||
|
|
||||||
request.Method = Method.Post;
|
|
||||||
|
|
||||||
request.AddParameter("text/plain",
|
|
||||||
JsonConvert.SerializeObject(body),
|
|
||||||
ParameterType.RequestBody
|
|
||||||
);
|
|
||||||
|
|
||||||
var response = await Client.ExecuteAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
|
||||||
{
|
|
||||||
if (response.StatusCode != 0)
|
|
||||||
{
|
|
||||||
throw new PleskException(
|
|
||||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
|
||||||
(int)response.StatusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<T>(response.Content!)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Post(PleskServer server, string resource, object? body)
|
|
||||||
{
|
|
||||||
var request = CreateRequest(server, resource);
|
|
||||||
|
|
||||||
request.Method = Method.Post;
|
|
||||||
|
|
||||||
if(body != null)
|
|
||||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
|
||||||
|
|
||||||
var response = await Client.ExecuteAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
|
||||||
{
|
|
||||||
if (response.StatusCode != 0)
|
|
||||||
{
|
|
||||||
throw new PleskException(
|
|
||||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
|
||||||
(int)response.StatusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task PostRaw(PleskServer server, string resource, object body)
|
|
||||||
{
|
|
||||||
var request = CreateRequest(server, resource);
|
|
||||||
|
|
||||||
request.Method = Method.Post;
|
|
||||||
|
|
||||||
request.AddParameter("text/plain", body, ParameterType.RequestBody);
|
|
||||||
|
|
||||||
var response = await Client.ExecuteAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
|
||||||
{
|
|
||||||
if (response.StatusCode != 0)
|
|
||||||
{
|
|
||||||
throw new PleskException(
|
|
||||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
|
||||||
(int)response.StatusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(PleskServer server, string resource, object? body)
|
|
||||||
{
|
|
||||||
var request = CreateRequest(server, resource);
|
|
||||||
|
|
||||||
request.Method = Method.Delete;
|
|
||||||
|
|
||||||
if(body != null)
|
|
||||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
|
||||||
|
|
||||||
var response = await Client.ExecuteAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
|
||||||
{
|
|
||||||
if (response.StatusCode != 0)
|
|
||||||
{
|
|
||||||
throw new PleskException(
|
|
||||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
|
||||||
(int)response.StatusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Put(PleskServer server, string resource, object? body)
|
|
||||||
{
|
|
||||||
var request = CreateRequest(server, resource);
|
|
||||||
|
|
||||||
request.Method = Method.Put;
|
|
||||||
|
|
||||||
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
|
|
||||||
|
|
||||||
var response = await Client.ExecuteAsync(request);
|
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
|
||||||
{
|
|
||||||
if (response.StatusCode != 0)
|
|
||||||
{
|
|
||||||
throw new PleskException(
|
|
||||||
$"An error occured: ({response.StatusCode}) {response.Content}",
|
|
||||||
(int)response.StatusCode
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception($"An internal error occured: {response.ErrorMessage}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RestRequest CreateRequest(PleskServer pleskServer, string resource)
|
|
||||||
{
|
|
||||||
var url = $"{pleskServer.ApiUrl}/" + resource;
|
|
||||||
|
|
||||||
var request = new RestRequest(url);
|
|
||||||
var ba = Convert.ToBase64String(Encoding.UTF8.GetBytes(pleskServer.ApiKey));
|
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
request.AddHeader("Authorization", "Basic " + ba);
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
58
Moonlight/App/Helpers/SyncStreamAdapter.cs
Normal file
58
Moonlight/App/Helpers/SyncStreamAdapter.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public class SyncStreamAdapter : Stream
|
||||||
|
{
|
||||||
|
private readonly Stream _stream;
|
||||||
|
|
||||||
|
public SyncStreamAdapter(Stream stream)
|
||||||
|
{
|
||||||
|
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead => _stream.CanRead;
|
||||||
|
public override bool CanSeek => _stream.CanSeek;
|
||||||
|
public override bool CanWrite => _stream.CanWrite;
|
||||||
|
public override long Length => _stream.Length;
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => _stream.Position;
|
||||||
|
set => _stream.Position = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
_stream.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
var task = Task.Run(() => _stream.ReadAsync(buffer, offset, count));
|
||||||
|
return task.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
return _stream.Seek(offset, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
_stream.SetLength(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
var task = Task.Run(() => _stream.WriteAsync(buffer, offset, count));
|
||||||
|
task.GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_stream?.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,17 +18,19 @@ public class OAuth2Controller : Controller
|
|||||||
private readonly DiscordOAuth2Service DiscordOAuth2Service;
|
private readonly DiscordOAuth2Service DiscordOAuth2Service;
|
||||||
private readonly UserRepository UserRepository;
|
private readonly UserRepository UserRepository;
|
||||||
private readonly UserService UserService;
|
private readonly UserService UserService;
|
||||||
|
private readonly DateTimeService DateTimeService;
|
||||||
|
|
||||||
public OAuth2Controller(
|
public OAuth2Controller(
|
||||||
GoogleOAuth2Service googleOAuth2Service,
|
GoogleOAuth2Service googleOAuth2Service,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
UserService userService,
|
UserService userService,
|
||||||
DiscordOAuth2Service discordOAuth2Service)
|
DiscordOAuth2Service discordOAuth2Service, DateTimeService dateTimeService)
|
||||||
{
|
{
|
||||||
GoogleOAuth2Service = googleOAuth2Service;
|
GoogleOAuth2Service = googleOAuth2Service;
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
UserService = userService;
|
UserService = userService;
|
||||||
DiscordOAuth2Service = discordOAuth2Service;
|
DiscordOAuth2Service = discordOAuth2Service;
|
||||||
|
DateTimeService = dateTimeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("google")]
|
[HttpGet("google")]
|
||||||
@@ -63,7 +65,7 @@ public class OAuth2Controller : Controller
|
|||||||
|
|
||||||
Response.Cookies.Append("token", token, new ()
|
Response.Cookies.Append("token", token, new ()
|
||||||
{
|
{
|
||||||
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10))
|
Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
|
||||||
});
|
});
|
||||||
|
|
||||||
return Redirect("/");
|
return Redirect("/");
|
||||||
@@ -121,7 +123,7 @@ public class OAuth2Controller : Controller
|
|||||||
|
|
||||||
Response.Cookies.Append("token", token, new ()
|
Response.Cookies.Append("token", token, new ()
|
||||||
{
|
{
|
||||||
Expires = new DateTimeOffset(DateTime.UtcNow.AddDays(10))
|
Expires = new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10))
|
||||||
});
|
});
|
||||||
|
|
||||||
return Redirect("/");
|
return Redirect("/");
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Http.Requests.Wings;
|
using Moonlight.App.Http.Requests.Wings;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
@@ -11,17 +12,17 @@ namespace Moonlight.App.Http.Controllers.Api.Remote;
|
|||||||
public class BackupController : Controller
|
public class BackupController : Controller
|
||||||
{
|
{
|
||||||
private readonly ServerBackupRepository ServerBackupRepository;
|
private readonly ServerBackupRepository ServerBackupRepository;
|
||||||
private readonly MessageService MessageService;
|
private readonly EventSystem Event;
|
||||||
private readonly NodeRepository NodeRepository;
|
private readonly NodeRepository NodeRepository;
|
||||||
|
|
||||||
public BackupController(
|
public BackupController(
|
||||||
ServerBackupRepository serverBackupRepository,
|
ServerBackupRepository serverBackupRepository,
|
||||||
NodeRepository nodeRepository,
|
NodeRepository nodeRepository,
|
||||||
MessageService messageService)
|
EventSystem eventSystem)
|
||||||
{
|
{
|
||||||
ServerBackupRepository = serverBackupRepository;
|
ServerBackupRepository = serverBackupRepository;
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
MessageService = messageService;
|
Event = eventSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{uuid}")]
|
[HttpGet("{uuid}")]
|
||||||
@@ -57,11 +58,11 @@ public class BackupController : Controller
|
|||||||
|
|
||||||
ServerBackupRepository.Update(backup);
|
ServerBackupRepository.Update(backup);
|
||||||
|
|
||||||
await MessageService.Emit($"wings.backups.create", backup);
|
await Event.Emit($"wings.backups.create", backup);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await MessageService.Emit($"wings.backups.createfailed", backup);
|
await Event.Emit($"wings.backups.createFailed", backup);
|
||||||
ServerBackupRepository.Delete(backup);
|
ServerBackupRepository.Delete(backup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +89,7 @@ public class BackupController : Controller
|
|||||||
if (backup == null)
|
if (backup == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
await MessageService.Emit($"wings.backups.restore", backup);
|
await Event.Emit($"wings.backups.restore", backup);
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Logging.Net;
|
using Logging.Net;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Http.Requests.Daemon;
|
using Moonlight.App.Http.Requests.Daemon;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
@@ -12,13 +13,13 @@ namespace Moonlight.App.Http.Controllers.Api.Remote;
|
|||||||
public class DdosController : Controller
|
public class DdosController : Controller
|
||||||
{
|
{
|
||||||
private readonly NodeRepository NodeRepository;
|
private readonly NodeRepository NodeRepository;
|
||||||
private readonly MessageService MessageService;
|
private readonly EventSystem Event;
|
||||||
private readonly DdosAttackRepository DdosAttackRepository;
|
private readonly DdosAttackRepository DdosAttackRepository;
|
||||||
|
|
||||||
public DdosController(NodeRepository nodeRepository, MessageService messageService, DdosAttackRepository ddosAttackRepository)
|
public DdosController(NodeRepository nodeRepository, EventSystem eventSystem, DdosAttackRepository ddosAttackRepository)
|
||||||
{
|
{
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
MessageService = messageService;
|
Event = eventSystem;
|
||||||
DdosAttackRepository = ddosAttackRepository;
|
DdosAttackRepository = ddosAttackRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ public class DdosController : Controller
|
|||||||
|
|
||||||
ddosAttack = DdosAttackRepository.Add(ddosAttack);
|
ddosAttack = DdosAttackRepository.Add(ddosAttack);
|
||||||
|
|
||||||
await MessageService.Emit("node.ddos", ddosAttack);
|
await Event.Emit("node.ddos", ddosAttack);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Http.Resources.Wings;
|
using Moonlight.App.Http.Resources.Wings;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
@@ -15,18 +16,18 @@ public class ServersController : Controller
|
|||||||
private readonly WingsServerConverter Converter;
|
private readonly WingsServerConverter Converter;
|
||||||
private readonly ServerRepository ServerRepository;
|
private readonly ServerRepository ServerRepository;
|
||||||
private readonly NodeRepository NodeRepository;
|
private readonly NodeRepository NodeRepository;
|
||||||
private readonly MessageService MessageService;
|
private readonly EventSystem Event;
|
||||||
|
|
||||||
public ServersController(
|
public ServersController(
|
||||||
WingsServerConverter converter,
|
WingsServerConverter converter,
|
||||||
ServerRepository serverRepository,
|
ServerRepository serverRepository,
|
||||||
NodeRepository nodeRepository,
|
NodeRepository nodeRepository,
|
||||||
MessageService messageService)
|
EventSystem eventSystem)
|
||||||
{
|
{
|
||||||
Converter = converter;
|
Converter = converter;
|
||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
MessageService = messageService;
|
Event = eventSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@@ -68,7 +69,7 @@ public class ServersController : Controller
|
|||||||
totalPages = slice.Length - 1;
|
totalPages = slice.Length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
await MessageService.Emit($"wings.{node.Id}.serverlist", node);
|
await Event.Emit($"wings.{node.Id}.serverList", node);
|
||||||
|
|
||||||
//Logger.Debug($"[BRIDGE] Node '{node.Name}' is requesting server list page {page} with {perPage} items per page");
|
//Logger.Debug($"[BRIDGE] Node '{node.Name}' is requesting server list page {page} with {perPage} items per page");
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ public class ServersController : Controller
|
|||||||
if (token != node.Token)
|
if (token != node.Token)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
await MessageService.Emit($"wings.{node.Id}.statereset", node);
|
await Event.Emit($"wings.{node.Id}.stateReset", node);
|
||||||
|
|
||||||
foreach (var server in ServerRepository
|
foreach (var server in ServerRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -136,7 +137,7 @@ public class ServersController : Controller
|
|||||||
if (server == null)
|
if (server == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
await MessageService.Emit($"wings.{node.Id}.serverfetch", server);
|
await Event.Emit($"wings.{node.Id}.serverFetch", server);
|
||||||
|
|
||||||
try //TODO: Remove
|
try //TODO: Remove
|
||||||
{
|
{
|
||||||
@@ -169,7 +170,7 @@ public class ServersController : Controller
|
|||||||
if (server == null)
|
if (server == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
await MessageService.Emit($"wings.{node.Id}.serverinstallfetch", server);
|
await Event.Emit($"wings.{node.Id}.serverInstallFetch", server);
|
||||||
|
|
||||||
return new WingsServerInstall()
|
return new WingsServerInstall()
|
||||||
{
|
{
|
||||||
@@ -202,8 +203,8 @@ public class ServersController : Controller
|
|||||||
server.Installing = false;
|
server.Installing = false;
|
||||||
ServerRepository.Update(server);
|
ServerRepository.Update(server);
|
||||||
|
|
||||||
await MessageService.Emit($"wings.{node.Id}.serverinstallcomplete", server);
|
await Event.Emit($"wings.{node.Id}.serverInstallComplete", server);
|
||||||
await MessageService.Emit($"server.{server.Uuid}.installcomplete", server);
|
await Event.Emit($"server.{server.Uuid}.installComplete", server);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using Logging.Net;
|
|
||||||
|
|
||||||
namespace Moonlight.App.MessageSystem;
|
|
||||||
|
|
||||||
public class MessageSender
|
|
||||||
{
|
|
||||||
private readonly List<MessageSubscriber> Subscribers;
|
|
||||||
|
|
||||||
public bool Debug { get; set; }
|
|
||||||
public TimeSpan TookToLongTime { get; set; } = TimeSpan.FromSeconds(1);
|
|
||||||
|
|
||||||
public MessageSender()
|
|
||||||
{
|
|
||||||
Subscribers = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Subscribe<T, K>(string name, object bind, Func<K, Task> method)
|
|
||||||
{
|
|
||||||
lock (Subscribers)
|
|
||||||
{
|
|
||||||
Subscribers.Add(new ()
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Action = method,
|
|
||||||
Type = typeof(T),
|
|
||||||
Bind = bind
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Debug)
|
|
||||||
Logger.Debug($"{bind} subscribed to '{name}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Unsubscribe(string name, object bind)
|
|
||||||
{
|
|
||||||
lock (Subscribers)
|
|
||||||
{
|
|
||||||
Subscribers.RemoveAll(x => x.Bind == bind);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Debug)
|
|
||||||
Logger.Debug($"{bind} unsubscribed from '{name}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Emit(string name, object? value, bool disableWarning = false)
|
|
||||||
{
|
|
||||||
lock (Subscribers)
|
|
||||||
{
|
|
||||||
foreach (var subscriber in Subscribers)
|
|
||||||
{
|
|
||||||
if (subscriber.Name == name)
|
|
||||||
{
|
|
||||||
var stopWatch = new Stopwatch();
|
|
||||||
stopWatch.Start();
|
|
||||||
|
|
||||||
var del = (Delegate)subscriber.Action;
|
|
||||||
|
|
||||||
((Task)del.DynamicInvoke(value)!).Wait();
|
|
||||||
|
|
||||||
stopWatch.Stop();
|
|
||||||
|
|
||||||
if (!disableWarning)
|
|
||||||
{
|
|
||||||
if (stopWatch.Elapsed.TotalMilliseconds > TookToLongTime.TotalMilliseconds)
|
|
||||||
{
|
|
||||||
Logger.Warn(
|
|
||||||
$"Subscriber {subscriber.Type.Name} for event '{name}' took long to process. {stopWatch.Elapsed.TotalMilliseconds}ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Debug)
|
|
||||||
{
|
|
||||||
Logger.Debug(
|
|
||||||
$"Subscriber {subscriber.Type.Name} for event '{name}' took {stopWatch.Elapsed.TotalMilliseconds}ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Moonlight.App.MessageSystem;
|
|
||||||
|
|
||||||
public class MessageSubscriber
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public object Action { get; set; }
|
|
||||||
public Type Type { get; set; }
|
|
||||||
public object Bind { get; set; }
|
|
||||||
}
|
|
||||||
19
Moonlight/App/Models/Forms/CloudPanelDataModel.cs
Normal file
19
Moonlight/App/Models/Forms/CloudPanelDataModel.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
|
public class CloudPanelDataModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "You have to enter a name")]
|
||||||
|
[MaxLength(32, ErrorMessage = "The name should not be longer than 32 characters")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "You need to specify the host")]
|
||||||
|
public string Host { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "You need to enter an api url")]
|
||||||
|
public string ApiUrl { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "You need to enter an api key")]
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ public class DomainOrderDataModel
|
|||||||
{
|
{
|
||||||
[Required(ErrorMessage = "You need to specify a name")]
|
[Required(ErrorMessage = "You need to specify a name")]
|
||||||
[MaxLength(32, ErrorMessage = "The max lenght for the name is 32 characters")]
|
[MaxLength(32, ErrorMessage = "The max lenght for the name is 32 characters")]
|
||||||
[RegularExpression(@"^[a-z]+$", ErrorMessage = "The name should only consist of lower case characters")]
|
[RegularExpression(@"^[a-z0-9]+$", ErrorMessage = "The name should only consist of lower case characters or numbers")]
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
[Required(ErrorMessage = "You need to specify a shared domain")]
|
[Required(ErrorMessage = "You need to specify a shared domain")]
|
||||||
|
|||||||
29
Moonlight/App/Models/Forms/ServerEditDataModel.cs
Normal file
29
Moonlight/App/Models/Forms/ServerEditDataModel.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
|
public class ServerEditDataModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "You need to enter a name")]
|
||||||
|
[MaxLength(32, ErrorMessage = "The name cannot be longer that 32 characters")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "You need to specify a user")]
|
||||||
|
public User Owner { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "You need to specify the cpu cores")]
|
||||||
|
public int Cpu { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "You need to specify the memory")]
|
||||||
|
public long Memory { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "You need to specify the disk")]
|
||||||
|
public long Disk { get; set; }
|
||||||
|
|
||||||
|
public string OverrideStartup { get; set; }
|
||||||
|
|
||||||
|
public int DockerImageIndex { get; set; }
|
||||||
|
|
||||||
|
public bool IsCleanupException { get; set; }
|
||||||
|
}
|
||||||
28
Moonlight/App/Repositories/NodeAllocationRepository.cs
Normal file
28
Moonlight/App/Repositories/NodeAllocationRepository.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.Database;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
public class NodeAllocationRepository : IDisposable
|
||||||
|
{
|
||||||
|
// This repository is ONLY for the server creation service, so allocations can be found
|
||||||
|
// using raw sql. DO NOT use this in any other component
|
||||||
|
|
||||||
|
private readonly DataContext DataContext;
|
||||||
|
|
||||||
|
public NodeAllocationRepository(DataContext dataContext)
|
||||||
|
{
|
||||||
|
DataContext = dataContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<NodeAllocation> Get()
|
||||||
|
{
|
||||||
|
return DataContext.NodeAllocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DataContext.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories;
|
|
||||||
|
|
||||||
public class PleskServerRepository : IDisposable
|
|
||||||
{
|
|
||||||
private readonly DataContext DataContext;
|
|
||||||
|
|
||||||
public PleskServerRepository(DataContext dataContext)
|
|
||||||
{
|
|
||||||
DataContext = dataContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbSet<PleskServer> Get()
|
|
||||||
{
|
|
||||||
return DataContext.PleskServers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PleskServer Add(PleskServer pleskServer)
|
|
||||||
{
|
|
||||||
var x = DataContext.PleskServers.Add(pleskServer);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
return x.Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(PleskServer pleskServer)
|
|
||||||
{
|
|
||||||
DataContext.PleskServers.Update(pleskServer);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(PleskServer pleskServer)
|
|
||||||
{
|
|
||||||
DataContext.PleskServers.Remove(pleskServer);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DataContext.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
Moonlight/App/Repositories/Repository.cs
Normal file
40
Moonlight/App/Repositories/Repository.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.Database;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
public class Repository<TEntity> where TEntity : class
|
||||||
|
{
|
||||||
|
private readonly DataContext DataContext;
|
||||||
|
private readonly DbSet<TEntity> DbSet;
|
||||||
|
|
||||||
|
public Repository(DataContext dbContext)
|
||||||
|
{
|
||||||
|
DataContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
||||||
|
DbSet = DataContext.Set<TEntity>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<TEntity> Get()
|
||||||
|
{
|
||||||
|
return DbSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TEntity Add(TEntity entity)
|
||||||
|
{
|
||||||
|
var x = DbSet.Add(entity);
|
||||||
|
DataContext.SaveChanges();
|
||||||
|
return x.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(TEntity entity)
|
||||||
|
{
|
||||||
|
DbSet.Update(entity);
|
||||||
|
DataContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Delete(TEntity entity)
|
||||||
|
{
|
||||||
|
DbSet.Remove(entity);
|
||||||
|
DataContext.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,19 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database;
|
using Moonlight.App.Database;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories;
|
namespace Moonlight.App.Repositories;
|
||||||
|
|
||||||
public class StatisticsRepository : IDisposable
|
public class StatisticsRepository : IDisposable
|
||||||
{
|
{
|
||||||
private readonly DataContext DataContext;
|
private readonly DataContext DataContext;
|
||||||
|
private readonly DateTimeService DateTimeService;
|
||||||
|
|
||||||
public StatisticsRepository(DataContext dataContext)
|
public StatisticsRepository(DataContext dataContext, DateTimeService dateTimeService)
|
||||||
{
|
{
|
||||||
DataContext = dataContext;
|
DataContext = dataContext;
|
||||||
|
DateTimeService = dateTimeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<StatisticsData> Get()
|
public DbSet<StatisticsData> Get()
|
||||||
@@ -27,7 +30,7 @@ public class StatisticsRepository : IDisposable
|
|||||||
|
|
||||||
public StatisticsData Add(string chart, double value)
|
public StatisticsData Add(string chart, double value)
|
||||||
{
|
{
|
||||||
return Add(new StatisticsData() {Chart = chart, Value = value, Date = DateTime.Now});
|
return Add(new StatisticsData() {Chart = chart, Value = value, Date = DateTimeService.GetCurrent()});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories;
|
|
||||||
|
|
||||||
public class SupportMessageRepository : IDisposable
|
|
||||||
{
|
|
||||||
private readonly DataContext DataContext;
|
|
||||||
|
|
||||||
public SupportMessageRepository(DataContext dataContext)
|
|
||||||
{
|
|
||||||
DataContext = dataContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbSet<SupportMessage> Get()
|
|
||||||
{
|
|
||||||
return DataContext.SupportMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SupportMessage Add(SupportMessage message)
|
|
||||||
{
|
|
||||||
var x = DataContext.SupportMessages.Add(message);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
return x.Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(SupportMessage message)
|
|
||||||
{
|
|
||||||
DataContext.SupportMessages.Update(message);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(SupportMessage message)
|
|
||||||
{
|
|
||||||
DataContext.SupportMessages.Remove(message);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DataContext.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories;
|
|
||||||
|
|
||||||
public class WebsiteRepository : IDisposable
|
|
||||||
{
|
|
||||||
private readonly DataContext DataContext;
|
|
||||||
|
|
||||||
public WebsiteRepository(DataContext dataContext)
|
|
||||||
{
|
|
||||||
DataContext = dataContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbSet<Website> Get()
|
|
||||||
{
|
|
||||||
return DataContext.Websites;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Website Add(Website website)
|
|
||||||
{
|
|
||||||
var x = DataContext.Websites.Add(website);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
return x.Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(Website website)
|
|
||||||
{
|
|
||||||
DataContext.Websites.Update(website);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(Website website)
|
|
||||||
{
|
|
||||||
DataContext.Websites.Remove(website);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DataContext.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ using Moonlight.App.Models.Wings;
|
|||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
using Logging.Net;
|
using Logging.Net;
|
||||||
|
using Moonlight.App.Events;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
@@ -23,21 +24,24 @@ public class CleanupService
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private readonly ConfigService ConfigService;
|
private readonly ConfigService ConfigService;
|
||||||
private readonly MessageService MessageService;
|
private readonly DateTimeService DateTimeService;
|
||||||
|
private readonly EventSystem Event;
|
||||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
private readonly PeriodicTimer Timer;
|
private readonly PeriodicTimer Timer;
|
||||||
|
|
||||||
public CleanupService(
|
public CleanupService(
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
IServiceScopeFactory serviceScopeFactory,
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
MessageService messageService)
|
DateTimeService dateTimeService,
|
||||||
|
EventSystem eventSystem)
|
||||||
{
|
{
|
||||||
ServiceScopeFactory = serviceScopeFactory;
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
MessageService = messageService;
|
DateTimeService = dateTimeService;
|
||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
|
Event = eventSystem;
|
||||||
|
|
||||||
StartedAt = DateTime.Now;
|
StartedAt = DateTimeService.GetCurrent();
|
||||||
CompletedAt = DateTime.Now;
|
CompletedAt = DateTimeService.GetCurrent();
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup");
|
||||||
@@ -145,7 +149,7 @@ public class CleanupService
|
|||||||
ServersRunning++;
|
ServersRunning++;
|
||||||
}
|
}
|
||||||
|
|
||||||
await MessageService.Emit("cleanup.updated", null);
|
await Event.Emit("cleanup.updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -175,7 +179,7 @@ public class CleanupService
|
|||||||
ServersRunning++;
|
ServersRunning++;
|
||||||
}
|
}
|
||||||
|
|
||||||
await MessageService.Emit("cleanup.updated", null);
|
await Event.Emit("cleanup.updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,7 +200,7 @@ public class CleanupService
|
|||||||
|
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
CleanupsPerformed++;
|
CleanupsPerformed++;
|
||||||
await MessageService.Emit("cleanup.updated", null);
|
await Event.Emit("cleanup.updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class ConfigService : IConfiguration
|
|||||||
private IConfiguration Configuration;
|
private IConfiguration Configuration;
|
||||||
|
|
||||||
public bool DebugMode { get; private set; } = false;
|
public bool DebugMode { get; private set; } = false;
|
||||||
|
public bool SqlDebugMode { get; private set; } = false;
|
||||||
|
|
||||||
public ConfigService(StorageService storageService)
|
public ConfigService(StorageService storageService)
|
||||||
{
|
{
|
||||||
@@ -28,6 +29,14 @@ public class ConfigService : IConfiguration
|
|||||||
|
|
||||||
if (DebugMode)
|
if (DebugMode)
|
||||||
Logger.Debug("Debug mode enabled");
|
Logger.Debug("Debug mode enabled");
|
||||||
|
|
||||||
|
var sqlDebugVar = Environment.GetEnvironmentVariable("ML_SQL_DEBUG");
|
||||||
|
|
||||||
|
if (sqlDebugVar != null)
|
||||||
|
SqlDebugMode = bool.Parse(sqlDebugVar);
|
||||||
|
|
||||||
|
if (SqlDebugMode)
|
||||||
|
Logger.Debug("Sql debug mode enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reload()
|
public void Reload()
|
||||||
|
|||||||
31
Moonlight/App/Services/DateTimeService.cs
Normal file
31
Moonlight/App/Services/DateTimeService.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class DateTimeService
|
||||||
|
{
|
||||||
|
public long GetCurrentUnix()
|
||||||
|
{
|
||||||
|
return new DateTimeOffset(GetCurrent()).ToUnixTimeMilliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long GetCurrentUnixSeconds()
|
||||||
|
{
|
||||||
|
return new DateTimeOffset(GetCurrent()).ToUnixTimeSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetCurrent()
|
||||||
|
{
|
||||||
|
return DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDate()
|
||||||
|
{
|
||||||
|
return Formatter.FormatDateOnly(GetCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDateTime()
|
||||||
|
{
|
||||||
|
return Formatter.FormatDate(GetCurrent());
|
||||||
|
}
|
||||||
|
}
|
||||||
99
Moonlight/App/Services/DiscordNotificationService.cs
Normal file
99
Moonlight/App/Services/DiscordNotificationService.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Webhook;
|
||||||
|
using Logging.Net;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class DiscordNotificationService
|
||||||
|
{
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
private readonly ResourceService ResourceService;
|
||||||
|
private readonly DiscordWebhookClient Client;
|
||||||
|
private readonly string AppUrl;
|
||||||
|
|
||||||
|
public DiscordNotificationService(
|
||||||
|
EventSystem eventSystem,
|
||||||
|
ConfigService configService,
|
||||||
|
ResourceService resourceService)
|
||||||
|
{
|
||||||
|
Event = eventSystem;
|
||||||
|
ResourceService = resourceService;
|
||||||
|
|
||||||
|
var config = configService.GetSection("Moonlight").GetSection("DiscordNotifications");
|
||||||
|
|
||||||
|
if (config.GetValue<bool>("Enable"))
|
||||||
|
{
|
||||||
|
Logger.Info("Discord notifications enabled");
|
||||||
|
|
||||||
|
Client = new(config.GetValue<string>("WebHook"));
|
||||||
|
AppUrl = configService.GetSection("Moonlight").GetValue<string>("AppUrl");
|
||||||
|
|
||||||
|
Event.On<User>("supportChat.new", this, OnNewSupportChat);
|
||||||
|
Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage);
|
||||||
|
Event.On<User>("supportChat.close", this, OnSupportChatClose);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Info("Discord notifications disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSupportChatClose(User user)
|
||||||
|
{
|
||||||
|
await SendNotification("", builder =>
|
||||||
|
{
|
||||||
|
builder.Title = "A new support chat has been marked as closed";
|
||||||
|
builder.Color = Color.Red;
|
||||||
|
builder.AddField("Email", user.Email);
|
||||||
|
builder.AddField("Firstname", user.FirstName);
|
||||||
|
builder.AddField("Lastname", user.LastName);
|
||||||
|
builder.Url = $"{AppUrl}/admin/support/view/{user.Id}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSupportChatMessage(SupportChatMessage message)
|
||||||
|
{
|
||||||
|
if(message.Sender == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await SendNotification("", builder =>
|
||||||
|
{
|
||||||
|
builder.Title = "New message in support chat";
|
||||||
|
builder.Color = Color.Blue;
|
||||||
|
builder.AddField("Message", message.Content);
|
||||||
|
builder.Author = new EmbedAuthorBuilder()
|
||||||
|
.WithName($"{message.Sender.FirstName} {message.Sender.LastName}")
|
||||||
|
.WithIconUrl(ResourceService.Avatar(message.Sender));
|
||||||
|
builder.Url = $"{AppUrl}/admin/support/view/{message.Recipient.Id}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnNewSupportChat(User user)
|
||||||
|
{
|
||||||
|
await SendNotification("", builder =>
|
||||||
|
{
|
||||||
|
builder.Title = "A new support chat has been marked as active";
|
||||||
|
builder.Color = Color.Green;
|
||||||
|
builder.AddField("Email", user.Email);
|
||||||
|
builder.AddField("Firstname", user.FirstName);
|
||||||
|
builder.AddField("Lastname", user.LastName);
|
||||||
|
builder.Url = $"{AppUrl}/admin/support/view/{user.Id}";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendNotification(string content, Action<EmbedBuilder>? embed = null)
|
||||||
|
{
|
||||||
|
var e = new EmbedBuilder();
|
||||||
|
embed?.Invoke(e);
|
||||||
|
|
||||||
|
await Client.SendMessageAsync(
|
||||||
|
content,
|
||||||
|
false,
|
||||||
|
new []{e.Build()},
|
||||||
|
"Moonlight Notification",
|
||||||
|
ResourceService.Image("logo.svg")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
96
Moonlight/App/Services/FabricService.cs
Normal file
96
Moonlight/App/Services/FabricService.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class FabricService
|
||||||
|
{
|
||||||
|
private readonly HttpClient Client;
|
||||||
|
|
||||||
|
public FabricService()
|
||||||
|
{
|
||||||
|
Client = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetLatestInstallerVersion()
|
||||||
|
{
|
||||||
|
var data = await Client
|
||||||
|
.GetStringAsync("https://meta.fabricmc.net/v2/versions/installer");
|
||||||
|
|
||||||
|
var x = JsonConvert.DeserializeObject<JObject[]>(data) ?? Array.Empty<JObject>();
|
||||||
|
|
||||||
|
var stableVersions = new List<string>();
|
||||||
|
|
||||||
|
foreach (var y in x)
|
||||||
|
{
|
||||||
|
var section = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(
|
||||||
|
y.Root.ToString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
if (section.GetValue<bool>("stable"))
|
||||||
|
{
|
||||||
|
stableVersions.Add(section.GetValue<string>("version"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseHelper.GetHighestVersion(stableVersions.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetLatestLoaderVersion()
|
||||||
|
{
|
||||||
|
var data = await Client
|
||||||
|
.GetStringAsync("https://meta.fabricmc.net/v2/versions/loader");
|
||||||
|
|
||||||
|
var x = JsonConvert.DeserializeObject<JObject[]>(data) ?? Array.Empty<JObject>();
|
||||||
|
|
||||||
|
var stableVersions = new List<string>();
|
||||||
|
|
||||||
|
foreach (var y in x)
|
||||||
|
{
|
||||||
|
var section = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(
|
||||||
|
y.Root.ToString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
if (section.GetValue<bool>("stable"))
|
||||||
|
{
|
||||||
|
stableVersions.Add(section.GetValue<string>("version"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseHelper.GetHighestVersion(stableVersions.ToArray());
|
||||||
|
}
|
||||||
|
public async Task<string[]> GetGameVersions()
|
||||||
|
{
|
||||||
|
var data = await Client
|
||||||
|
.GetStringAsync("https://meta.fabricmc.net/v2/versions/game");
|
||||||
|
|
||||||
|
var x = JsonConvert.DeserializeObject<JObject[]>(data) ?? Array.Empty<JObject>();
|
||||||
|
|
||||||
|
var stableVersions = new List<string>();
|
||||||
|
|
||||||
|
foreach (var y in x)
|
||||||
|
{
|
||||||
|
var section = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(
|
||||||
|
y.Root.ToString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
if (section.GetValue<bool>("stable"))
|
||||||
|
{
|
||||||
|
stableVersions.Add(section.GetValue<string>("version"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stableVersions.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Moonlight/App/Services/FileDownloadService.cs
Normal file
33
Moonlight/App/Services/FileDownloadService.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class FileDownloadService
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime JSRuntime;
|
||||||
|
|
||||||
|
public FileDownloadService(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
JSRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadStream(string fileName, Stream stream)
|
||||||
|
{
|
||||||
|
using var streamRef = new DotNetStreamReference(stream);
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("moonlight.downloads.downloadStream", fileName, streamRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadBytes(string fileName, byte[] bytes)
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream(bytes);
|
||||||
|
|
||||||
|
await DownloadStream(fileName, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadString(string fileName, string content)
|
||||||
|
{
|
||||||
|
await DownloadBytes(fileName, Encoding.UTF8.GetBytes(content));
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Moonlight/App/Services/ForgeService.cs
Normal file
37
Moonlight/App/Services/ForgeService.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class ForgeService
|
||||||
|
{
|
||||||
|
private readonly HttpClient Client;
|
||||||
|
|
||||||
|
public ForgeService()
|
||||||
|
{
|
||||||
|
Client = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key: 1.9.4-recommended Value: 12.17.0.2317
|
||||||
|
public async Task<Dictionary<string, string>> GetVersions()
|
||||||
|
{
|
||||||
|
var data = await Client.GetStringAsync(
|
||||||
|
"https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json");
|
||||||
|
|
||||||
|
var json = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(
|
||||||
|
data
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
var d = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
foreach (var section in json.GetSection("promos").GetChildren())
|
||||||
|
{
|
||||||
|
d.Add(section.Key, section.Value!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,14 +10,9 @@ public class ClipboardService
|
|||||||
{
|
{
|
||||||
JsRuntime = jsRuntime;
|
JsRuntime = jsRuntime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyToClipboard(string data)
|
|
||||||
{
|
|
||||||
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data);
|
|
||||||
}
|
|
||||||
public async Task Copy(string data)
|
public async Task Copy(string data)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("copyTextToClipboard", data);
|
await JsRuntime.InvokeVoidAsync("moonlight.clipboard.copy", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -13,36 +13,36 @@ public class ToastService
|
|||||||
|
|
||||||
public async Task Info(string message)
|
public async Task Info(string message)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("showInfoToast", message);
|
await JsRuntime.InvokeVoidAsync("moonlight.toasts.info", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Error(string message)
|
public async Task Error(string message)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("showErrorToast", message);
|
await JsRuntime.InvokeVoidAsync("moonlight.toasts.error", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Warning(string message)
|
public async Task Warning(string message)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("showWarningToast", message);
|
await JsRuntime.InvokeVoidAsync("moonlight.toasts.warning", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Success(string message)
|
public async Task Success(string message)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("showSuccessToast", message);
|
await JsRuntime.InvokeVoidAsync("moonlight.toasts.success", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateProcessToast(string id, string text)
|
public async Task CreateProcessToast(string id, string text)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("createToast", id, text);
|
await JsRuntime.InvokeVoidAsync("moonlight.toasts.create", id, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateProcessToast(string id, string text)
|
public async Task UpdateProcessToast(string id, string text)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("modifyToast", id, text);
|
await JsRuntime.InvokeVoidAsync("moonlight.toasts.modify", id, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveProcessToast(string id)
|
public async Task RemoveProcessToast(string id)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("removeToast", id);
|
await JsRuntime.InvokeVoidAsync("moonlight.toasts.remove", id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Mail;
|
using System.Net.Mail;
|
||||||
using Logging.Net;
|
using Logging.Net;
|
||||||
|
using MimeKit;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ public class MailService
|
|||||||
private readonly string Password;
|
private readonly string Password;
|
||||||
private readonly string Email;
|
private readonly string Email;
|
||||||
private readonly int Port;
|
private readonly int Port;
|
||||||
|
private readonly bool Ssl;
|
||||||
|
|
||||||
public MailService(ConfigService configService)
|
public MailService(ConfigService configService)
|
||||||
{
|
{
|
||||||
@@ -24,6 +27,7 @@ public class MailService
|
|||||||
Password = mailConfig.GetValue<string>("Password");
|
Password = mailConfig.GetValue<string>("Password");
|
||||||
Email = mailConfig.GetValue<string>("Email");
|
Email = mailConfig.GetValue<string>("Email");
|
||||||
Port = mailConfig.GetValue<int>("Port");
|
Port = mailConfig.GetValue<int>("Port");
|
||||||
|
Ssl = mailConfig.GetValue<bool>("Ssl");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendMail(
|
public async Task SendMail(
|
||||||
@@ -54,20 +58,24 @@ public class MailService
|
|||||||
{
|
{
|
||||||
using var client = new SmtpClient();
|
using var client = new SmtpClient();
|
||||||
|
|
||||||
client.Host = Server;
|
var mailMessage = new MimeMessage();
|
||||||
client.Port = Port;
|
mailMessage.From.Add(new MailboxAddress(Email, Email));
|
||||||
client.EnableSsl = true;
|
mailMessage.To.Add(new MailboxAddress(user.Email, user.Email));
|
||||||
client.Credentials = new NetworkCredential(Email, Password);
|
mailMessage.Subject = $"Hey {user.FirstName}, there are news from moonlight";
|
||||||
|
|
||||||
await client.SendMailAsync(new MailMessage()
|
var body = new BodyBuilder
|
||||||
{
|
{
|
||||||
From = new MailAddress(Email),
|
HtmlBody = parsed
|
||||||
Sender = new MailAddress(Email),
|
};
|
||||||
Body = parsed,
|
mailMessage.Body = body.ToMessageBody();
|
||||||
IsBodyHtml = true,
|
|
||||||
Subject = $"Hey {user.FirstName}, there are news from moonlight",
|
using (var smtpClient = new SmtpClient())
|
||||||
To = { new MailAddress(user.Email) }
|
{
|
||||||
});
|
await smtpClient.ConnectAsync(Server, Port, Ssl);
|
||||||
|
await smtpClient.AuthenticateAsync(Email, Password);
|
||||||
|
await smtpClient.SendAsync(mailMessage);
|
||||||
|
await smtpClient.DisconnectAsync(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
using Moonlight.App.MessageSystem;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
|
||||||
|
|
||||||
public class MessageService : MessageSender
|
|
||||||
{
|
|
||||||
public MessageService()
|
|
||||||
{
|
|
||||||
Debug = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database;
|
using Moonlight.App.Database;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Helpers.Files;
|
using Moonlight.App.Helpers.Files;
|
||||||
@@ -21,8 +22,8 @@ public class ServerService
|
|||||||
private readonly UserRepository UserRepository;
|
private readonly UserRepository UserRepository;
|
||||||
private readonly ImageRepository ImageRepository;
|
private readonly ImageRepository ImageRepository;
|
||||||
private readonly NodeRepository NodeRepository;
|
private readonly NodeRepository NodeRepository;
|
||||||
|
private readonly NodeAllocationRepository NodeAllocationRepository;
|
||||||
private readonly WingsApiHelper WingsApiHelper;
|
private readonly WingsApiHelper WingsApiHelper;
|
||||||
private readonly MessageService MessageService;
|
|
||||||
private readonly UserService UserService;
|
private readonly UserService UserService;
|
||||||
private readonly ConfigService ConfigService;
|
private readonly ConfigService ConfigService;
|
||||||
private readonly WingsJwtHelper WingsJwtHelper;
|
private readonly WingsJwtHelper WingsJwtHelper;
|
||||||
@@ -30,6 +31,8 @@ public class ServerService
|
|||||||
private readonly AuditLogService AuditLogService;
|
private readonly AuditLogService AuditLogService;
|
||||||
private readonly ErrorLogService ErrorLogService;
|
private readonly ErrorLogService ErrorLogService;
|
||||||
private readonly NodeService NodeService;
|
private readonly NodeService NodeService;
|
||||||
|
private readonly DateTimeService DateTimeService;
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
|
||||||
public ServerService(
|
public ServerService(
|
||||||
ServerRepository serverRepository,
|
ServerRepository serverRepository,
|
||||||
@@ -37,21 +40,22 @@ public class ServerService
|
|||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
ImageRepository imageRepository,
|
ImageRepository imageRepository,
|
||||||
NodeRepository nodeRepository,
|
NodeRepository nodeRepository,
|
||||||
MessageService messageService,
|
|
||||||
UserService userService,
|
UserService userService,
|
||||||
ConfigService configService,
|
ConfigService configService,
|
||||||
WingsJwtHelper wingsJwtHelper,
|
WingsJwtHelper wingsJwtHelper,
|
||||||
SecurityLogService securityLogService,
|
SecurityLogService securityLogService,
|
||||||
AuditLogService auditLogService,
|
AuditLogService auditLogService,
|
||||||
ErrorLogService errorLogService,
|
ErrorLogService errorLogService,
|
||||||
NodeService nodeService)
|
NodeService nodeService,
|
||||||
|
NodeAllocationRepository nodeAllocationRepository,
|
||||||
|
DateTimeService dateTimeService,
|
||||||
|
EventSystem eventSystem)
|
||||||
{
|
{
|
||||||
ServerRepository = serverRepository;
|
ServerRepository = serverRepository;
|
||||||
WingsApiHelper = wingsApiHelper;
|
WingsApiHelper = wingsApiHelper;
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
ImageRepository = imageRepository;
|
ImageRepository = imageRepository;
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
MessageService = messageService;
|
|
||||||
UserService = userService;
|
UserService = userService;
|
||||||
ConfigService = configService;
|
ConfigService = configService;
|
||||||
WingsJwtHelper = wingsJwtHelper;
|
WingsJwtHelper = wingsJwtHelper;
|
||||||
@@ -59,6 +63,9 @@ public class ServerService
|
|||||||
AuditLogService = auditLogService;
|
AuditLogService = auditLogService;
|
||||||
ErrorLogService = errorLogService;
|
ErrorLogService = errorLogService;
|
||||||
NodeService = nodeService;
|
NodeService = nodeService;
|
||||||
|
NodeAllocationRepository = nodeAllocationRepository;
|
||||||
|
DateTimeService = dateTimeService;
|
||||||
|
Event = eventSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Server EnsureNodeData(Server s)
|
private Server EnsureNodeData(Server s)
|
||||||
@@ -112,9 +119,9 @@ public class ServerService
|
|||||||
|
|
||||||
var backup = new ServerBackup()
|
var backup = new ServerBackup()
|
||||||
{
|
{
|
||||||
Name = $"Created at {DateTime.Now.ToShortDateString()} {DateTime.Now.ToShortTimeString()}",
|
Name = $"Created at {DateTimeService.GetCurrent().ToShortDateString()} {DateTimeService.GetCurrent().ToShortTimeString()}",
|
||||||
Uuid = Guid.NewGuid(),
|
Uuid = Guid.NewGuid(),
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
Created = false
|
Created = false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -186,15 +193,27 @@ public class ServerService
|
|||||||
.Include(x => x.Backups)
|
.Include(x => x.Backups)
|
||||||
.First(x => x.Id == server.Id);
|
.First(x => x.Id == server.Id);
|
||||||
|
|
||||||
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
|
try
|
||||||
null);
|
{
|
||||||
|
await WingsApiHelper.Delete(serverData.Node, $"api/servers/{serverData.Uuid}/backup/{serverBackup.Uuid}",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
catch (WingsException e)
|
||||||
|
{
|
||||||
|
// when a backup is not longer there we can
|
||||||
|
// safely delete the backup so we ignore this error
|
||||||
|
if (e.StatusCode != 404)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var backup = serverData.Backups.First(x => x.Uuid == serverBackup.Uuid);
|
var backup = serverData.Backups.First(x => x.Uuid == serverBackup.Uuid);
|
||||||
serverData.Backups.Remove(backup);
|
serverData.Backups.Remove(backup);
|
||||||
|
|
||||||
ServerRepository.Update(serverData);
|
ServerRepository.Update(serverData);
|
||||||
|
|
||||||
await MessageService.Emit("wings.backups.delete", backup);
|
await Event.Emit("wings.backups.delete", backup);
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.DeleteBackup,
|
await AuditLogService.Log(AuditLogType.DeleteBackup,
|
||||||
x =>
|
x =>
|
||||||
@@ -244,7 +263,7 @@ public class ServerService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null,
|
public async Task<Server> Create(string name, int cpu, long memory, long disk, User u, Image i, Node? n = null,
|
||||||
Action<Server>? modifyDetails = null, int allocations = 1)
|
Action<Server>? modifyDetails = null)
|
||||||
{
|
{
|
||||||
var user = UserRepository
|
var user = UserRepository
|
||||||
.Get()
|
.Get()
|
||||||
@@ -256,32 +275,21 @@ public class ServerService
|
|||||||
.Include(x => x.DockerImages)
|
.Include(x => x.DockerImages)
|
||||||
.First(x => x.Id == i.Id);
|
.First(x => x.Id == i.Id);
|
||||||
|
|
||||||
Node node;
|
var allocations = image.Allocations;
|
||||||
|
|
||||||
if (n == null)
|
Node node = n ?? NodeRepository.Get().First();
|
||||||
{
|
|
||||||
node = NodeRepository
|
|
||||||
.Get()
|
|
||||||
.Include(x => x.Allocations)
|
|
||||||
.First(); //TODO: Add smart deploy maybe
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node = NodeRepository
|
|
||||||
.Get()
|
|
||||||
.Include(x => x.Allocations)
|
|
||||||
.First(x => x.Id == n.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeAllocation[] freeAllocations;
|
NodeAllocation[] freeAllocations;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
freeAllocations = node.Allocations
|
// We have sadly no choice to use entity framework to do what the sql call does, there
|
||||||
.Where(a => !ServerRepository.Get()
|
// are only slower ways, so we will use a raw sql call as a exception
|
||||||
.SelectMany(s => s.Allocations)
|
|
||||||
.Any(b => b.Id == a.Id))
|
freeAllocations = NodeAllocationRepository
|
||||||
.Take(allocations).ToArray();
|
.Get()
|
||||||
|
.FromSqlRaw($"SELECT * FROM `NodeAllocations` WHERE ServerId IS NULL AND NodeId={node.Id} LIMIT {allocations}")
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -372,7 +380,7 @@ public class ServerService
|
|||||||
|
|
||||||
var user = await UserService.SftpLogin(id, password);
|
var user = await UserService.SftpLogin(id, password);
|
||||||
|
|
||||||
if (server.Owner.Id == user.Id)
|
if (server.Owner.Id == user.Id || user.Admin)
|
||||||
{
|
{
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using JWT.Builder;
|
|||||||
using JWT.Exceptions;
|
using JWT.Exceptions;
|
||||||
using Logging.Net;
|
using Logging.Net;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.LogServices;
|
using Moonlight.App.Services.LogServices;
|
||||||
@@ -123,9 +124,9 @@ public class IdentityService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var issuedAt = DateTimeOffset.FromUnixTimeSeconds(iat).DateTime;
|
var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime;
|
||||||
|
|
||||||
if (issuedAt < user.TokenValidTime.ToUniversalTime())
|
if (iatD < user.TokenValidTime)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
UserCache = user;
|
UserCache = user;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class SessionService
|
|||||||
private readonly IdentityService IdentityService;
|
private readonly IdentityService IdentityService;
|
||||||
private readonly NavigationManager NavigationManager;
|
private readonly NavigationManager NavigationManager;
|
||||||
private readonly AlertService AlertService;
|
private readonly AlertService AlertService;
|
||||||
|
private readonly DateTimeService DateTimeService;
|
||||||
|
|
||||||
private Session? OwnSession;
|
private Session? OwnSession;
|
||||||
|
|
||||||
@@ -19,12 +20,14 @@ public class SessionService
|
|||||||
SessionRepository sessionRepository,
|
SessionRepository sessionRepository,
|
||||||
IdentityService identityService,
|
IdentityService identityService,
|
||||||
NavigationManager navigationManager,
|
NavigationManager navigationManager,
|
||||||
AlertService alertService)
|
AlertService alertService,
|
||||||
|
DateTimeService dateTimeService)
|
||||||
{
|
{
|
||||||
SessionRepository = sessionRepository;
|
SessionRepository = sessionRepository;
|
||||||
IdentityService = identityService;
|
IdentityService = identityService;
|
||||||
NavigationManager = navigationManager;
|
NavigationManager = navigationManager;
|
||||||
AlertService = alertService;
|
AlertService = alertService;
|
||||||
|
DateTimeService = dateTimeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Register()
|
public async Task Register()
|
||||||
@@ -36,7 +39,7 @@ public class SessionService
|
|||||||
Ip = IdentityService.GetIp(),
|
Ip = IdentityService.GetIp(),
|
||||||
Url = NavigationManager.Uri,
|
Url = NavigationManager.Uri,
|
||||||
Device = IdentityService.GetDevice(),
|
Device = IdentityService.GetDevice(),
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
User = user,
|
User = user,
|
||||||
Navigation = NavigationManager,
|
Navigation = NavigationManager,
|
||||||
AlertService = AlertService
|
AlertService = AlertService
|
||||||
@@ -64,10 +67,8 @@ public class SessionService
|
|||||||
{
|
{
|
||||||
foreach (var session in SessionRepository.Get())
|
foreach (var session in SessionRepository.Get())
|
||||||
{
|
{
|
||||||
if (session.User.Id == user.Id)
|
if(session.User != null && session.User.Id == user.Id)
|
||||||
{
|
|
||||||
session.Navigation.NavigateTo(session.Navigation.Uri, true);
|
session.Navigation.NavigateTo(session.Navigation.Uri, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,18 +6,20 @@ namespace Moonlight.App.Services;
|
|||||||
public class SmartDeployService
|
public class SmartDeployService
|
||||||
{
|
{
|
||||||
private readonly NodeRepository NodeRepository;
|
private readonly NodeRepository NodeRepository;
|
||||||
private readonly PleskServerRepository PleskServerRepository;
|
private readonly Repository<CloudPanel> CloudPanelRepository;
|
||||||
private readonly WebsiteService WebsiteService;
|
private readonly WebSpaceService WebSpaceService;
|
||||||
private readonly NodeService NodeService;
|
private readonly NodeService NodeService;
|
||||||
|
|
||||||
public SmartDeployService(
|
public SmartDeployService(
|
||||||
NodeRepository nodeRepository,
|
NodeRepository nodeRepository,
|
||||||
NodeService nodeService, PleskServerRepository pleskServerRepository, WebsiteService websiteService)
|
NodeService nodeService,
|
||||||
|
WebSpaceService webSpaceService,
|
||||||
|
Repository<CloudPanel> cloudPanelRepository)
|
||||||
{
|
{
|
||||||
NodeRepository = nodeRepository;
|
NodeRepository = nodeRepository;
|
||||||
NodeService = nodeService;
|
NodeService = nodeService;
|
||||||
PleskServerRepository = pleskServerRepository;
|
WebSpaceService = webSpaceService;
|
||||||
WebsiteService = websiteService;
|
CloudPanelRepository = cloudPanelRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Node?> GetNode()
|
public async Task<Node?> GetNode()
|
||||||
@@ -38,16 +40,14 @@ public class SmartDeployService
|
|||||||
return data.MaxBy(x => x.Value).Key;
|
return data.MaxBy(x => x.Value).Key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PleskServer?> GetPleskServer()
|
public async Task<CloudPanel?> GetCloudPanel()
|
||||||
{
|
{
|
||||||
var result = new List<PleskServer>();
|
var result = new List<CloudPanel>();
|
||||||
|
|
||||||
foreach (var pleskServer in PleskServerRepository.Get().ToArray())
|
foreach (var cloudPanel in CloudPanelRepository.Get().ToArray())
|
||||||
{
|
{
|
||||||
if (await WebsiteService.IsHostUp(pleskServer))
|
if (await WebSpaceService.IsHostUp(cloudPanel))
|
||||||
{
|
result.Add(cloudPanel);
|
||||||
result.Add(pleskServer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.FirstOrDefault();
|
return result.FirstOrDefault();
|
||||||
|
|||||||
@@ -1,57 +1,78 @@
|
|||||||
using Moonlight.App.Database;
|
using Logging.Net;
|
||||||
|
using Moonlight.App.Database;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
|
using Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Statistics;
|
namespace Moonlight.App.Services.Statistics;
|
||||||
|
|
||||||
public class StatisticsCaptureService
|
public class StatisticsCaptureService
|
||||||
{
|
{
|
||||||
private readonly DataContext DataContext;
|
|
||||||
private readonly ConfigService ConfigService;
|
|
||||||
private readonly StatisticsRepository StatisticsRepository;
|
|
||||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
private readonly WebsiteService WebsiteService;
|
private readonly DateTimeService DateTimeService;
|
||||||
private readonly PleskServerRepository PleskServerRepository;
|
private readonly PeriodicTimer Timer;
|
||||||
private PeriodicTimer Timer;
|
|
||||||
|
|
||||||
public StatisticsCaptureService(IServiceScopeFactory serviceScopeFactory, ConfigService configService)
|
public StatisticsCaptureService(IServiceScopeFactory serviceScopeFactory, ConfigService configService, DateTimeService dateTimeService)
|
||||||
{
|
{
|
||||||
ServiceScopeFactory = serviceScopeFactory;
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
var provider = ServiceScopeFactory.CreateScope().ServiceProvider;
|
DateTimeService = dateTimeService;
|
||||||
|
|
||||||
DataContext = provider.GetRequiredService<DataContext>();
|
|
||||||
ConfigService = configService;
|
|
||||||
StatisticsRepository = provider.GetRequiredService<StatisticsRepository>();
|
|
||||||
WebsiteService = provider.GetRequiredService<WebsiteService>();
|
|
||||||
PleskServerRepository = provider.GetRequiredService<PleskServerRepository>();
|
|
||||||
|
|
||||||
var config = ConfigService.GetSection("Moonlight").GetSection("Statistics");
|
var config = configService
|
||||||
|
.GetSection("Moonlight")
|
||||||
|
.GetSection("Statistics");
|
||||||
|
|
||||||
if(!config.GetValue<bool>("Enabled"))
|
if(!config.GetValue<bool>("Enabled"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var _period = config.GetValue<int>("Wait");
|
var period = TimeSpan.FromMinutes(config.GetValue<int>("Wait"));
|
||||||
var period = TimeSpan.FromMinutes(_period);
|
|
||||||
Timer = new(period);
|
Timer = new(period);
|
||||||
|
|
||||||
|
Logger.Info("Starting statistics system");
|
||||||
Task.Run(Run);
|
Task.Run(Run);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Run()
|
private async Task Run()
|
||||||
{
|
{
|
||||||
while (await Timer.WaitForNextTickAsync())
|
try
|
||||||
{
|
{
|
||||||
StatisticsRepository.Add("statistics.usersCount", DataContext.Users.Count());
|
while (await Timer.WaitForNextTickAsync())
|
||||||
StatisticsRepository.Add("statistics.serversCount", DataContext.Servers.Count());
|
|
||||||
StatisticsRepository.Add("statistics.domainsCount", DataContext.Domains.Count());
|
|
||||||
StatisticsRepository.Add("statistics.websitesCount", DataContext.Websites.Count());
|
|
||||||
|
|
||||||
int databases = 0;
|
|
||||||
|
|
||||||
await foreach (var pleskServer in PleskServerRepository.Get())
|
|
||||||
{
|
{
|
||||||
databases += (await WebsiteService.GetDefaultDatabaseServer(pleskServer)).DbCount;
|
Logger.Warn("Creating statistics");
|
||||||
|
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
|
||||||
|
var statisticsRepo = scope.ServiceProvider.GetRequiredService<Repository<StatisticsData>>();
|
||||||
|
var usersRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||||
|
var serversRepo = scope.ServiceProvider.GetRequiredService<Repository<Server>>();
|
||||||
|
var domainsRepo = scope.ServiceProvider.GetRequiredService<Repository<Domain>>();
|
||||||
|
var webspacesRepo = scope.ServiceProvider.GetRequiredService<Repository<WebSpace>>();
|
||||||
|
var databasesRepo = scope.ServiceProvider.GetRequiredService<Repository<MySqlDatabase>>();
|
||||||
|
var sessionService = scope.ServiceProvider.GetRequiredService<SessionService>();
|
||||||
|
|
||||||
|
void AddEntry(string chart, int value)
|
||||||
|
{
|
||||||
|
statisticsRepo!.Add(new StatisticsData()
|
||||||
|
{
|
||||||
|
Chart = chart,
|
||||||
|
Value = value,
|
||||||
|
Date = DateTimeService.GetCurrent()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AddEntry("usersCount", usersRepo.Get().Count());
|
||||||
|
AddEntry("serversCount", serversRepo.Get().Count());
|
||||||
|
AddEntry("domainsCount", domainsRepo.Get().Count());
|
||||||
|
AddEntry("webspacesCount", webspacesRepo.Get().Count());
|
||||||
|
AddEntry("databasesCount", databasesRepo.Get().Count());
|
||||||
|
AddEntry("sessionsCount", sessionService.GetAll().Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
StatisticsRepository.Add("statistics.databasesCount", databases);
|
Logger.Log("Statistics are weird");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error("An unexpected error occured while capturing statistics");
|
||||||
|
Logger.Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,15 +7,17 @@ namespace Moonlight.App.Services.Statistics;
|
|||||||
public class StatisticsViewService
|
public class StatisticsViewService
|
||||||
{
|
{
|
||||||
private readonly StatisticsRepository StatisticsRepository;
|
private readonly StatisticsRepository StatisticsRepository;
|
||||||
|
private readonly DateTimeService DateTimeService;
|
||||||
|
|
||||||
public StatisticsViewService(StatisticsRepository statisticsRepository)
|
public StatisticsViewService(StatisticsRepository statisticsRepository, DateTimeService dateTimeService)
|
||||||
{
|
{
|
||||||
StatisticsRepository = statisticsRepository;
|
StatisticsRepository = statisticsRepository;
|
||||||
|
DateTimeService = dateTimeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatisticsData[] GetData(string chart, StatisticsTimeSpan timeSpan)
|
public StatisticsData[] GetData(string chart, StatisticsTimeSpan timeSpan)
|
||||||
{
|
{
|
||||||
var startDate = DateTime.Now - TimeSpan.FromHours((int)timeSpan);
|
var startDate = DateTimeService.GetCurrent() - TimeSpan.FromHours((int)timeSpan);
|
||||||
|
|
||||||
var objs = StatisticsRepository.Get().Where(x => x.Date > startDate && x.Chart == chart);
|
var objs = StatisticsRepository.Get().Where(x => x.Date > startDate && x.Chart == chart);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
@@ -14,20 +15,18 @@ public class SubscriptionService
|
|||||||
private readonly OneTimeJwtService OneTimeJwtService;
|
private readonly OneTimeJwtService OneTimeJwtService;
|
||||||
private readonly IdentityService IdentityService;
|
private readonly IdentityService IdentityService;
|
||||||
private readonly UserRepository UserRepository;
|
private readonly UserRepository UserRepository;
|
||||||
private readonly ConfigService ConfigService;
|
|
||||||
|
|
||||||
public SubscriptionService(
|
public SubscriptionService(
|
||||||
SubscriptionRepository subscriptionRepository,
|
SubscriptionRepository subscriptionRepository,
|
||||||
OneTimeJwtService oneTimeJwtService,
|
OneTimeJwtService oneTimeJwtService,
|
||||||
IdentityService identityService,
|
IdentityService identityService,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository
|
||||||
ConfigService configService)
|
)
|
||||||
{
|
{
|
||||||
SubscriptionRepository = subscriptionRepository;
|
SubscriptionRepository = subscriptionRepository;
|
||||||
OneTimeJwtService = oneTimeJwtService;
|
OneTimeJwtService = oneTimeJwtService;
|
||||||
IdentityService = identityService;
|
IdentityService = identityService;
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
ConfigService = configService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Subscription?> GetCurrent()
|
public async Task<Subscription?> GetCurrent()
|
||||||
@@ -90,13 +89,15 @@ public class SubscriptionService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SubscriptionLimit> GetLimit(string identifier)
|
public async Task<SubscriptionLimit> GetLimit(string identifier) // Cache, optimize sql code
|
||||||
{
|
{
|
||||||
var subscription = await GetCurrent();
|
var subscription = await GetCurrent();
|
||||||
|
var defaultLimits = await GetDefaultLimits();
|
||||||
|
|
||||||
if (subscription == null)
|
if (subscription == null)
|
||||||
{
|
{
|
||||||
return new()
|
// If the default subscription limit with identifier is found, return it. if not, return empty
|
||||||
|
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
||||||
{
|
{
|
||||||
Identifier = identifier,
|
Identifier = identifier,
|
||||||
Amount = 0
|
Amount = 0
|
||||||
@@ -111,8 +112,9 @@ public class SubscriptionService
|
|||||||
|
|
||||||
if (foundLimit != null)
|
if (foundLimit != null)
|
||||||
return foundLimit;
|
return foundLimit;
|
||||||
|
|
||||||
return new()
|
// If the default subscription limit with identifier is found, return it. if not, return empty
|
||||||
|
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
||||||
{
|
{
|
||||||
Identifier = identifier,
|
Identifier = identifier,
|
||||||
Amount = 0
|
Amount = 0
|
||||||
@@ -133,4 +135,17 @@ public class SubscriptionService
|
|||||||
|
|
||||||
return userWithData;
|
return userWithData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<SubscriptionLimit[]> GetDefaultLimits() // Add cache and reload option
|
||||||
|
{
|
||||||
|
var defaultSubscriptionJson = "[]";
|
||||||
|
|
||||||
|
if (File.Exists(PathBuilder.File("storage", "configs", "default_subscription.json")))
|
||||||
|
{
|
||||||
|
defaultSubscriptionJson =
|
||||||
|
await File.ReadAllTextAsync(PathBuilder.File("storage", "configs", "default_subscription.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.DeserializeObject<SubscriptionLimit[]>(defaultSubscriptionJson) ?? Array.Empty<SubscriptionLimit>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Support;
|
|
||||||
|
|
||||||
public class SupportAdminService
|
|
||||||
{
|
|
||||||
private readonly SupportServerService SupportServerService;
|
|
||||||
private readonly IdentityService IdentityService;
|
|
||||||
private readonly MessageService MessageService;
|
|
||||||
|
|
||||||
public EventHandler<SupportMessage> OnNewMessage;
|
|
||||||
|
|
||||||
public EventHandler OnUpdateTyping;
|
|
||||||
private List<string> TypingUsers = new();
|
|
||||||
|
|
||||||
private User Self;
|
|
||||||
private User Recipient;
|
|
||||||
|
|
||||||
public SupportAdminService(
|
|
||||||
SupportServerService supportServerService,
|
|
||||||
IdentityService identityService,
|
|
||||||
MessageService messageService)
|
|
||||||
{
|
|
||||||
SupportServerService = supportServerService;
|
|
||||||
IdentityService = identityService;
|
|
||||||
MessageService = messageService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Start(User user)
|
|
||||||
{
|
|
||||||
Self = (await IdentityService.Get())!;
|
|
||||||
Recipient = user;
|
|
||||||
|
|
||||||
MessageService.Subscribe<SupportClientService, SupportMessage>(
|
|
||||||
$"support.{Recipient.Id}.message",
|
|
||||||
this,
|
|
||||||
message =>
|
|
||||||
{
|
|
||||||
OnNewMessage?.Invoke(this, message);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
|
|
||||||
MessageService.Subscribe<SupportClientService, User>(
|
|
||||||
$"support.{Self.Id}.typing",
|
|
||||||
this,
|
|
||||||
user =>
|
|
||||||
{
|
|
||||||
HandleTyping(user);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Typing
|
|
||||||
|
|
||||||
private void HandleTyping(User user)
|
|
||||||
{
|
|
||||||
var name = $"{user.FirstName} {user.LastName}";
|
|
||||||
|
|
||||||
lock (TypingUsers)
|
|
||||||
{
|
|
||||||
if (!TypingUsers.Contains(name))
|
|
||||||
{
|
|
||||||
TypingUsers.Add(name);
|
|
||||||
OnUpdateTyping!.Invoke(this, null!);
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
|
||||||
|
|
||||||
if (TypingUsers.Contains(name))
|
|
||||||
{
|
|
||||||
TypingUsers.Remove(name);
|
|
||||||
OnUpdateTyping!.Invoke(this, null!);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string[] GetTypingUsers()
|
|
||||||
{
|
|
||||||
lock (TypingUsers)
|
|
||||||
{
|
|
||||||
return TypingUsers.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task TriggerTyping()
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MessageService.Emit($"support.{Recipient.Id}.admintyping", Self);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public async Task<SupportMessage[]> GetMessages()
|
|
||||||
{
|
|
||||||
return await SupportServerService.GetMessages(Recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendMessage(string content)
|
|
||||||
{
|
|
||||||
var message = new SupportMessage()
|
|
||||||
{
|
|
||||||
Message = content
|
|
||||||
};
|
|
||||||
|
|
||||||
await SupportServerService.SendMessage(
|
|
||||||
Recipient,
|
|
||||||
message,
|
|
||||||
Self,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Close()
|
|
||||||
{
|
|
||||||
await SupportServerService.Close(Recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
MessageService.Unsubscribe($"support.{Recipient.Id}.message", this);
|
|
||||||
MessageService.Unsubscribe($"support.{Recipient.Id}.typing", this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Support;
|
|
||||||
|
|
||||||
public class SupportClientService : IDisposable
|
|
||||||
{
|
|
||||||
private readonly SupportServerService SupportServerService;
|
|
||||||
private readonly IdentityService IdentityService;
|
|
||||||
private readonly MessageService MessageService;
|
|
||||||
|
|
||||||
public EventHandler<SupportMessage> OnNewMessage;
|
|
||||||
|
|
||||||
public EventHandler OnUpdateTyping;
|
|
||||||
private List<string> TypingUsers = new();
|
|
||||||
|
|
||||||
private User Self;
|
|
||||||
|
|
||||||
public SupportClientService(
|
|
||||||
SupportServerService supportServerService,
|
|
||||||
IdentityService identityService,
|
|
||||||
MessageService messageService)
|
|
||||||
{
|
|
||||||
SupportServerService = supportServerService;
|
|
||||||
IdentityService = identityService;
|
|
||||||
MessageService = messageService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Start()
|
|
||||||
{
|
|
||||||
Self = (await IdentityService.Get())!;
|
|
||||||
|
|
||||||
MessageService.Subscribe<SupportClientService, SupportMessage>(
|
|
||||||
$"support.{Self.Id}.message",
|
|
||||||
this,
|
|
||||||
message =>
|
|
||||||
{
|
|
||||||
OnNewMessage?.Invoke(this, message);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
|
|
||||||
MessageService.Subscribe<SupportClientService, User>(
|
|
||||||
$"support.{Self.Id}.admintyping",
|
|
||||||
this,
|
|
||||||
user =>
|
|
||||||
{
|
|
||||||
HandleTyping(user);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Typing
|
|
||||||
|
|
||||||
private void HandleTyping(User user)
|
|
||||||
{
|
|
||||||
var name = $"{user.FirstName} {user.LastName}";
|
|
||||||
|
|
||||||
lock (TypingUsers)
|
|
||||||
{
|
|
||||||
if (!TypingUsers.Contains(name))
|
|
||||||
{
|
|
||||||
TypingUsers.Add(name);
|
|
||||||
OnUpdateTyping!.Invoke(this, null!);
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
|
||||||
|
|
||||||
if (TypingUsers.Contains(name))
|
|
||||||
{
|
|
||||||
TypingUsers.Remove(name);
|
|
||||||
OnUpdateTyping!.Invoke(this, null!);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string[] GetTypingUsers()
|
|
||||||
{
|
|
||||||
lock (TypingUsers)
|
|
||||||
{
|
|
||||||
return TypingUsers.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task TriggerTyping()
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await MessageService.Emit($"support.{Self.Id}.typing", Self);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public async Task<SupportMessage[]> GetMessages()
|
|
||||||
{
|
|
||||||
return await SupportServerService.GetMessages(Self);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendMessage(string content)
|
|
||||||
{
|
|
||||||
var message = new SupportMessage()
|
|
||||||
{
|
|
||||||
Message = content
|
|
||||||
};
|
|
||||||
|
|
||||||
await SupportServerService.SendMessage(
|
|
||||||
Self,
|
|
||||||
message,
|
|
||||||
Self
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
MessageService.Unsubscribe($"support.{Self.Id}.message", this);
|
|
||||||
MessageService.Unsubscribe($"support.{Self.Id}.admintyping", this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Repositories;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Support;
|
|
||||||
|
|
||||||
public class SupportServerService : IDisposable
|
|
||||||
{
|
|
||||||
private SupportMessageRepository SupportMessageRepository;
|
|
||||||
private MessageService MessageService;
|
|
||||||
private UserRepository UserRepository;
|
|
||||||
private readonly IServiceScopeFactory ServiceScopeFactory;
|
|
||||||
private IServiceScope ServiceScope;
|
|
||||||
|
|
||||||
public SupportServerService(IServiceScopeFactory serviceScopeFactory)
|
|
||||||
{
|
|
||||||
ServiceScopeFactory = serviceScopeFactory;
|
|
||||||
|
|
||||||
Task.Run(Run);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendMessage(User r, SupportMessage message, User s, bool isSupport = false)
|
|
||||||
{
|
|
||||||
var recipient = UserRepository.Get().First(x => x.Id == r.Id);
|
|
||||||
var sender = UserRepository.Get().First(x => x.Id == s.Id);
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
message.CreatedAt = DateTime.UtcNow;
|
|
||||||
message.Sender = sender;
|
|
||||||
message.Recipient = recipient;
|
|
||||||
message.IsSupport = isSupport;
|
|
||||||
|
|
||||||
SupportMessageRepository.Add(message);
|
|
||||||
|
|
||||||
await MessageService.Emit($"support.{recipient.Id}.message", message);
|
|
||||||
|
|
||||||
if (!recipient.SupportPending)
|
|
||||||
{
|
|
||||||
recipient.SupportPending = true;
|
|
||||||
UserRepository.Update(recipient);
|
|
||||||
|
|
||||||
if (!message.IsSupport)
|
|
||||||
{
|
|
||||||
var systemMessage = new SupportMessage()
|
|
||||||
{
|
|
||||||
Recipient = recipient,
|
|
||||||
Sender = null,
|
|
||||||
IsSystem = true,
|
|
||||||
Message = "The support team has been notified. Please be patient"
|
|
||||||
};
|
|
||||||
|
|
||||||
SupportMessageRepository.Add(systemMessage);
|
|
||||||
|
|
||||||
await MessageService.Emit($"support.{recipient.Id}.message", systemMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
await MessageService.Emit($"support.new", recipient);
|
|
||||||
|
|
||||||
Logger.Info("Support ticket created: " + recipient.Id);
|
|
||||||
//TODO: Ping or so
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Error("Error sending message");
|
|
||||||
Logger.Error(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Close(User user)
|
|
||||||
{
|
|
||||||
var recipient = UserRepository.Get().First(x => x.Id == user.Id);
|
|
||||||
|
|
||||||
recipient.SupportPending = false;
|
|
||||||
UserRepository.Update(recipient);
|
|
||||||
|
|
||||||
var systemMessage = new SupportMessage()
|
|
||||||
{
|
|
||||||
Recipient = recipient,
|
|
||||||
Sender = null,
|
|
||||||
IsSystem = true,
|
|
||||||
Message = "The ticket is now closed. Type a message to open it again"
|
|
||||||
};
|
|
||||||
|
|
||||||
SupportMessageRepository.Add(systemMessage);
|
|
||||||
|
|
||||||
await MessageService.Emit($"support.{recipient.Id}.message", systemMessage);
|
|
||||||
await MessageService.Emit($"support.close", recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<SupportMessage[]> GetMessages(User r)
|
|
||||||
{
|
|
||||||
var recipient = UserRepository.Get().First(x => x.Id == r.Id);
|
|
||||||
|
|
||||||
var messages = SupportMessageRepository
|
|
||||||
.Get()
|
|
||||||
.Include(x => x.Recipient)
|
|
||||||
.Include(x => x.Sender)
|
|
||||||
.Where(x => x.Recipient.Id == recipient.Id)
|
|
||||||
.AsEnumerable()
|
|
||||||
.TakeLast(50)
|
|
||||||
.OrderBy(x => x.Id)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
return Task.FromResult(messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task Run()
|
|
||||||
{
|
|
||||||
ServiceScope = ServiceScopeFactory.CreateScope();
|
|
||||||
|
|
||||||
SupportMessageRepository = ServiceScope
|
|
||||||
.ServiceProvider
|
|
||||||
.GetRequiredService<SupportMessageRepository>();
|
|
||||||
|
|
||||||
MessageService = ServiceScope
|
|
||||||
.ServiceProvider
|
|
||||||
.GetRequiredService<MessageService>();
|
|
||||||
|
|
||||||
UserRepository = ServiceScope
|
|
||||||
.ServiceProvider
|
|
||||||
.GetRequiredService<UserRepository>();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
SupportMessageRepository.Dispose();
|
|
||||||
UserRepository.Dispose();
|
|
||||||
ServiceScope.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
134
Moonlight/App/Services/SupportChat/SupportChatAdminService.cs
Normal file
134
Moonlight/App/Services/SupportChat/SupportChatAdminService.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.SupportChat;
|
||||||
|
|
||||||
|
public class SupportChatAdminService
|
||||||
|
{
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
private readonly IdentityService IdentityService;
|
||||||
|
private readonly SupportChatServerService ServerService;
|
||||||
|
|
||||||
|
public Func<SupportChatMessage, Task>? OnMessage { get; set; }
|
||||||
|
public Func<string[], Task>? OnTypingChanged { get; set; }
|
||||||
|
|
||||||
|
private User? User;
|
||||||
|
private User Recipient = null!;
|
||||||
|
private readonly List<User> TypingUsers = new();
|
||||||
|
|
||||||
|
public SupportChatAdminService(
|
||||||
|
EventSystem eventSystem,
|
||||||
|
SupportChatServerService serverService,
|
||||||
|
IdentityService identityService)
|
||||||
|
{
|
||||||
|
Event = eventSystem;
|
||||||
|
ServerService = serverService;
|
||||||
|
IdentityService = identityService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start(User recipient)
|
||||||
|
{
|
||||||
|
User = await IdentityService.Get();
|
||||||
|
Recipient = recipient;
|
||||||
|
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
await Event.On<SupportChatMessage>($"supportChat.{Recipient.Id}.message", this, async message =>
|
||||||
|
{
|
||||||
|
if (OnMessage != null)
|
||||||
|
{
|
||||||
|
if(message.Sender != null && message.Sender.Id == User.Id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await OnMessage.Invoke(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Event.On<User>($"supportChat.{Recipient.Id}.typing", this, async user =>
|
||||||
|
{
|
||||||
|
await HandleTyping(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SupportChatMessage[]> GetMessages()
|
||||||
|
{
|
||||||
|
if (User == null)
|
||||||
|
return Array.Empty<SupportChatMessage>();
|
||||||
|
|
||||||
|
return await ServerService.GetMessages(Recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SupportChatMessage> SendMessage(string content)
|
||||||
|
{
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
return await ServerService.SendMessage(Recipient, content, User);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task HandleTyping(User user)
|
||||||
|
{
|
||||||
|
lock (TypingUsers)
|
||||||
|
{
|
||||||
|
if (!TypingUsers.Contains(user))
|
||||||
|
{
|
||||||
|
TypingUsers.Add(user);
|
||||||
|
|
||||||
|
if (OnTypingChanged != null)
|
||||||
|
{
|
||||||
|
OnTypingChanged.Invoke(
|
||||||
|
TypingUsers
|
||||||
|
.Where(x => x.Id != User!.Id)
|
||||||
|
.Select(x => $"{x.FirstName} {x.LastName}")
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
if (TypingUsers.Contains(user))
|
||||||
|
{
|
||||||
|
TypingUsers.Remove(user);
|
||||||
|
|
||||||
|
if (OnTypingChanged != null)
|
||||||
|
{
|
||||||
|
await OnTypingChanged.Invoke(
|
||||||
|
TypingUsers
|
||||||
|
.Where(x => x.Id != User!.Id)
|
||||||
|
.Select(x => $"{x.FirstName} {x.LastName}")
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendTyping()
|
||||||
|
{
|
||||||
|
await Event.Emit($"supportChat.{Recipient.Id}.typing", User);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Close()
|
||||||
|
{
|
||||||
|
await ServerService.CloseChat(Recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Dispose()
|
||||||
|
{
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
await Event.Off($"supportChat.{Recipient.Id}.message", this);
|
||||||
|
await Event.Off($"supportChat.{Recipient.Id}.typing", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
Moonlight/App/Services/SupportChat/SupportChatClientService.cs
Normal file
128
Moonlight/App/Services/SupportChat/SupportChatClientService.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using Logging.Net;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.SupportChat;
|
||||||
|
|
||||||
|
public class SupportChatClientService : IDisposable
|
||||||
|
{
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
private readonly IdentityService IdentityService;
|
||||||
|
private readonly SupportChatServerService ServerService;
|
||||||
|
|
||||||
|
public Func<SupportChatMessage, Task>? OnMessage { get; set; }
|
||||||
|
public Func<string[], Task>? OnTypingChanged { get; set; }
|
||||||
|
|
||||||
|
private User? User;
|
||||||
|
private readonly List<User> TypingUsers = new();
|
||||||
|
|
||||||
|
public SupportChatClientService(
|
||||||
|
EventSystem eventSystem,
|
||||||
|
SupportChatServerService serverService,
|
||||||
|
IdentityService identityService)
|
||||||
|
{
|
||||||
|
Event = eventSystem;
|
||||||
|
ServerService = serverService;
|
||||||
|
IdentityService = identityService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start()
|
||||||
|
{
|
||||||
|
User = await IdentityService.Get();
|
||||||
|
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
await Event.On<SupportChatMessage>($"supportChat.{User.Id}.message", this, async message =>
|
||||||
|
{
|
||||||
|
if (OnMessage != null)
|
||||||
|
{
|
||||||
|
if(message.Sender != null && message.Sender.Id == User.Id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await OnMessage.Invoke(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Event.On<User>($"supportChat.{User.Id}.typing", this, async user =>
|
||||||
|
{
|
||||||
|
await HandleTyping(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SupportChatMessage[]> GetMessages()
|
||||||
|
{
|
||||||
|
if (User == null)
|
||||||
|
return Array.Empty<SupportChatMessage>();
|
||||||
|
|
||||||
|
return await ServerService.GetMessages(User);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SupportChatMessage> SendMessage(string content)
|
||||||
|
{
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
return await ServerService.SendMessage(User, content, User);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task HandleTyping(User user)
|
||||||
|
{
|
||||||
|
lock (TypingUsers)
|
||||||
|
{
|
||||||
|
if (!TypingUsers.Contains(user))
|
||||||
|
{
|
||||||
|
TypingUsers.Add(user);
|
||||||
|
|
||||||
|
if (OnTypingChanged != null)
|
||||||
|
{
|
||||||
|
OnTypingChanged.Invoke(
|
||||||
|
TypingUsers
|
||||||
|
.Where(x => x.Id != User!.Id)
|
||||||
|
.Select(x => $"{x.FirstName} {x.LastName}")
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
if (TypingUsers.Contains(user))
|
||||||
|
{
|
||||||
|
TypingUsers.Remove(user);
|
||||||
|
|
||||||
|
if (OnTypingChanged != null)
|
||||||
|
{
|
||||||
|
await OnTypingChanged.Invoke(
|
||||||
|
TypingUsers
|
||||||
|
.Where(x => x.Id != User!.Id)
|
||||||
|
.Select(x => $"{x.FirstName} {x.LastName}")
|
||||||
|
.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendTyping()
|
||||||
|
{
|
||||||
|
await Event.Emit($"supportChat.{User!.Id}.typing", User);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Dispose()
|
||||||
|
{
|
||||||
|
if (User != null)
|
||||||
|
{
|
||||||
|
await Event.Off($"supportChat.{User.Id}.message", this);
|
||||||
|
await Event.Off($"supportChat.{User.Id}.typing", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
146
Moonlight/App/Services/SupportChat/SupportChatServerService.cs
Normal file
146
Moonlight/App/Services/SupportChat/SupportChatServerService.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.SupportChat;
|
||||||
|
|
||||||
|
public class SupportChatServerService
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory ServiceScopeFactory;
|
||||||
|
private readonly DateTimeService DateTimeService;
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
|
||||||
|
public SupportChatServerService(
|
||||||
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
DateTimeService dateTimeService,
|
||||||
|
EventSystem eventSystem)
|
||||||
|
{
|
||||||
|
ServiceScopeFactory = serviceScopeFactory;
|
||||||
|
DateTimeService = dateTimeService;
|
||||||
|
Event = eventSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<SupportChatMessage[]> GetMessages(User recipient)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
|
||||||
|
|
||||||
|
var messages = msgRepo
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Recipient)
|
||||||
|
.Include(x => x.Sender)
|
||||||
|
.Where(x => x.Recipient.Id == recipient.Id)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.AsEnumerable()
|
||||||
|
.Take(50)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return Task.FromResult(messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SupportChatMessage> SendMessage(User recipient, string content, User? sender, string? attachment = null)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
|
||||||
|
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||||
|
|
||||||
|
var message = new SupportChatMessage()
|
||||||
|
{
|
||||||
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
|
IsQuestion = false,
|
||||||
|
Sender = sender == null ? null : userRepo.Get().First(x => x.Id == sender.Id),
|
||||||
|
Recipient = userRepo.Get().First(x => x.Id == recipient.Id),
|
||||||
|
Answer = "",
|
||||||
|
Attachment = attachment ?? "",
|
||||||
|
Content = content,
|
||||||
|
UpdatedAt = DateTimeService.GetCurrent()
|
||||||
|
};
|
||||||
|
|
||||||
|
var finalMessage = msgRepo.Add(message);
|
||||||
|
|
||||||
|
await Event.Emit($"supportChat.{recipient.Id}.message", finalMessage);
|
||||||
|
await Event.Emit("supportChat.message", finalMessage);
|
||||||
|
|
||||||
|
if (!userRepo.Get().First(x => x.Id == recipient.Id).SupportPending)
|
||||||
|
{
|
||||||
|
var ticketStart = new SupportChatMessage()
|
||||||
|
{
|
||||||
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
|
IsQuestion = false,
|
||||||
|
Sender = null,
|
||||||
|
Recipient = userRepo.Get().First(x => x.Id == recipient.Id),
|
||||||
|
Answer = "",
|
||||||
|
Attachment = "",
|
||||||
|
Content = "Support ticket open", //TODO: Config
|
||||||
|
UpdatedAt = DateTimeService.GetCurrent()
|
||||||
|
};
|
||||||
|
|
||||||
|
var ticketStartFinal = msgRepo.Add(ticketStart);
|
||||||
|
|
||||||
|
var user = userRepo.Get().First(x => x.Id == recipient.Id);
|
||||||
|
user.SupportPending = true;
|
||||||
|
userRepo.Update(user);
|
||||||
|
|
||||||
|
await Event.Emit($"supportChat.{recipient.Id}.message", ticketStartFinal);
|
||||||
|
await Event.Emit("supportChat.message", ticketStartFinal);
|
||||||
|
await Event.Emit("supportChat.new", recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Dictionary<User, SupportChatMessage?>> GetOpenChats()
|
||||||
|
{
|
||||||
|
var result = new Dictionary<User, SupportChatMessage?>();
|
||||||
|
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||||
|
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
|
||||||
|
|
||||||
|
foreach (var user in userRepo.Get().Where(x => x.SupportPending).ToArray())
|
||||||
|
{
|
||||||
|
var lastMessage = msgRepo
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Recipient)
|
||||||
|
.Include(x => x.Sender)
|
||||||
|
.Where(x => x.Recipient.Id == user.Id)
|
||||||
|
.OrderByDescending(x => x.CreatedAt)
|
||||||
|
.AsEnumerable()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
result.Add(user, lastMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CloseChat(User recipient)
|
||||||
|
{
|
||||||
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var msgRepo = scope.ServiceProvider.GetRequiredService<Repository<SupportChatMessage>>();
|
||||||
|
var userRepo = scope.ServiceProvider.GetRequiredService<Repository<User>>();
|
||||||
|
|
||||||
|
var ticketEnd = new SupportChatMessage()
|
||||||
|
{
|
||||||
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
|
IsQuestion = false,
|
||||||
|
Sender = null,
|
||||||
|
Recipient = userRepo.Get().First(x => x.Id == recipient.Id),
|
||||||
|
Answer = "",
|
||||||
|
Attachment = "",
|
||||||
|
Content = "Support ticket closed", //TODO: Config
|
||||||
|
UpdatedAt = DateTimeService.GetCurrent()
|
||||||
|
};
|
||||||
|
|
||||||
|
var ticketEndFinal = msgRepo.Add(ticketEnd);
|
||||||
|
|
||||||
|
var user = userRepo.Get().First(x => x.Id == recipient.Id);
|
||||||
|
user.SupportPending = false;
|
||||||
|
userRepo.Update(user);
|
||||||
|
|
||||||
|
await Event.Emit($"supportChat.{recipient.Id}.message", ticketEndFinal);
|
||||||
|
await Event.Emit("supportChat.message", ticketEndFinal);
|
||||||
|
await Event.Emit("supportChat.close", recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ public class UserService
|
|||||||
private readonly MailService MailService;
|
private readonly MailService MailService;
|
||||||
private readonly IdentityService IdentityService;
|
private readonly IdentityService IdentityService;
|
||||||
private readonly IpLocateService IpLocateService;
|
private readonly IpLocateService IpLocateService;
|
||||||
|
private readonly DateTimeService DateTimeService;
|
||||||
|
|
||||||
private readonly string JwtSecret;
|
private readonly string JwtSecret;
|
||||||
|
|
||||||
@@ -29,7 +30,9 @@ public class UserService
|
|||||||
SecurityLogService securityLogService,
|
SecurityLogService securityLogService,
|
||||||
AuditLogService auditLogService,
|
AuditLogService auditLogService,
|
||||||
MailService mailService,
|
MailService mailService,
|
||||||
IdentityService identityService, IpLocateService ipLocateService)
|
IdentityService identityService,
|
||||||
|
IpLocateService ipLocateService,
|
||||||
|
DateTimeService dateTimeService)
|
||||||
{
|
{
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
TotpService = totpService;
|
TotpService = totpService;
|
||||||
@@ -38,6 +41,7 @@ public class UserService
|
|||||||
MailService = mailService;
|
MailService = mailService;
|
||||||
IdentityService = identityService;
|
IdentityService = identityService;
|
||||||
IpLocateService = ipLocateService;
|
IpLocateService = ipLocateService;
|
||||||
|
DateTimeService = dateTimeService;
|
||||||
|
|
||||||
JwtSecret = configService
|
JwtSecret = configService
|
||||||
.GetSection("Moonlight")
|
.GetSection("Moonlight")
|
||||||
@@ -70,12 +74,12 @@ public class UserService
|
|||||||
LastName = lastname,
|
LastName = lastname,
|
||||||
State = "",
|
State = "",
|
||||||
Status = UserStatus.Unverified,
|
Status = UserStatus.Unverified,
|
||||||
CreatedAt = DateTime.UtcNow,
|
CreatedAt = DateTimeService.GetCurrent(),
|
||||||
DiscordId = 0,
|
DiscordId = 0,
|
||||||
TotpEnabled = false,
|
TotpEnabled = false,
|
||||||
TotpSecret = "",
|
TotpSecret = "",
|
||||||
UpdatedAt = DateTime.UtcNow,
|
UpdatedAt = DateTimeService.GetCurrent(),
|
||||||
TokenValidTime = DateTime.Now.AddDays(-5)
|
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5)
|
||||||
});
|
});
|
||||||
|
|
||||||
await MailService.SendMail(user!, "register", values => {});
|
await MailService.SendMail(user!, "register", values => {});
|
||||||
@@ -168,7 +172,7 @@ public class UserService
|
|||||||
public async Task ChangePassword(User user, string password, bool isSystemAction = false)
|
public async Task ChangePassword(User user, string password, bool isSystemAction = false)
|
||||||
{
|
{
|
||||||
user.Password = BCrypt.Net.BCrypt.HashPassword(password);
|
user.Password = BCrypt.Net.BCrypt.HashPassword(password);
|
||||||
user.TokenValidTime = DateTime.Now;
|
user.TokenValidTime = DateTimeService.GetCurrent();
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
if (isSystemAction)
|
if (isSystemAction)
|
||||||
@@ -244,8 +248,8 @@ public class UserService
|
|||||||
var token = JwtBuilder.Create()
|
var token = JwtBuilder.Create()
|
||||||
.WithAlgorithm(new HMACSHA256Algorithm())
|
.WithAlgorithm(new HMACSHA256Algorithm())
|
||||||
.WithSecret(JwtSecret)
|
.WithSecret(JwtSecret)
|
||||||
.AddClaim("exp", DateTimeOffset.UtcNow.AddDays(10).ToUnixTimeSeconds())
|
.AddClaim("exp", new DateTimeOffset(DateTimeService.GetCurrent().AddDays(10)).ToUnixTimeSeconds())
|
||||||
.AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
.AddClaim("iat", DateTimeService.GetCurrentUnixSeconds())
|
||||||
.AddClaim("userid", user.Id)
|
.AddClaim("userid", user.Id)
|
||||||
.Encode();
|
.Encode();
|
||||||
|
|
||||||
|
|||||||
191
Moonlight/App/Services/WebSpaceService.cs
Normal file
191
Moonlight/App/Services/WebSpaceService.cs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
using Logging.Net;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moonlight.App.ApiClients.CloudPanel;
|
||||||
|
using Moonlight.App.ApiClients.CloudPanel.Requests;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Helpers.Files;
|
||||||
|
using Moonlight.App.Models.Plesk.Requests;
|
||||||
|
using Moonlight.App.Models.Plesk.Resources;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class WebSpaceService
|
||||||
|
{
|
||||||
|
private readonly Repository<CloudPanel> CloudPanelRepository;
|
||||||
|
private readonly Repository<WebSpace> WebSpaceRepository;
|
||||||
|
private readonly Repository<MySqlDatabase> DatabaseRepository;
|
||||||
|
|
||||||
|
private readonly CloudPanelApiHelper CloudPanelApiHelper;
|
||||||
|
|
||||||
|
public WebSpaceService(Repository<CloudPanel> cloudPanelRepository, Repository<WebSpace> webSpaceRepository, CloudPanelApiHelper cloudPanelApiHelper, Repository<MySqlDatabase> databaseRepository)
|
||||||
|
{
|
||||||
|
CloudPanelRepository = cloudPanelRepository;
|
||||||
|
WebSpaceRepository = webSpaceRepository;
|
||||||
|
CloudPanelApiHelper = cloudPanelApiHelper;
|
||||||
|
DatabaseRepository = databaseRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WebSpace> Create(string domain, User owner, CloudPanel? ps = null)
|
||||||
|
{
|
||||||
|
if (WebSpaceRepository.Get().Any(x => x.Domain == domain))
|
||||||
|
throw new DisplayException("A website with this domain does already exist");
|
||||||
|
|
||||||
|
var cloudPanel = ps ?? CloudPanelRepository.Get().First();
|
||||||
|
|
||||||
|
var ftpLogin = domain.Replace(".", "_");
|
||||||
|
var ftpPassword = StringHelper.GenerateString(16);
|
||||||
|
|
||||||
|
var phpVersion = "8.1"; // TODO: Add config option or smth
|
||||||
|
|
||||||
|
var w = new WebSpace()
|
||||||
|
{
|
||||||
|
CloudPanel = cloudPanel,
|
||||||
|
Owner = owner,
|
||||||
|
Domain = domain,
|
||||||
|
UserName = ftpLogin,
|
||||||
|
Password = ftpPassword,
|
||||||
|
VHostTemplate = "Generic" //TODO: Implement as select option
|
||||||
|
};
|
||||||
|
|
||||||
|
var webSpace = WebSpaceRepository.Add(w);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CloudPanelApiHelper.Post(cloudPanel, "site/php", new AddPhpSite()
|
||||||
|
{
|
||||||
|
VHostTemplate = w.VHostTemplate,
|
||||||
|
DomainName = w.Domain,
|
||||||
|
PhpVersion = phpVersion,
|
||||||
|
SiteUser = w.UserName,
|
||||||
|
SiteUserPassword = w.Password
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
WebSpaceRepository.Delete(webSpace);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return webSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(WebSpace w)
|
||||||
|
{
|
||||||
|
var website = EnsureData(w);
|
||||||
|
|
||||||
|
await CloudPanelApiHelper.Delete(website.CloudPanel, $"site/{website.Domain}", null);
|
||||||
|
|
||||||
|
WebSpaceRepository.Delete(website);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsHostUp(CloudPanel cloudPanel)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await CloudPanelApiHelper.Post(cloudPanel, "", null);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (CloudPanelException e)
|
||||||
|
{
|
||||||
|
if (e.StatusCode == 404)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsHostUp(WebSpace w)
|
||||||
|
{
|
||||||
|
var webSpace = EnsureData(w);
|
||||||
|
|
||||||
|
return await IsHostUp(webSpace.CloudPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task IssueSslCertificate(WebSpace w)
|
||||||
|
{
|
||||||
|
var webspace = EnsureData(w);
|
||||||
|
|
||||||
|
await CloudPanelApiHelper.Post(webspace.CloudPanel, "letsencrypt/install/certificate", new InstallLetsEncrypt()
|
||||||
|
{
|
||||||
|
DomainName = webspace.Domain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Databases
|
||||||
|
|
||||||
|
public Task<MySqlDatabase[]> GetDatabases(WebSpace w)
|
||||||
|
{
|
||||||
|
return Task.FromResult(WebSpaceRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.Databases)
|
||||||
|
.First(x => x.Id == w.Id)
|
||||||
|
.Databases.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateDatabase(WebSpace w, string name, string password)
|
||||||
|
{
|
||||||
|
if (DatabaseRepository.Get().Any(x => x.UserName == name))
|
||||||
|
throw new DisplayException("A database with this name does already exist");
|
||||||
|
|
||||||
|
var webspace = EnsureData(w);
|
||||||
|
|
||||||
|
var database = new MySqlDatabase()
|
||||||
|
{
|
||||||
|
UserName = name,
|
||||||
|
Password = password
|
||||||
|
};
|
||||||
|
|
||||||
|
await CloudPanelApiHelper.Post(webspace.CloudPanel, "db", new AddDatabase()
|
||||||
|
{
|
||||||
|
DomainName = webspace.Domain,
|
||||||
|
DatabaseName = database.UserName,
|
||||||
|
DatabaseUserName = database.UserName,
|
||||||
|
DatabaseUserPassword = database.Password
|
||||||
|
});
|
||||||
|
|
||||||
|
webspace.Databases.Add(database);
|
||||||
|
WebSpaceRepository.Update(webspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteDatabase(WebSpace w, MySqlDatabase database)
|
||||||
|
{
|
||||||
|
var webspace = EnsureData(w);
|
||||||
|
|
||||||
|
await CloudPanelApiHelper.Delete(webspace.CloudPanel, $"db/{database.UserName}", null);
|
||||||
|
|
||||||
|
webspace.Databases.Remove(database);
|
||||||
|
WebSpaceRepository.Update(webspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public Task<FileAccess> CreateFileAccess(WebSpace w)
|
||||||
|
{
|
||||||
|
var webspace = EnsureData(w);
|
||||||
|
|
||||||
|
return Task.FromResult<FileAccess>(
|
||||||
|
new SftpFileAccess(webspace.CloudPanel.Host, webspace.UserName, webspace.Password, 22, true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebSpace EnsureData(WebSpace webSpace)
|
||||||
|
{
|
||||||
|
if (webSpace.CloudPanel == null || webSpace.Owner == null)
|
||||||
|
return WebSpaceRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.CloudPanel)
|
||||||
|
.Include(x => x.Owner)
|
||||||
|
.First(x => x.Id == webSpace.Id);
|
||||||
|
|
||||||
|
return webSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,383 +0,0 @@
|
|||||||
using Logging.Net;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Exceptions;
|
|
||||||
using Moonlight.App.Helpers;
|
|
||||||
using Moonlight.App.Helpers.Files;
|
|
||||||
using Moonlight.App.Models.Plesk.Requests;
|
|
||||||
using Moonlight.App.Models.Plesk.Resources;
|
|
||||||
using Moonlight.App.Repositories;
|
|
||||||
using FileAccess = Moonlight.App.Helpers.Files.FileAccess;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
|
||||||
|
|
||||||
public class WebsiteService
|
|
||||||
{
|
|
||||||
private readonly WebsiteRepository WebsiteRepository;
|
|
||||||
private readonly PleskServerRepository PleskServerRepository;
|
|
||||||
private readonly PleskApiHelper PleskApiHelper;
|
|
||||||
private readonly UserRepository UserRepository;
|
|
||||||
|
|
||||||
public WebsiteService(WebsiteRepository websiteRepository, PleskApiHelper pleskApiHelper, PleskServerRepository pleskServerRepository, UserRepository userRepository)
|
|
||||||
{
|
|
||||||
WebsiteRepository = websiteRepository;
|
|
||||||
PleskApiHelper = pleskApiHelper;
|
|
||||||
PleskServerRepository = pleskServerRepository;
|
|
||||||
UserRepository = userRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Website> Create(string baseDomain, User owner, PleskServer? ps = null)
|
|
||||||
{
|
|
||||||
if (WebsiteRepository.Get().Any(x => x.BaseDomain == baseDomain))
|
|
||||||
throw new DisplayException("A website with this domain does already exist");
|
|
||||||
|
|
||||||
var pleskServer = ps ?? PleskServerRepository.Get().First();
|
|
||||||
|
|
||||||
var ftpLogin = baseDomain;
|
|
||||||
var ftpPassword = StringHelper.GenerateString(16);
|
|
||||||
|
|
||||||
var w = new Website()
|
|
||||||
{
|
|
||||||
PleskServer = pleskServer,
|
|
||||||
Owner = owner,
|
|
||||||
BaseDomain = baseDomain,
|
|
||||||
PleskId = 0,
|
|
||||||
FtpPassword = ftpPassword,
|
|
||||||
FtpLogin = ftpLogin
|
|
||||||
};
|
|
||||||
|
|
||||||
var website = WebsiteRepository.Add(w);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var id = await GetAdminAccount(pleskServer);
|
|
||||||
|
|
||||||
var result = await PleskApiHelper.Post<CreateResult>(pleskServer, "domains", new CreateDomain()
|
|
||||||
{
|
|
||||||
Description = $"moonlight website {website.Id}",
|
|
||||||
Name = baseDomain,
|
|
||||||
HostingType = "virtual",
|
|
||||||
Plan = new()
|
|
||||||
{
|
|
||||||
Name = "Unlimited"
|
|
||||||
},
|
|
||||||
HostingSettings = new()
|
|
||||||
{
|
|
||||||
FtpLogin = ftpLogin,
|
|
||||||
FtpPassword = ftpPassword
|
|
||||||
},
|
|
||||||
OwnerClient = new()
|
|
||||||
{
|
|
||||||
Id = id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
website.PleskId = result.Id;
|
|
||||||
|
|
||||||
WebsiteRepository.Update(website);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
WebsiteRepository.Delete(website);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return website;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
await PleskApiHelper.Delete(website.PleskServer, $"domains/{w.PleskId}", null);
|
|
||||||
|
|
||||||
WebsiteRepository.Delete(website);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> IsHostUp(PleskServer pleskServer)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var res = await PleskApiHelper.Get<ServerStatus>(pleskServer, "server");
|
|
||||||
|
|
||||||
if (res != null)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> IsHostUp(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var res = await PleskApiHelper.Get<ServerStatus>(website.PleskServer, "server");
|
|
||||||
|
|
||||||
if (res != null)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Get host
|
|
||||||
|
|
||||||
public async Task<string> GetHost(PleskServer pleskServer)
|
|
||||||
{
|
|
||||||
return (await PleskApiHelper.Get<ServerStatus>(pleskServer, "server")).Hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetHost(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
return await GetHost(website.PleskServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private async Task<int> GetAdminAccount(PleskServer pleskServer)
|
|
||||||
{
|
|
||||||
var users = await PleskApiHelper.Get<Client[]>(pleskServer, "clients");
|
|
||||||
|
|
||||||
var user = users.FirstOrDefault(x => x.Type == "admin");
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
throw new DisplayException("No admin account in plesk found");
|
|
||||||
|
|
||||||
return user.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region SSL
|
|
||||||
public async Task<string[]> GetSslCertificates(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
var certs = new List<string>();
|
|
||||||
|
|
||||||
var data = await ExecuteCli(website.PleskServer, "certificate", p =>
|
|
||||||
{
|
|
||||||
p.Add("-l");
|
|
||||||
p.Add("-domain");
|
|
||||||
p.Add(w.BaseDomain);
|
|
||||||
});
|
|
||||||
|
|
||||||
string[] lines = data.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
foreach (string line in lines)
|
|
||||||
{
|
|
||||||
if (line.Contains("Lets Encrypt"))
|
|
||||||
{
|
|
||||||
string[] parts = line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
if(parts.Length > 6)
|
|
||||||
certs.Add($"{parts[4]} {parts[5]} {parts[6]}");
|
|
||||||
}
|
|
||||||
else if (line.Contains("Listing of SSL/TLS certificates repository was successful"))
|
|
||||||
{
|
|
||||||
// This line indicates the end of the certificate listing, so we can stop parsing
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return certs.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateSslCertificate(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
await ExecuteCli(website.PleskServer, "extension", p =>
|
|
||||||
{
|
|
||||||
p.Add("--exec");
|
|
||||||
p.Add("letsencrypt");
|
|
||||||
p.Add("cli.php");
|
|
||||||
p.Add("-d");
|
|
||||||
p.Add(website.BaseDomain);
|
|
||||||
p.Add("-m");
|
|
||||||
p.Add(website.Owner.Email);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteSslCertificate(Website w, string name)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ExecuteCli(website.PleskServer, "site", p =>
|
|
||||||
{
|
|
||||||
p.Add("-u");
|
|
||||||
p.Add(website.BaseDomain);
|
|
||||||
p.Add("-ssl");
|
|
||||||
p.Add("false");
|
|
||||||
});
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ExecuteCli(website.PleskServer, "certificate", p =>
|
|
||||||
{
|
|
||||||
p.Add("--remove");
|
|
||||||
p.Add(name);
|
|
||||||
p.Add("-domain");
|
|
||||||
p.Add(website.BaseDomain);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Warn("Error removing ssl certificate");
|
|
||||||
Logger.Warn(e);
|
|
||||||
|
|
||||||
throw new DisplayException("An unknown error occured while removing ssl certificate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (DisplayException)
|
|
||||||
{
|
|
||||||
// Redirect all display exception to soft error handler
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.Warn("Error disabling ssl certificate");
|
|
||||||
Logger.Warn(e);
|
|
||||||
|
|
||||||
throw new DisplayException("An unknown error occured while disabling ssl certificate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Databases
|
|
||||||
|
|
||||||
public async Task<Models.Plesk.Resources.Database[]> GetDatabases(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
var dbs = await PleskApiHelper.Get<Models.Plesk.Resources.Database[]>(
|
|
||||||
website.PleskServer,
|
|
||||||
$"databases?domain={w.BaseDomain}"
|
|
||||||
);
|
|
||||||
|
|
||||||
return dbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CreateDatabase(Website w, string name, string password)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
var server = await GetDefaultDatabaseServer(website);
|
|
||||||
|
|
||||||
if (server == null)
|
|
||||||
throw new DisplayException("No database server marked as default found");
|
|
||||||
|
|
||||||
var dbReq = new CreateDatabase()
|
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Type = "mysql",
|
|
||||||
ParentDomain = new()
|
|
||||||
{
|
|
||||||
Name = website.BaseDomain
|
|
||||||
},
|
|
||||||
ServerId = server.Id
|
|
||||||
};
|
|
||||||
|
|
||||||
var db = await PleskApiHelper.Post<Models.Plesk.Resources.Database>(website.PleskServer, "databases", dbReq);
|
|
||||||
|
|
||||||
if (db == null)
|
|
||||||
throw new DisplayException("Unable to create database via api");
|
|
||||||
|
|
||||||
var dbUserReq = new CreateDatabaseUser()
|
|
||||||
{
|
|
||||||
DatabaseId = db.Id,
|
|
||||||
Login = name,
|
|
||||||
Password = password
|
|
||||||
};
|
|
||||||
|
|
||||||
await PleskApiHelper.Post(website.PleskServer, "dbusers", dbUserReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeleteDatabase(Website w, Models.Plesk.Resources.Database database)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
var dbUsers = await PleskApiHelper.Get<DatabaseUser[]>(
|
|
||||||
website.PleskServer,
|
|
||||||
$"dbusers?dbId={database.Id}"
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach (var dbUser in dbUsers)
|
|
||||||
{
|
|
||||||
await PleskApiHelper.Delete(website.PleskServer, $"dbusers/{dbUser.Id}", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
await PleskApiHelper.Delete(website.PleskServer, $"databases/{database.Id}", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DatabaseServer?> GetDefaultDatabaseServer(PleskServer pleskServer)
|
|
||||||
{
|
|
||||||
var dbServers = await PleskApiHelper.Get<DatabaseServer[]>(pleskServer, "dbservers");
|
|
||||||
|
|
||||||
return dbServers.FirstOrDefault(x => x.IsDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DatabaseServer?> GetDefaultDatabaseServer(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
|
|
||||||
return await GetDefaultDatabaseServer(website.PleskServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
public async Task<FileAccess> CreateFileAccess(Website w)
|
|
||||||
{
|
|
||||||
var website = EnsureData(w);
|
|
||||||
var host = await GetHost(website.PleskServer);
|
|
||||||
|
|
||||||
return new FtpFileAccess(host, 21, website.FtpLogin, website.FtpPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> ExecuteCli(
|
|
||||||
PleskServer server,
|
|
||||||
string cli, Action<List<string>>? parameters = null,
|
|
||||||
Action<Dictionary<string, string>>? variables = null
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var p = new List<string>();
|
|
||||||
var v = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
parameters?.Invoke(p);
|
|
||||||
variables?.Invoke(v);
|
|
||||||
|
|
||||||
var req = new CliCall()
|
|
||||||
{
|
|
||||||
Env = v,
|
|
||||||
Params = p
|
|
||||||
};
|
|
||||||
|
|
||||||
var res = await PleskApiHelper.Post<CliResult>(server, $"cli/{cli}/call", req);
|
|
||||||
|
|
||||||
return res.Stdout;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Website EnsureData(Website website)
|
|
||||||
{
|
|
||||||
if (website.PleskServer == null || website.Owner == null)
|
|
||||||
return WebsiteRepository
|
|
||||||
.Get()
|
|
||||||
.Include(x => x.PleskServer)
|
|
||||||
.Include(x => x.Owner)
|
|
||||||
.First(x => x.Id == website.Id);
|
|
||||||
|
|
||||||
return website;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,10 +19,12 @@
|
|||||||
<PackageReference Include="CloudFlare.Client" Version="6.1.4" />
|
<PackageReference Include="CloudFlare.Client" Version="6.1.4" />
|
||||||
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
|
<PackageReference Include="CurrieTechnologies.Razor.SweetAlert2" Version="5.4.0" />
|
||||||
<PackageReference Include="Discord.Net" Version="3.10.0" />
|
<PackageReference Include="Discord.Net" Version="3.10.0" />
|
||||||
|
<PackageReference Include="Discord.Net.Webhook" Version="3.10.0" />
|
||||||
<PackageReference Include="FluentFTP" Version="46.0.2" />
|
<PackageReference Include="FluentFTP" Version="46.0.2" />
|
||||||
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
|
<PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
|
||||||
<PackageReference Include="JWT" Version="10.0.2" />
|
<PackageReference Include="JWT" Version="10.0.2" />
|
||||||
<PackageReference Include="Logging.Net" Version="1.1.0" />
|
<PackageReference Include="Logging.Net" Version="1.1.0" />
|
||||||
|
<PackageReference Include="MailKit" Version="4.0.0" />
|
||||||
<PackageReference Include="Mappy.Net" Version="1.0.2" />
|
<PackageReference Include="Mappy.Net" Version="1.0.2" />
|
||||||
<PackageReference Include="Markdig" Version="0.31.0" />
|
<PackageReference Include="Markdig" Version="0.31.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
|
||||||
@@ -41,6 +43,7 @@
|
|||||||
<PackageReference Include="PteroConsole.NET" Version="1.0.4" />
|
<PackageReference Include="PteroConsole.NET" Version="1.0.4" />
|
||||||
<PackageReference Include="QRCoder" Version="1.4.3" />
|
<PackageReference Include="QRCoder" Version="1.4.3" />
|
||||||
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
|
<PackageReference Include="RestSharp" Version="109.0.0-preview.1" />
|
||||||
|
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
||||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||||
<PackageReference Include="XtermBlazor" Version="1.6.1" />
|
<PackageReference Include="XtermBlazor" Version="1.6.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -67,6 +70,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
|
||||||
<Folder Include="App\Http\Middleware" />
|
<Folder Include="App\Http\Middleware" />
|
||||||
<Folder Include="App\Models\Daemon\Requests" />
|
<Folder Include="App\Models\Daemon\Requests" />
|
||||||
<Folder Include="App\Models\Google\Resources" />
|
<Folder Include="App\Models\Google\Resources" />
|
||||||
|
|||||||
@@ -1,29 +1,54 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@using Moonlight.App.Services
|
||||||
@namespace Moonlight.Pages
|
@namespace Moonlight.Pages
|
||||||
|
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@{
|
@{
|
||||||
Layout = "_Layout";
|
Layout = "_Layout";
|
||||||
}
|
}
|
||||||
|
|
||||||
<component type="typeof(BlazorApp)" render-mode="ServerPrerendered" />
|
<component type="typeof(BlazorApp)" render-mode="ServerPrerendered"/>
|
||||||
|
|
||||||
<div id="components-reconnect-modal" class="my-reconnect-modal components-reconnect-hide">
|
<div id="components-reconnect-modal" class="my-reconnect-modal components-reconnect-hide">
|
||||||
<div class="show">
|
<div class="show">
|
||||||
<p>
|
<div class="modal d-block">
|
||||||
<br/>
|
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||||
Connecting to moonlight servers
|
<div class="modal-content">
|
||||||
</p>
|
<img src="/assets/media/svg/loading.svg" class="card-img-top w-25 mx-auto pt-5" alt="...">
|
||||||
|
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||||
|
<h2>@(SmartTranslateService.Translate("Your connection has been paused"))</h2>
|
||||||
|
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("We paused your connection because of inactivity. The resume just focus the tab and wait a few seconds"))</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="failed">
|
<div class="failed">
|
||||||
<p>
|
<div class="modal d-block">
|
||||||
<br />
|
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||||
Connection to moonlight servers failed
|
<div class="modal-content">
|
||||||
</p>
|
<img src="/assets/media/svg/serverdown.svg" class="card-img-top w-25 mx-auto pt-5" alt="...">
|
||||||
|
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||||
|
<h2>@(SmartTranslateService.Translate("Failed to reconnect to the moonlight servers"))</h2>
|
||||||
|
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("We were unable to reconnect to moonlight. Please refresh the page"))</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rejected">
|
<div class="rejected">
|
||||||
<p>
|
<div class="modal d-block">
|
||||||
<br />
|
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||||
Connection to moonlight servers rejected
|
<div class="modal-content">
|
||||||
</p>
|
<img src="/assets/media/svg/serverdown.svg" class="card-img-top w-25 mx-auto pt-5" alt="...">
|
||||||
|
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||||
|
<h2>@(SmartTranslateService.Translate("Failed to reconnect to the moonlight servers. The connection has been rejected"))</h2>
|
||||||
|
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("We were unable to reconnect to moonlight. Most of the time this is caused by an update of moonlight. Please refresh the page"))</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using Moonlight.App.Extensions
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@namespace Moonlight.Pages
|
@namespace Moonlight.Pages
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
|
||||||
@inject ConfigService ConfigService
|
@inject ConfigService ConfigService
|
||||||
|
@inject LoadingMessageRepository LoadingMessageRepository
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var headerConfig = ConfigService
|
var headerConfig = ConfigService
|
||||||
@@ -51,7 +54,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<base href="~/"/>
|
<base href="~/"/>
|
||||||
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
|
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered"/>
|
||||||
|
|
||||||
<title>Loading</title>
|
<title>Loading</title>
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
@@ -73,9 +76,13 @@
|
|||||||
<div class="app-page-loader flex-column">
|
<div class="app-page-loader flex-column">
|
||||||
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-25px"/>
|
<img alt="Logo" src="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg" class="h-25px"/>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var loadingMessage = LoadingMessageRepository.Get().Random();
|
||||||
|
}
|
||||||
|
|
||||||
<div class="d-flex align-items-center mt-5">
|
<div class="d-flex align-items-center mt-5">
|
||||||
<span class="spinner-border text-primary" role="status"></span>
|
<span class="spinner-border text-primary" role="status"></span>
|
||||||
<span class="text-muted fs-6 fw-semibold ms-5">CHANGEME</span>
|
<span class="text-muted fs-6 fw-semibold ms-5">@(loadingMessage.Message)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -93,25 +100,18 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
|
||||||
|
|
||||||
<script src="/assets/js/xtermAddons.js"></script>
|
|
||||||
|
|
||||||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
|
||||||
<script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
|
<script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
|
||||||
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
<script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
|
||||||
<script src="/_content/BlazorMonaco/jsInterop.js"></script>
|
<script src="/_content/BlazorMonaco/jsInterop.js"></script>
|
||||||
<script src="/assets/js/monacoTheme.js"></script>
|
|
||||||
|
|
||||||
<script src="/assets/js/scripts.bundle.js"></script>
|
<script src="/assets/js/scripts.bundle.js"></script>
|
||||||
<script src="/assets/js/flashbang.js"></script>
|
|
||||||
<script src="/assets/js/cookieUtils.js"></script>
|
|
||||||
<script src="/assets/js/clipboard.js"></script>
|
|
||||||
<script src="/assets/js/toastUtils.js"></script>
|
|
||||||
<script src="/assets/js/alertUtils.js"></script>
|
|
||||||
<script src="/assets/js/utils.js"></script>
|
|
||||||
<script src="/assets/js/loggingUtils.js"></script>
|
|
||||||
<script src="/assets/js/snow.js"></script>
|
|
||||||
<script src="/assets/js/recaptcha.js"></script>
|
|
||||||
<script src="/assets/js/moonlight.js"></script>
|
<script src="/assets/js/moonlight.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
moonlight.loading.registerXterm();
|
||||||
|
</script>
|
||||||
|
|
||||||
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
|
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
|
||||||
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
|
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ using BlazorDownloadFile;
|
|||||||
using BlazorTable;
|
using BlazorTable;
|
||||||
using CurrieTechnologies.Razor.SweetAlert2;
|
using CurrieTechnologies.Razor.SweetAlert2;
|
||||||
using Logging.Net;
|
using Logging.Net;
|
||||||
|
using Moonlight.App.ApiClients.CloudPanel;
|
||||||
using Moonlight.App.Database;
|
using Moonlight.App.Database;
|
||||||
|
using Moonlight.App.Events;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.LogMigrator;
|
using Moonlight.App.LogMigrator;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
@@ -17,7 +19,7 @@ using Moonlight.App.Services.Notifications;
|
|||||||
using Moonlight.App.Services.OAuth2;
|
using Moonlight.App.Services.OAuth2;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
using Moonlight.App.Services.Statistics;
|
using Moonlight.App.Services.Statistics;
|
||||||
using Moonlight.App.Services.Support;
|
using Moonlight.App.Services.SupportChat;
|
||||||
|
|
||||||
namespace Moonlight
|
namespace Moonlight
|
||||||
{
|
{
|
||||||
@@ -41,7 +43,13 @@ namespace Moonlight
|
|||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor()
|
||||||
|
.AddHubOptions(options =>
|
||||||
|
{
|
||||||
|
options.MaximumReceiveMessageSize = 10000000;
|
||||||
|
options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
|
||||||
|
options.HandshakeTimeout = TimeSpan.FromSeconds(10);
|
||||||
|
});
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
// Databases
|
// Databases
|
||||||
@@ -54,23 +62,20 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<ServerRepository>();
|
builder.Services.AddScoped<ServerRepository>();
|
||||||
builder.Services.AddScoped<ServerBackupRepository>();
|
builder.Services.AddScoped<ServerBackupRepository>();
|
||||||
builder.Services.AddScoped<ImageRepository>();
|
builder.Services.AddScoped<ImageRepository>();
|
||||||
builder.Services.AddScoped<SupportMessageRepository>();
|
|
||||||
builder.Services.AddScoped<DomainRepository>();
|
builder.Services.AddScoped<DomainRepository>();
|
||||||
builder.Services.AddScoped<SharedDomainRepository>();
|
builder.Services.AddScoped<SharedDomainRepository>();
|
||||||
builder.Services.AddScoped<RevokeRepository>();
|
builder.Services.AddScoped<RevokeRepository>();
|
||||||
builder.Services.AddScoped<NotificationRepository>();
|
builder.Services.AddScoped<NotificationRepository>();
|
||||||
builder.Services.AddScoped<DdosAttackRepository>();
|
builder.Services.AddScoped<DdosAttackRepository>();
|
||||||
builder.Services.AddScoped<SubscriptionRepository>();
|
builder.Services.AddScoped<SubscriptionRepository>();
|
||||||
builder.Services.AddScoped<PleskServerRepository>();
|
|
||||||
builder.Services.AddScoped<WebsiteRepository>();
|
|
||||||
builder.Services.AddScoped<LoadingMessageRepository>();
|
builder.Services.AddScoped<LoadingMessageRepository>();
|
||||||
builder.Services.AddScoped<NewsEntryRepository>();
|
builder.Services.AddScoped<NewsEntryRepository>();
|
||||||
|
builder.Services.AddScoped<NodeAllocationRepository>();
|
||||||
builder.Services.AddScoped<StatisticsRepository>();
|
builder.Services.AddScoped<StatisticsRepository>();
|
||||||
|
|
||||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
builder.Services.AddScoped<AuditLogEntryRepository>();
|
||||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
||||||
builder.Services.AddScoped<SecurityLogEntryRepository>();
|
builder.Services.AddScoped<SecurityLogEntryRepository>();
|
||||||
|
builder.Services.AddScoped(typeof(Repository<>));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
builder.Services.AddSingleton<ConfigService>();
|
builder.Services.AddSingleton<ConfigService>();
|
||||||
@@ -85,7 +90,6 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<TotpService>();
|
builder.Services.AddScoped<TotpService>();
|
||||||
builder.Services.AddScoped<ToastService>();
|
builder.Services.AddScoped<ToastService>();
|
||||||
builder.Services.AddScoped<NodeService>();
|
builder.Services.AddScoped<NodeService>();
|
||||||
builder.Services.AddSingleton<MessageService>();
|
|
||||||
builder.Services.AddScoped<ServerService>();
|
builder.Services.AddScoped<ServerService>();
|
||||||
builder.Services.AddSingleton<PaperService>();
|
builder.Services.AddSingleton<PaperService>();
|
||||||
builder.Services.AddScoped<ClipboardService>();
|
builder.Services.AddScoped<ClipboardService>();
|
||||||
@@ -97,8 +101,13 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<NotificationClientService>();
|
builder.Services.AddScoped<NotificationClientService>();
|
||||||
builder.Services.AddScoped<ModalService>();
|
builder.Services.AddScoped<ModalService>();
|
||||||
builder.Services.AddScoped<SmartDeployService>();
|
builder.Services.AddScoped<SmartDeployService>();
|
||||||
builder.Services.AddScoped<WebsiteService>();
|
builder.Services.AddScoped<WebSpaceService>();
|
||||||
builder.Services.AddScoped<StatisticsViewService>();
|
builder.Services.AddScoped<StatisticsViewService>();
|
||||||
|
builder.Services.AddSingleton<DateTimeService>();
|
||||||
|
builder.Services.AddSingleton<EventSystem>();
|
||||||
|
builder.Services.AddScoped<FileDownloadService>();
|
||||||
|
builder.Services.AddScoped<ForgeService>();
|
||||||
|
builder.Services.AddScoped<FabricService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<GoogleOAuth2Service>();
|
builder.Services.AddScoped<GoogleOAuth2Service>();
|
||||||
builder.Services.AddScoped<DiscordOAuth2Service>();
|
builder.Services.AddScoped<DiscordOAuth2Service>();
|
||||||
@@ -106,8 +115,6 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<SubscriptionService>();
|
builder.Services.AddScoped<SubscriptionService>();
|
||||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
builder.Services.AddScoped<SubscriptionAdminService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<CleanupService>();
|
|
||||||
|
|
||||||
// Loggers
|
// Loggers
|
||||||
builder.Services.AddScoped<SecurityLogService>();
|
builder.Services.AddScoped<SecurityLogService>();
|
||||||
builder.Services.AddScoped<AuditLogService>();
|
builder.Services.AddScoped<AuditLogService>();
|
||||||
@@ -116,10 +123,10 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<MailService>();
|
builder.Services.AddScoped<MailService>();
|
||||||
builder.Services.AddSingleton<TrashMailDetectorService>();
|
builder.Services.AddSingleton<TrashMailDetectorService>();
|
||||||
|
|
||||||
// Support
|
// Support chat
|
||||||
builder.Services.AddSingleton<SupportServerService>();
|
builder.Services.AddSingleton<SupportChatServerService>();
|
||||||
builder.Services.AddScoped<SupportAdminService>();
|
builder.Services.AddScoped<SupportChatClientService>();
|
||||||
builder.Services.AddScoped<SupportClientService>();
|
builder.Services.AddScoped<SupportChatAdminService>();
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
builder.Services.AddSingleton<SmartTranslateHelper>();
|
builder.Services.AddSingleton<SmartTranslateHelper>();
|
||||||
@@ -130,11 +137,13 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<PaperApiHelper>();
|
builder.Services.AddSingleton<PaperApiHelper>();
|
||||||
builder.Services.AddSingleton<HostSystemHelper>();
|
builder.Services.AddSingleton<HostSystemHelper>();
|
||||||
builder.Services.AddScoped<DaemonApiHelper>();
|
builder.Services.AddScoped<DaemonApiHelper>();
|
||||||
builder.Services.AddScoped<PleskApiHelper>();
|
builder.Services.AddScoped<CloudPanelApiHelper>();
|
||||||
|
|
||||||
// Background services
|
// Background services
|
||||||
builder.Services.AddSingleton<DiscordBotService>();
|
builder.Services.AddSingleton<DiscordBotService>();
|
||||||
builder.Services.AddSingleton<StatisticsCaptureService>();
|
builder.Services.AddSingleton<StatisticsCaptureService>();
|
||||||
|
builder.Services.AddSingleton<DiscordNotificationService>();
|
||||||
|
builder.Services.AddSingleton<CleanupService>();
|
||||||
|
|
||||||
// Third party services
|
// Third party services
|
||||||
builder.Services.AddBlazorTable();
|
builder.Services.AddBlazorTable();
|
||||||
@@ -160,14 +169,12 @@ namespace Moonlight
|
|||||||
|
|
||||||
app.MapBlazorHub();
|
app.MapBlazorHub();
|
||||||
app.MapFallbackToPage("/_Host");
|
app.MapFallbackToPage("/_Host");
|
||||||
|
|
||||||
// Support service
|
|
||||||
var supportServerService = app.Services.GetRequiredService<SupportServerService>();
|
|
||||||
|
|
||||||
// AutoStart services
|
// AutoStart services
|
||||||
_ = app.Services.GetRequiredService<CleanupService>();
|
_ = app.Services.GetRequiredService<CleanupService>();
|
||||||
_ = app.Services.GetRequiredService<DiscordBotService>();
|
_ = app.Services.GetRequiredService<DiscordBotService>();
|
||||||
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
_ = app.Services.GetRequiredService<StatisticsCaptureService>();
|
||||||
|
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
||||||
|
|
||||||
// Discord bot service
|
// Discord bot service
|
||||||
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
|
//var discordBotService = app.Services.GetRequiredService<DiscordBotService>();
|
||||||
|
|||||||
@@ -17,12 +17,15 @@
|
|||||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||||
"dotnetRunMessages": true
|
"dotnetRunMessages": true
|
||||||
},
|
},
|
||||||
"IIS Express": {
|
"Sql Debug": {
|
||||||
"commandName": "IISExpress",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
}
|
"ML_SQL_DEBUG": "true"
|
||||||
|
},
|
||||||
|
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||||
|
"dotnetRunMessages": true
|
||||||
},
|
},
|
||||||
"Docker": {
|
"Docker": {
|
||||||
"commandName": "Docker",
|
"commandName": "Docker",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@using Moonlight.App.Exceptions
|
@using Moonlight.App.Exceptions
|
||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Logging.Net
|
@using Logging.Net
|
||||||
|
@using Moonlight.App.ApiClients.CloudPanel
|
||||||
@inherits ErrorBoundaryBase
|
@inherits ErrorBoundaryBase
|
||||||
|
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@@ -57,12 +58,16 @@ else
|
|||||||
SmartTranslateService.Translate("Error from daemon"),
|
SmartTranslateService.Translate("Error from daemon"),
|
||||||
wingsException.Message
|
wingsException.Message
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//TODO: Error log service
|
||||||
|
|
||||||
|
Logger.Warn($"Wings exception status code: {wingsException.StatusCode}");
|
||||||
}
|
}
|
||||||
else if (exception is PleskException pleskException)
|
else if (exception is CloudPanelException cloudPanelException)
|
||||||
{
|
{
|
||||||
await AlertService.Error(
|
await AlertService.Error(
|
||||||
SmartTranslateService.Translate("Error from plesk"),
|
SmartTranslateService.Translate("Error from cloud panel"),
|
||||||
pleskException.Message
|
cloudPanelException.Message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (exception is NotImplementedException)
|
else if (exception is NotImplementedException)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("initMonacoTheme");
|
await JsRuntime.InvokeVoidAsync("moonlight.loading.loadMonaco");
|
||||||
|
|
||||||
Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () =>
|
Editor.OnDidInit = new EventCallback<MonacoEditorBase>(this, async () =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@using Moonlight.App.Helpers.Files
|
@using Moonlight.App.Helpers.Files
|
||||||
|
@using Logging.Net
|
||||||
|
|
||||||
<div class="badge badge-lg badge-light-primary">
|
<div class="badge badge-lg badge-light-primary">
|
||||||
<div class="d-flex align-items-center flex-wrap">
|
<div class="d-flex align-items-center flex-wrap">
|
||||||
|
|||||||
@@ -35,88 +35,90 @@
|
|||||||
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
|
<table class="table align-middle table-row-dashed fs-6 gy-5 dataTable no-footer" style="width: 100%;">
|
||||||
<tbody class="fw-semibold text-gray-600">
|
<tbody class="fw-semibold text-gray-600">
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<tr class="even">
|
<ContentBlock @ref="ContentBlock" AllowContentOverride="true">
|
||||||
<td class="w-10px">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<span class="icon-wrapper">
|
|
||||||
<i class="bx bx-md bx-up-arrow-alt text-primary"></i>
|
|
||||||
</span>
|
|
||||||
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
|
|
||||||
<TL>Go up</TL>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td class="text-end">
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<div class="ms-2">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@foreach (var file in Data)
|
|
||||||
{
|
|
||||||
<tr class="even">
|
<tr class="even">
|
||||||
<td class="w-10px">
|
<td class="w-10px">
|
||||||
@if (!HideSelect)
|
|
||||||
{
|
|
||||||
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
|
||||||
@{
|
|
||||||
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (toggle)
|
|
||||||
{
|
|
||||||
<input @onclick="() => SetToggleState(file, false)" class="form-check-input" type="checkbox" checked="checked">
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="icon-wrapper">
|
<span class="icon-wrapper">
|
||||||
@if (file.IsFile)
|
<i class="bx bx-md bx-up-arrow-alt text-primary"></i>
|
||||||
{
|
|
||||||
<i class="bx bx-md bx-file text-primary"></i>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<i class="bx bx-md bx-folder text-primary"></i>
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
<a href="#" @onclick:preventDefault @onclick="() => OnClicked(file)" class="ms-3 text-gray-800 text-hover-primary">@(file.Name)</a>
|
<a href="#" @onclick:preventDefault @onclick="GoUp" class="ms-3 text-gray-800 text-hover-primary">
|
||||||
|
<TL>Go up</TL>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>@(Formatter.FormatSize(file.Size))</td>
|
<td></td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<div class="ms-2 me-6">
|
<div class="ms-2">
|
||||||
@if (ContextActions.Any())
|
|
||||||
{
|
|
||||||
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
|
|
||||||
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
|
||||||
<span class="svg-icon svg-icon-5 m-0">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
|
||||||
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
|
||||||
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</ContextMenuTrigger>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
@foreach (var file in Data)
|
||||||
|
{
|
||||||
|
<tr class="even">
|
||||||
|
<td class="w-10px">
|
||||||
|
@if (!HideSelect)
|
||||||
|
{
|
||||||
|
<div class="form-check form-check-sm form-check-custom form-check-solid">
|
||||||
|
@{
|
||||||
|
var toggle = ToggleStatusCache.ContainsKey(file) && ToggleStatusCache[file];
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (toggle)
|
||||||
|
{
|
||||||
|
<input @onclick="() => SetToggleState(file, false)" class="form-check-input" type="checkbox" checked="checked">
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input @onclick="() => SetToggleState(file, true)" class="form-check-input" type="checkbox">
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<span class="icon-wrapper">
|
||||||
|
@if (file.IsFile)
|
||||||
|
{
|
||||||
|
<i class="bx bx-md bx-file text-primary"></i>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<i class="bx bx-md bx-folder text-primary"></i>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<a href="#" @onclick:preventDefault @onclick="() => OnClicked(file)" class="ms-3 text-gray-800 text-hover-primary">@(file.Name)</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>@(Formatter.FormatSize(file.Size))</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<div class="ms-2 me-6">
|
||||||
|
@if (ContextActions.Any())
|
||||||
|
{
|
||||||
|
<ContextMenuTrigger MenuId="triggerMenu" MouseButtonTrigger="MouseButtonTrigger.Both" Data="file">
|
||||||
|
<button class="btn btn-sm btn-icon btn-light btn-active-light-primary me-2">
|
||||||
|
<span class="svg-icon svg-icon-5 m-0">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="10" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||||
|
<rect x="17" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||||
|
<rect x="3" y="10" width="4" height="4" rx="2" fill="currentColor"></rect>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</ContentBlock>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -138,12 +140,13 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public FileAccess Access { get; set; }
|
public FileAccess Access { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
|
public Func<FileData, Task<bool>>? OnElementClicked { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<Task>? OnSelectionChanged { get; set; }
|
public Func<Task>? OnSelectionChanged { get; set; }
|
||||||
|
|
||||||
@@ -155,7 +158,7 @@
|
|||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool DisableScrolling { get; set; } = false;
|
public bool DisableScrolling { get; set; } = false;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public Func<FileData, bool>? Filter { get; set; }
|
public Func<FileData, bool>? Filter { get; set; }
|
||||||
|
|
||||||
@@ -169,15 +172,19 @@
|
|||||||
private Dictionary<FileData, bool> ToggleStatusCache = new();
|
private Dictionary<FileData, bool> ToggleStatusCache = new();
|
||||||
private bool AllToggled = false;
|
private bool AllToggled = false;
|
||||||
|
|
||||||
|
private ContentBlock ContentBlock;
|
||||||
|
|
||||||
public async Task Refresh()
|
public async Task Refresh()
|
||||||
{
|
{
|
||||||
|
ContentBlock?.SetBlocking(true);
|
||||||
|
|
||||||
var list = new List<FileData>();
|
var list = new List<FileData>();
|
||||||
|
|
||||||
foreach (var fileData in await Access.Ls())
|
foreach (var fileData in await Access.Ls())
|
||||||
{
|
{
|
||||||
if (Filter != null)
|
if (Filter != null)
|
||||||
{
|
{
|
||||||
if(Filter.Invoke(fileData))
|
if (Filter.Invoke(fileData))
|
||||||
list.Add(fileData);
|
list.Add(fileData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -185,7 +192,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Data = list.ToArray();
|
Data = list.ToArray();
|
||||||
|
|
||||||
ToggleStatusCache.Clear();
|
ToggleStatusCache.Clear();
|
||||||
AllToggled = false;
|
AllToggled = false;
|
||||||
|
|
||||||
@@ -196,6 +203,8 @@
|
|||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
OnSelectionChanged?.Invoke();
|
OnSelectionChanged?.Invoke();
|
||||||
|
|
||||||
|
ContentBlock?.SetBlocking(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Load(LazyLoader arg)
|
private async Task Load(LazyLoader arg)
|
||||||
@@ -257,7 +266,7 @@
|
|||||||
if (canceled)
|
if (canceled)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Access.Up();
|
await Access.Up();
|
||||||
await Refresh();
|
await Refresh();
|
||||||
}
|
}
|
||||||
@@ -273,4 +282,4 @@
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
<div class="card-body pt-0 pb-0">
|
<div class="card-body pt-0 pb-0">
|
||||||
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||||
<li class="nav-item mt-2">
|
<li class="nav-item mt-2">
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/websites">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/webspaces">
|
||||||
<TL>Websites</TL>
|
<TL>Webspaces</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item mt-2">
|
<li class="nav-item mt-2">
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/websites/servers">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/webspaces/servers">
|
||||||
<TL>Plesk servers</TL>
|
<TL>Cloud panels</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
52
Moonlight/Shared/Components/Partials/ContentBlock.razor
Normal file
52
Moonlight/Shared/Components/Partials/ContentBlock.razor
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
@if (AllowContentOverride)
|
||||||
|
{
|
||||||
|
if (IsBlocking)
|
||||||
|
{
|
||||||
|
<div class="overlay overlay-block">
|
||||||
|
<div class="overlay-wrapper p-5">
|
||||||
|
@(ChildContent)
|
||||||
|
</div>
|
||||||
|
<div class="overlay-layer">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@ChildContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="@(IsBlocking ? "overlay overlay-block" : "")">
|
||||||
|
<div class="@(IsBlocking ? "overlay-wrapper p-5" : "")">
|
||||||
|
@(ChildContent)
|
||||||
|
</div>
|
||||||
|
@if (IsBlocking)
|
||||||
|
{
|
||||||
|
<div class="overlay-layer">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter]
|
||||||
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool AllowContentOverride { get; set; } = false;
|
||||||
|
|
||||||
|
private bool IsBlocking = false;
|
||||||
|
|
||||||
|
public async Task SetBlocking(bool b)
|
||||||
|
{
|
||||||
|
IsBlocking = b;
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,33 +9,36 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject CookieService CookieService
|
@inject CookieService CookieService
|
||||||
|
|
||||||
<div class="menu menu-column justify-content-center"
|
@if (User != null)
|
||||||
data-kt-menu="true">
|
{
|
||||||
<div class="menu-item">
|
<div class="menu menu-column justify-content-center"
|
||||||
<div class="dropdown">
|
data-kt-menu="true">
|
||||||
<button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
<div class="menu-item">
|
||||||
<TL>Create</TL>
|
<div class="dropdown">
|
||||||
</button>
|
<button class="btn btn-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||||
<ul class="dropdown-menu">
|
<TL>Create</TL>
|
||||||
<li>
|
</button>
|
||||||
<a class="dropdown-item py-2" href="/servers/create">
|
<ul class="dropdown-menu">
|
||||||
<TL>Server</TL>
|
<li>
|
||||||
</a>
|
<a class="dropdown-item py-2" href="/servers/create">
|
||||||
</li>
|
<TL>Server</TL>
|
||||||
<li>
|
</a>
|
||||||
<a class="dropdown-item py-2" href="/domains/create">
|
</li>
|
||||||
<TL>Domain</TL>
|
<li>
|
||||||
</a>
|
<a class="dropdown-item py-2" href="/domains/create">
|
||||||
</li>
|
<TL>Domain</TL>
|
||||||
<li>
|
</a>
|
||||||
<a class="dropdown-item py-2" href="/websites/create">
|
</li>
|
||||||
<TL>Website</TL>
|
<li>
|
||||||
</a>
|
<a class="dropdown-item py-2" href="/webspaces/create">
|
||||||
</li>
|
<TL>Webspace</TL>
|
||||||
</ul>
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
<div class="app-navbar flex-shrink-0">
|
<div class="app-navbar flex-shrink-0">
|
||||||
<div class="app-navbar-item ms-1 ms-lg-3">
|
<div class="app-navbar-item ms-1 ms-lg-3">
|
||||||
|
|||||||
@@ -45,11 +45,11 @@ else
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
<a class="menu-link" href="/websites">
|
<a class="menu-link" href="/webspaces">
|
||||||
<span class="menu-icon">
|
<span class="menu-icon">
|
||||||
<i class="bx bx-globe"></i>
|
<i class="bx bx-globe"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="menu-title"><TL>Websites</TL></span>
|
<span class="menu-title"><TL>Webspaces</TL></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
@@ -148,11 +148,11 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
<a class="menu-link" href="/admin/websites">
|
<a class="menu-link" href="/admin/webspaces">
|
||||||
<span class="menu-icon">
|
<span class="menu-icon">
|
||||||
<i class="bx bx-globe"></i>
|
<i class="bx bx-globe"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="menu-title"><TL>Websites</TL></span>
|
<span class="menu-title"><TL>Webspaces</TL></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
|
|||||||
@@ -47,6 +47,6 @@
|
|||||||
|
|
||||||
private async void TriggerFlashbang()
|
private async void TriggerFlashbang()
|
||||||
{
|
{
|
||||||
await JsRuntime.InvokeVoidAsync("flashbang");
|
await JsRuntime.InvokeVoidAsync("moonlight.flashbang.run");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
@using Logging.Net
|
@using Logging.Net
|
||||||
@using BlazorContextMenu
|
@using BlazorContextMenu
|
||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Events
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
@inject ServerService ServerService
|
@inject ServerService ServerService
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject ClipboardService ClipboardService
|
@inject ClipboardService ClipboardService
|
||||||
@inject MessageService MessageService
|
@inject EventSystem Event
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
@@ -112,64 +113,51 @@
|
|||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.create", this, (backup) =>
|
Event.On<ServerBackup>("wings.backups.create", this, async (backup) =>
|
||||||
{
|
|
||||||
if (AllBackups == null)
|
|
||||||
return Task.CompletedTask;
|
|
||||||
|
|
||||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
|
||||||
await ToastService.Success(SmartTranslateService.Translate("Backup successfully created"));
|
|
||||||
await LazyLoader.Reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
|
|
||||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.createfailed", this, (backup) =>
|
|
||||||
{
|
|
||||||
if (AllBackups == null)
|
|
||||||
return Task.CompletedTask;
|
|
||||||
|
|
||||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await ToastService.Error(SmartTranslateService.Translate("Backup creation failed"));
|
|
||||||
await LazyLoader.Reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
|
|
||||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.delete", this, async (backup) =>
|
|
||||||
{
|
{
|
||||||
if (AllBackups == null)
|
if (AllBackups == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||||
{
|
await ToastService.Success(SmartTranslateService.Translate("Backup successfully created"));
|
||||||
await ToastService.Success(SmartTranslateService.Translate("Backup successfully deleted"));
|
await LazyLoader.Reload();
|
||||||
await LazyLoader.Reload();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
MessageService.Subscribe<ServerBackups, ServerBackup>("wings.backups.restore", this, async (backup) =>
|
Event.On<ServerBackup>("wings.backups.createFailed", this, async (backup) =>
|
||||||
{
|
{
|
||||||
if (AllBackups == null)
|
if (AllBackups == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (AllBackups.Any(x => x.Id == backup.Id))
|
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||||
{
|
{
|
||||||
Task.Run(async () => { await ToastService.Success(SmartTranslateService.Translate("Backup successfully restored")); });
|
await ToastService.Error(SmartTranslateService.Translate("Backup creation failed"));
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Event.On<ServerBackup>("wings.backups.delete", this, async (backup) =>
|
||||||
|
{
|
||||||
|
if (AllBackups == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||||
|
{
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Backup successfully deleted"));
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Event.On<ServerBackup>("wings.backups.restore", this, async (backup) =>
|
||||||
|
{
|
||||||
|
if (AllBackups == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (AllBackups.Any(x => x.Id == backup.Id))
|
||||||
|
{
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Backup successfully restored"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -186,95 +174,102 @@
|
|||||||
|
|
||||||
private async Task Download(ServerBackup serverBackup)
|
private async Task Download(ServerBackup serverBackup)
|
||||||
{
|
{
|
||||||
|
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo(url);
|
||||||
|
await ToastService.Success(SmartTranslateService.Translate("Backup download successfully started"));
|
||||||
|
|
||||||
|
/*
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
|
||||||
|
|
||||||
NavigationManager.NavigateTo(url);
|
|
||||||
await ToastService.Success(SmartTranslateService.Translate("Backup download successfully started"));
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Error starting backup download");
|
Logger.Warn("Error starting backup download");
|
||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
await ToastService.Error(SmartTranslateService.Translate("Backup download failed"));
|
await ToastService.Error(SmartTranslateService.Translate("Backup download failed"));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CopyUrl(ServerBackup serverBackup)
|
private async Task CopyUrl(ServerBackup serverBackup)
|
||||||
{
|
{
|
||||||
|
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
||||||
|
|
||||||
|
await ClipboardService.Copy(url);
|
||||||
|
await AlertService.Success(
|
||||||
|
SmartTranslateService.Translate("Success"),
|
||||||
|
SmartTranslateService.Translate("Backup URL successfully copied to your clipboard"));
|
||||||
|
|
||||||
|
/*
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = await ServerService.DownloadBackup(CurrentServer, serverBackup);
|
|
||||||
|
|
||||||
await ClipboardService.CopyToClipboard(url);
|
|
||||||
await AlertService.Success(
|
|
||||||
SmartTranslateService.Translate("Success"),
|
|
||||||
SmartTranslateService.Translate("Backup URL successfully copied to your clipboard"));
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Error copying backup url");
|
Logger.Warn("Error copying backup url");
|
||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while generating backup url"));
|
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while generating backup url"));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Delete(ServerBackup serverBackup)
|
private async Task Delete(ServerBackup serverBackup)
|
||||||
{
|
{
|
||||||
|
await ToastService.Info(SmartTranslateService.Translate("Backup deletion started"));
|
||||||
|
await ServerService.DeleteBackup(CurrentServer, serverBackup);
|
||||||
|
|
||||||
|
/*
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ToastService.Info(SmartTranslateService.Translate("Backup deletion started"));
|
|
||||||
await ServerService.DeleteBackup(CurrentServer, serverBackup);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Error deleting backup");
|
Logger.Warn("Error deleting backup");
|
||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while starting backup deletion"));
|
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while starting backup deletion"));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Restore(ServerBackup serverBackup)
|
private async Task Restore(ServerBackup serverBackup)
|
||||||
{
|
{
|
||||||
|
await ServerService.RestoreBackup(CurrentServer, serverBackup);
|
||||||
|
await ToastService.Info(SmartTranslateService.Translate("Backup restore started"));
|
||||||
|
|
||||||
|
/*
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ServerService.RestoreBackup(CurrentServer, serverBackup);
|
|
||||||
|
|
||||||
await ToastService.Info(SmartTranslateService.Translate("Backup restore started"));
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Error restoring backup");
|
Logger.Warn("Error restoring backup");
|
||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while restoring a backup"));
|
await ToastService.Error(SmartTranslateService.Translate("An unknown error occured while restoring a backup"));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Create()
|
private async Task Create()
|
||||||
{
|
{
|
||||||
|
await ToastService.Info(SmartTranslateService.Translate("Started backup creation"));
|
||||||
|
await ServerService.CreateBackup(CurrentServer);
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
|
||||||
|
/*
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ToastService.Info(SmartTranslateService.Translate("Started backup creation"));
|
|
||||||
var backup = await ServerService.CreateBackup(CurrentServer);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// Modify the backup list so no reload needed. Also the create event will work
|
// Modify the backup list so no reload needed. Also the create event will work
|
||||||
var list = AllBackups!.ToList();
|
//var list = AllBackups!.ToList();
|
||||||
list.Add(backup);
|
//list.Add(backup);
|
||||||
AllBackups = list.ToArray();
|
//AllBackups = list.ToArray();
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
await LazyLoader.Reload();
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Error creating backup");
|
Logger.Warn("Error creating backup");
|
||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
await ToastService.Error(SmartTranslateService.Translate("An unknown error has occured while creating a backup"));
|
await ToastService.Error(SmartTranslateService.Translate("An unknown error has occured while creating a backup"));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnContextMenuClick(ItemClickEventArgs args)
|
private async Task OnContextMenuClick(ItemClickEventArgs args)
|
||||||
@@ -300,11 +295,11 @@
|
|||||||
await Refresh(LazyLoader);
|
await Refresh(LazyLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public async void Dispose()
|
||||||
{
|
{
|
||||||
MessageService.Unsubscribe("wings.backups.create", this);
|
await Event.Off("wings.backups.create", this);
|
||||||
MessageService.Unsubscribe("wings.backups.createfailed", this);
|
await Event.Off("wings.backups.createFailed", this);
|
||||||
MessageService.Unsubscribe("wings.backups.restore", this);
|
await Event.Off("wings.backups.restore", this);
|
||||||
MessageService.Unsubscribe("wings.backups.delete", this);
|
await Event.Off("wings.backups.delete", this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
<span class="fw-bold"><TL>Server ID</TL>:</span>
|
||||||
<span class="ms-1 text-muted">@(CurrentServer.Id)</span>
|
<span class="ms-1 text-muted">
|
||||||
|
@if (User.Admin)
|
||||||
|
{
|
||||||
|
<a href="/admin/servers/edit/@(CurrentServer.Id)">@(CurrentServer.Id)</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@(CurrentServer.Id)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col fs-5">
|
<div class="col fs-5">
|
||||||
<span class="fw-bold"><TL>Status</TL>:</span>
|
<span class="fw-bold"><TL>Status</TL>:</span>
|
||||||
@@ -164,6 +173,9 @@
|
|||||||
{
|
{
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Server CurrentServer { get; set; }
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
|
[CascadingParameter]
|
||||||
|
public User User { get; set; }
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public PteroConsole Console { get; set; }
|
public PteroConsole Console { get; set; }
|
||||||
|
|||||||
@@ -37,6 +37,12 @@
|
|||||||
if(Tags.Contains("paperversion"))
|
if(Tags.Contains("paperversion"))
|
||||||
Settings.Add("Paper version", typeof(PaperVersionSetting));
|
Settings.Add("Paper version", typeof(PaperVersionSetting));
|
||||||
|
|
||||||
|
if(Tags.Contains("forgeversion"))
|
||||||
|
Settings.Add("Forge version", typeof(ForgeVersionSetting));
|
||||||
|
|
||||||
|
if(Tags.Contains("fabricversion"))
|
||||||
|
Settings.Add("Fabric version", typeof(FabricVersionSetting));
|
||||||
|
|
||||||
if(Tags.Contains("join2start"))
|
if(Tags.Contains("join2start"))
|
||||||
Settings.Add("Join2Start", typeof(Join2StartSetting));
|
Settings.Add("Join2Start", typeof(Join2StartSetting));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Repositories.Servers
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
|
@inject ServerService ServerService
|
||||||
|
@inject ServerRepository ServerRepository
|
||||||
|
@inject ImageRepository ImageRepository
|
||||||
|
@inject FabricService FabricService
|
||||||
|
@inject SmartTranslateService TranslationService
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card card-body">
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
<label class="mb-2 form-label">
|
||||||
|
<TL>Fabric version</TL>
|
||||||
|
</label>
|
||||||
|
<input class="mb-2 form-control" disabled="" value="@(FabricVersion)"/>
|
||||||
|
<label class="mb-2 form-label">
|
||||||
|
<TL>Fabric loader version</TL>
|
||||||
|
</label>
|
||||||
|
<input class="mb-2 form-control" disabled="" value="@(LoaderVersion)"/>
|
||||||
|
<label class="mb-2 form-label">
|
||||||
|
<TL>Minecraft version</TL>
|
||||||
|
</label>
|
||||||
|
<select class="mb-2 form-select" @bind="CurrentVersion">
|
||||||
|
@foreach (var version in Versions)
|
||||||
|
{
|
||||||
|
if (version == CurrentVersion)
|
||||||
|
{
|
||||||
|
<option value="@(version)" selected="">@(version)</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@(version)">@(version)</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<WButton
|
||||||
|
OnClick="Save"
|
||||||
|
Text="@(TranslationService.Translate("Change"))"
|
||||||
|
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||||
|
CssClasses="btn-primary">
|
||||||
|
</WButton>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[CascadingParameter]
|
||||||
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
|
private string[] Versions = Array.Empty<string>();
|
||||||
|
private string CurrentVersion = "";
|
||||||
|
|
||||||
|
private string FabricVersion = "";
|
||||||
|
private string LoaderVersion = "";
|
||||||
|
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
// Fabric version
|
||||||
|
|
||||||
|
var fabricVersion = LiveMigrateVar(
|
||||||
|
"FABRIC_VERSION",
|
||||||
|
await FabricService.GetLatestInstallerVersion(),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
FabricVersion = fabricVersion.Value;
|
||||||
|
|
||||||
|
// Fabric loader version
|
||||||
|
|
||||||
|
var loaderVersion = LiveMigrateVar(
|
||||||
|
"LOADER_VERSION",
|
||||||
|
await FabricService.GetLatestLoaderVersion(),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
LoaderVersion = loaderVersion.Value;
|
||||||
|
|
||||||
|
// Minecraft versions
|
||||||
|
|
||||||
|
Versions = await FabricService.GetGameVersions();
|
||||||
|
|
||||||
|
var mcVersion = LiveMigrateVar(
|
||||||
|
"MC_VERSION",
|
||||||
|
Versions.First()
|
||||||
|
);
|
||||||
|
|
||||||
|
CurrentVersion = mcVersion.Value;
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerVariable LiveMigrateVar(string key, string value, bool overwrite = false)
|
||||||
|
{
|
||||||
|
var v = CurrentServer.Variables.FirstOrDefault(x => x.Key == key);
|
||||||
|
|
||||||
|
if (v == null)
|
||||||
|
{
|
||||||
|
CurrentServer.Variables.Add(new()
|
||||||
|
{
|
||||||
|
Key = key,
|
||||||
|
Value = value
|
||||||
|
});
|
||||||
|
|
||||||
|
ServerRepository.Update(CurrentServer);
|
||||||
|
|
||||||
|
return CurrentServer.Variables.First(x => x.Key == key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(v.Value) || overwrite)
|
||||||
|
{
|
||||||
|
v.Value = value;
|
||||||
|
|
||||||
|
ServerRepository.Update(CurrentServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
var vars = CurrentServer.Variables;
|
||||||
|
var versionVar = vars.First(x => x.Key == "MC_VERSION");
|
||||||
|
|
||||||
|
versionVar.Value = CurrentVersion;
|
||||||
|
|
||||||
|
// This searches for the display name of a version using the constructed full version
|
||||||
|
var version = ParseHelper.MinecraftToInt(CurrentVersion);
|
||||||
|
|
||||||
|
var serverImage = ImageRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.DockerImages)
|
||||||
|
.First(x => x.Id == CurrentServer.Image.Id);
|
||||||
|
|
||||||
|
var dockerImages = serverImage.DockerImages;
|
||||||
|
|
||||||
|
var dockerImageToUpdate = dockerImages.Last();
|
||||||
|
|
||||||
|
if (version < 1130)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 1130)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("11"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 1170)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("16"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 1190)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("17"));
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentServer.DockerImageIndex = dockerImages.IndexOf(dockerImageToUpdate);
|
||||||
|
|
||||||
|
ServerRepository.Update(CurrentServer);
|
||||||
|
|
||||||
|
await ServerService.Reinstall(CurrentServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Microsoft.EntityFrameworkCore
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Repositories
|
||||||
|
@using Moonlight.App.Repositories.Servers
|
||||||
|
@using Logging.Net
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
|
||||||
|
@inject ServerService ServerService
|
||||||
|
@inject ServerRepository ServerRepository
|
||||||
|
@inject ImageRepository ImageRepository
|
||||||
|
@inject ForgeService ForgeService
|
||||||
|
@inject SmartTranslateService TranslationService
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<div class="card card-body">
|
||||||
|
<LazyLoader Load="Load">
|
||||||
|
<label class="mb-2 form-label"><TL>Forge version</TL></label>
|
||||||
|
<select class="mb-2 form-select" @bind="CurrentVersion">
|
||||||
|
@foreach (var version in Versions.Keys)
|
||||||
|
{
|
||||||
|
if (DisplayToData(version) == CurrentVersion)
|
||||||
|
{
|
||||||
|
<option value="@(DisplayToData(version))" selected="">@(version)</option>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<option value="@(DisplayToData(version))">@(version)</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<WButton
|
||||||
|
OnClick="Save"
|
||||||
|
Text="@(TranslationService.Translate("Change"))"
|
||||||
|
WorkingText="@(TranslationService.Translate("Changing"))"
|
||||||
|
CssClasses="btn-primary">
|
||||||
|
</WButton>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[CascadingParameter]
|
||||||
|
public Server CurrentServer { get; set; }
|
||||||
|
|
||||||
|
private Dictionary<string, string> Versions = new();
|
||||||
|
private string CurrentVersion = "";
|
||||||
|
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
Versions = await ForgeService.GetVersions();
|
||||||
|
|
||||||
|
var vars = CurrentServer.Variables;
|
||||||
|
var versionVar = vars.FirstOrDefault(x => x.Key == "FORGE_VERSION");
|
||||||
|
|
||||||
|
// Live migration
|
||||||
|
if (versionVar == null)
|
||||||
|
{
|
||||||
|
CurrentServer.Variables.Add(new ()
|
||||||
|
{
|
||||||
|
Key = "FORGE_VERSION",
|
||||||
|
Value = LatestVersion()
|
||||||
|
});
|
||||||
|
|
||||||
|
ServerRepository.Update(CurrentServer);
|
||||||
|
|
||||||
|
versionVar = vars.First(x => x.Key == "FORGE_VERSION");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(versionVar.Value))
|
||||||
|
{
|
||||||
|
versionVar.Value = LatestVersion();
|
||||||
|
ServerRepository.Update(CurrentServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentVersion = versionVar.Value;
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DisplayToData(string display)
|
||||||
|
{
|
||||||
|
return display
|
||||||
|
.Replace("-recommended", "")
|
||||||
|
.Replace("-latest", "") + "-" + Versions[display];
|
||||||
|
}
|
||||||
|
|
||||||
|
private string LatestVersion()
|
||||||
|
{
|
||||||
|
var versionsSorted = Versions.Keys
|
||||||
|
.OrderByDescending(ParseHelper.MinecraftToInt);
|
||||||
|
|
||||||
|
return DisplayToData(versionsSorted.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Save()
|
||||||
|
{
|
||||||
|
var vars = CurrentServer.Variables;
|
||||||
|
var versionVar = vars.First(x => x.Key == "FORGE_VERSION");
|
||||||
|
|
||||||
|
versionVar.Value = CurrentVersion;
|
||||||
|
|
||||||
|
// This searches for the display name of a version using the constructed full version
|
||||||
|
var version = ParseHelper.MinecraftToInt(
|
||||||
|
Versions.First(
|
||||||
|
x => DisplayToData(x.Key) == CurrentVersion).Key);
|
||||||
|
|
||||||
|
var serverImage = ImageRepository
|
||||||
|
.Get()
|
||||||
|
.Include(x => x.DockerImages)
|
||||||
|
.First(x => x.Id == CurrentServer.Image.Id);
|
||||||
|
|
||||||
|
var dockerImages = serverImage.DockerImages;
|
||||||
|
|
||||||
|
var dockerImageToUpdate = dockerImages.Last();
|
||||||
|
|
||||||
|
if (version < 1130)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 1130)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("11"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 1170)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("16"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version >= 1190)
|
||||||
|
{
|
||||||
|
dockerImageToUpdate = dockerImages.First(x => x.Name.Contains("17"));
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentServer.DockerImageIndex = dockerImages.IndexOf(dockerImageToUpdate);
|
||||||
|
|
||||||
|
ServerRepository.Update(CurrentServer);
|
||||||
|
|
||||||
|
await ServerService.Reinstall(CurrentServer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
|
||||||
|
@inject WebSpaceService WebSpaceService
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject AlertService AlertService
|
||||||
|
|
||||||
|
<div class="row gy-5 g-xl-10">
|
||||||
|
<div class="col-xl-4 mb-xl-10">
|
||||||
|
<div class="card h-md-100">
|
||||||
|
<div class="card-body d-flex flex-column flex-center">
|
||||||
|
<img class="img-fluid" src="https://image.thum.io/get/http://@(CurrentWebSpace.Domain)" alt="Website screenshot"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xl-8 mb-5 mb-xl-10">
|
||||||
|
<div class="card card-flush h-xl-100">
|
||||||
|
<div class="card-body pt-2">
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="card border">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<TL>SSL certificates</TL>
|
||||||
|
</span>
|
||||||
|
<div class="card-toolbar">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Issue certificate"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||||
|
CssClasses="btn-success"
|
||||||
|
OnClick="IssueCertificate">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LazyLoader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[CascadingParameter]
|
||||||
|
public WebSpace CurrentWebSpace { get; set; }
|
||||||
|
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
|
private Task Load(LazyLoader lazyLoader)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task IssueCertificate()
|
||||||
|
{
|
||||||
|
await WebSpaceService.IssueSslCertificate(CurrentWebSpace);
|
||||||
|
await AlertService.Success(SmartTranslateService.Translate("Lets Encrypt certificate successfully issued"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject WebsiteService WebsiteService
|
@inject WebSpaceService WebSpaceService
|
||||||
|
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<div class="card w-100 mb-4">
|
<div class="card w-100 mb-4">
|
||||||
<div class="card-body py-2">
|
<div class="card-body py-2">
|
||||||
|
|
||||||
<div class="my-4 w-100">
|
<div class="my-4 w-100">
|
||||||
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
|
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<span>@(database.Name) - @(database.Type.ToUpper().Replace("MYSQL", "MySQL"))</span>
|
<span>@(database.UserName) - @(database.UserName.ToUpper().Replace("MYSQL", "MySQL"))</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body me-1">
|
<div class="card-body me-1">
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td class="pb-2">
|
<td class="pb-2">
|
||||||
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(Host)">
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.CloudPanel.Host)">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td class="pb-2">
|
<td class="pb-2">
|
||||||
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(DatabaseServer.Port)">
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="3306">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -66,7 +66,17 @@
|
|||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td class="pb-2">
|
<td class="pb-2">
|
||||||
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.Name)">
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.UserName)">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<label class="fs-6 fw-semibold form-label mt-3">
|
||||||
|
<TL>Password</TL>
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td class="pb-2">
|
||||||
|
<input type="text" class="form-control form-control-solid disabled blur-unless-hover" disabled="disabled" value="@(database.Password)">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -76,7 +86,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td class="pb-2">
|
<td class="pb-2">
|
||||||
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.Name)">
|
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(database.UserName)">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -93,7 +103,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
<TL>No databases found for this website</TL>
|
<TL>No databases found for this webspace</TL>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
@@ -101,36 +111,28 @@
|
|||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Website CurrentWebsite { get; set; }
|
public WebSpace CurrentWebSpace { get; set; }
|
||||||
|
|
||||||
private LazyLoader LazyLoader;
|
private LazyLoader LazyLoader;
|
||||||
private Database[] Databases;
|
private MySqlDatabase[] Databases;
|
||||||
private DatabaseServer DatabaseServer;
|
|
||||||
private string Host;
|
|
||||||
|
|
||||||
private DatabaseDataModel Model = new();
|
private DatabaseDataModel Model = new();
|
||||||
|
|
||||||
private async Task Load(LazyLoader arg)
|
private async Task Load(LazyLoader arg)
|
||||||
{
|
{
|
||||||
Databases = await WebsiteService.GetDatabases(CurrentWebsite);
|
Databases = await WebSpaceService.GetDatabases(CurrentWebSpace);
|
||||||
|
|
||||||
if (Databases.Any())
|
|
||||||
{
|
|
||||||
DatabaseServer = (await WebsiteService.GetDefaultDatabaseServer(CurrentWebsite))!;
|
|
||||||
Host = await WebsiteService.GetHost(CurrentWebsite);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnValidSubmit()
|
private async Task OnValidSubmit()
|
||||||
{
|
{
|
||||||
await WebsiteService.CreateDatabase(CurrentWebsite, Model.Name, Model.Password);
|
await WebSpaceService.CreateDatabase(CurrentWebSpace, Model.Name, Model.Password);
|
||||||
Model = new();
|
Model = new();
|
||||||
await LazyLoader.Reload();
|
await LazyLoader.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteDatabase(Database database)
|
private async Task DeleteDatabase(MySqlDatabase database)
|
||||||
{
|
{
|
||||||
await WebsiteService.DeleteDatabase(CurrentWebsite, database);
|
await WebSpaceService.DeleteDatabase(CurrentWebSpace, database);
|
||||||
await LazyLoader.Reload();
|
await LazyLoader.Reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
@using Moonlight.App.Services
|
@using Moonlight.App.Services
|
||||||
@using Moonlight.Shared.Components.FileManagerPartials
|
@using Moonlight.Shared.Components.FileManagerPartials
|
||||||
|
|
||||||
@inject WebsiteService WebsiteService
|
@inject WebSpaceService WebSpaceService
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<FileManager Access="Access">
|
<FileManager Access="Access">
|
||||||
@@ -13,12 +13,12 @@
|
|||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
public Website CurrentWebsite { get; set; }
|
public WebSpace CurrentWebSpace { get; set; }
|
||||||
|
|
||||||
private FileAccess Access;
|
private FileAccess Access;
|
||||||
|
|
||||||
private async Task Load(LazyLoader arg)
|
private async Task Load(LazyLoader arg)
|
||||||
{
|
{
|
||||||
Access = await WebsiteService.CreateFileAccess(CurrentWebsite);
|
Access = await WebSpaceService.CreateFileAccess(CurrentWebSpace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user