Merge pull request #79 from Moonlight-Panel/CloudPanelInteration

Cloud panel interation
This commit is contained in:
Marcel Baumgartner
2023-04-19 22:40:03 +02:00
committed by GitHub
47 changed files with 3623 additions and 869 deletions

View File

@@ -0,0 +1,83 @@
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Plesk.Resources;
using Newtonsoft.Json;
using RestSharp;
namespace Moonlight.App.ApiClients.CloudPanel;
public class CloudPanelApiHelper
{
private readonly RestClient Client;
public CloudPanelApiHelper()
{
Client = new();
}
public async Task Post(Database.Entities.CloudPanel cloudPanel, string resource, object? body)
{
var request = CreateRequest(cloudPanel, resource);
request.Method = Method.Post;
if(body != null)
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new CloudPanelException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
public async Task Delete(Database.Entities.CloudPanel cloudPanel, string resource, object? body)
{
var request = CreateRequest(cloudPanel, resource);
request.Method = Method.Delete;
if(body != null)
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new CloudPanelException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
private RestRequest CreateRequest(Database.Entities.CloudPanel cloudPanel, string resource)
{
var url = $"{cloudPanel.ApiUrl}/" + resource;
var request = new RestRequest(url);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization", "Bearer " + cloudPanel.ApiKey);
return request;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,6 +44,10 @@ public class DataContext : DbContext
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; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
if (!optionsBuilder.IsConfigured) if (!optionsBuilder.IsConfigured)

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -19,6 +19,33 @@ namespace Moonlight.App.Database.Migrations
.HasAnnotation("ProductVersion", "7.0.3") .HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ApiKey")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ApiUrl")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Host")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("CloudPanels");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -281,6 +308,30 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("SecurityLog"); b.ToTable("SecurityLog");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("WebSpaceId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("WebSpaceId");
b.ToTable("Databases");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -748,6 +799,43 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("Users"); b.ToTable("Users");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("CloudPanelId")
.HasColumnType("int");
b.Property<string>("Domain")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("OwnerId")
.HasColumnType("int");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("UserName")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("VHostTemplate")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("CloudPanelId");
b.HasIndex("OwnerId");
b.ToTable("WebSpaces");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -828,6 +916,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)
@@ -932,6 +1031,25 @@ namespace Moonlight.App.Database.Migrations
b.Navigation("CurrentSubscription"); b.Navigation("CurrentSubscription");
}); });
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")
.WithMany()
.HasForeignKey("OwnerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CloudPanel");
b.Navigation("Owner");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b => modelBuilder.Entity("Moonlight.App.Database.Entities.Website", b =>
{ {
b.HasOne("Moonlight.App.Database.Entities.User", "Owner") b.HasOne("Moonlight.App.Database.Entities.User", "Owner")
@@ -971,6 +1089,11 @@ namespace Moonlight.App.Database.Migrations
b.Navigation("Variables"); b.Navigation("Variables");
}); });
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
{
b.Navigation("Databases");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ public class StatisticsCaptureService
private readonly ConfigService ConfigService; private readonly ConfigService ConfigService;
private readonly StatisticsRepository StatisticsRepository; private readonly StatisticsRepository StatisticsRepository;
private readonly IServiceScopeFactory ServiceScopeFactory; private readonly IServiceScopeFactory ServiceScopeFactory;
private readonly WebsiteService WebsiteService; private readonly WebSpaceService WebSpaceService;
private readonly PleskServerRepository PleskServerRepository; private readonly PleskServerRepository PleskServerRepository;
private PeriodicTimer Timer; private PeriodicTimer Timer;
@@ -21,7 +21,7 @@ public class StatisticsCaptureService
DataContext = provider.GetRequiredService<DataContext>(); DataContext = provider.GetRequiredService<DataContext>();
ConfigService = configService; ConfigService = configService;
StatisticsRepository = provider.GetRequiredService<StatisticsRepository>(); StatisticsRepository = provider.GetRequiredService<StatisticsRepository>();
WebsiteService = provider.GetRequiredService<WebsiteService>(); WebSpaceService = provider.GetRequiredService<WebSpaceService>();
PleskServerRepository = provider.GetRequiredService<PleskServerRepository>(); PleskServerRepository = provider.GetRequiredService<PleskServerRepository>();
var config = ConfigService.GetSection("Moonlight").GetSection("Statistics"); var config = ConfigService.GetSection("Moonlight").GetSection("Statistics");
@@ -48,7 +48,7 @@ public class StatisticsCaptureService
await foreach (var pleskServer in PleskServerRepository.Get()) await foreach (var pleskServer in PleskServerRepository.Get())
{ {
databases += (await WebsiteService.GetDefaultDatabaseServer(pleskServer)).DbCount; //databases += (await WebsiteService.GetDefaultDatabaseServer(pleskServer)).DbCount;
} }
StatisticsRepository.Add("statistics.databasesCount", databases); StatisticsRepository.Add("statistics.databasesCount", databases);

View File

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

View File

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

View File

@@ -41,6 +41,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 +68,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="App\ApiClients\CloudPanel\Resources\" />
<Folder Include="App\Http\Middleware" /> <Folder Include="App\Http\Middleware" />
<Folder Include="App\Models\Daemon\Requests" /> <Folder Include="App\Models\Daemon\Requests" />
<Folder Include="App\Models\Google\Resources" /> <Folder Include="App\Models\Google\Resources" />

View File

@@ -2,6 +2,7 @@ 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.Helpers; using Moonlight.App.Helpers;
using Moonlight.App.LogMigrator; using Moonlight.App.LogMigrator;
@@ -76,6 +77,7 @@ namespace Moonlight
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>();
@@ -102,7 +104,7 @@ namespace Moonlight
builder.Services.AddScoped<NotificationClientService>(); builder.Services.AddScoped<NotificationClientService>();
builder.Services.AddScoped<ModalService>(); builder.Services.AddScoped<ModalService>();
builder.Services.AddScoped<SmartDeployService>(); builder.Services.AddScoped<SmartDeployService>();
builder.Services.AddScoped<WebsiteService>(); builder.Services.AddScoped<WebSpaceService>();
builder.Services.AddScoped<StatisticsViewService>(); builder.Services.AddScoped<StatisticsViewService>();
builder.Services.AddScoped<GoogleOAuth2Service>(); builder.Services.AddScoped<GoogleOAuth2Service>();
@@ -136,6 +138,7 @@ namespace Moonlight
builder.Services.AddSingleton<HostSystemHelper>(); builder.Services.AddSingleton<HostSystemHelper>();
builder.Services.AddScoped<DaemonApiHelper>(); builder.Services.AddScoped<DaemonApiHelper>();
builder.Services.AddScoped<PleskApiHelper>(); builder.Services.AddScoped<PleskApiHelper>();
builder.Services.AddScoped<CloudPanelApiHelper>();
// Background services // Background services
builder.Services.AddSingleton<DiscordBotService>(); builder.Services.AddSingleton<DiscordBotService>();

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,60 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@inject WebSpaceService WebSpaceService
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
<div class="row gy-5 g-xl-10">
<div class="col-xl-4 mb-xl-10">
<div class="card h-md-100">
<div class="card-body d-flex flex-column flex-center">
<img class="img-fluid" src="https://image.thum.io/get/http://@(CurrentWebSpace.Domain)" alt="Website screenshot"/>
</div>
</div>
</div>
<div class="col-xl-8 mb-5 mb-xl-10">
<div class="card card-flush h-xl-100">
<div class="card-body pt-2">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="row mt-5">
<div class="card border">
<div class="card-header">
<span class="card-title">
<TL>SSL certificates</TL>
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("Issue certificate"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-success"
OnClick="IssueCertificate">
</WButton>
</div>
</div>
</div>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public WebSpace CurrentWebSpace { get; set; }
private LazyLoader LazyLoader;
private Task Load(LazyLoader lazyLoader)
{
return Task.CompletedTask;
}
private async Task IssueCertificate()
{
await WebSpaceService.IssueSslCertificate(CurrentWebSpace);
await AlertService.Success(SmartTranslateService.Translate("Lets Encrypt certificate successfully issued"));
}
}

View File

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

View File

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

View File

@@ -10,8 +10,8 @@
</div> </div>
</div> </div>
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<div class="mb-1 fs-4">@(Website.BaseDomain)</div> <div class="mb-1 fs-4">@(WebSpace.Domain)</div>
<div class="text-muted fs-5">@(Website.PleskServer.Name)</div> <div class="text-muted fs-5">@(WebSpace.CloudPanel.Name)</div>
</div> </div>
</div> </div>
</div> </div>
@@ -24,22 +24,22 @@
<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="/website/@(Website.Id)"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/webspace/@(WebSpace.Id)">
<TL>Dashboard</TL> <TL>Dashboard</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="/website/@(Website.Id)/files"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/webspace/@(WebSpace.Id)/files">
<TL>Files</TL> <TL>Files</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 == 2 ? "active" : "")" href="/website/@(Website.Id)/ftp"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/webspace/@(WebSpace.Id)/sftp">
<TL>Ftp</TL> <TL>Sftp</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 == 3 ? "active" : "")" href="/website/@(Website.Id)/databases"> <a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/webspace/@(WebSpace.Id)/databases">
<TL>Databases</TL> <TL>Databases</TL>
</a> </a>
</li> </li>
@@ -53,5 +53,5 @@
public int Index { get; set; } public int Index { get; set; }
[Parameter] [Parameter]
public Website Website { get; set; } public WebSpace WebSpace { get; set; }
} }

View File

@@ -0,0 +1,55 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Services
@inject WebSpaceService WebSpaceService
<div class="card card-flush h-xl-100">
<div class="card-body pt-2">
<div class="mt-7 row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Host</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.CloudPanel.Host)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Port</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="21">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Username</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled" disabled="disabled" value="@(CurrentWebSpace.UserName)">
</div>
</div>
<div class="row fv-row mb-7">
<div class="col-md-3 text-md-start">
<label class="fs-6 fw-semibold form-label mt-3">
<TL>Sftp Password</TL>
</label>
</div>
<div class="col-md-9">
<input type="text" class="form-control form-control-solid disabled blur-unless-hover" disabled="disabled" value="@(CurrentWebSpace.Password)">
</div>
</div>
</div>
</div>
@code
{
[CascadingParameter]
public WebSpace CurrentWebSpace { get; set; }
}

View File

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

View File

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

View File

@@ -2,17 +2,18 @@
@using Moonlight.App.Repositories.Servers @using Moonlight.App.Repositories.Servers
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Domains @using Moonlight.App.Repositories.Domains
@using Moonlight.App.Database.Entities
@inject ServerRepository ServerRepository @inject ServerRepository ServerRepository
@inject UserRepository UserRepository @inject UserRepository UserRepository
@inject WebsiteRepository WebsiteRepository @inject Repository<WebSpace> WebSpaceRepository
@inject DomainRepository DomainRepository @inject DomainRepository DomainRepository
<OnlyAdmin> <OnlyAdmin>
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<div class="row mb-5"> <div class="row mb-5">
<div class="col-12 col-lg-6 col-xl"> <div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/servers"> <a class="mt-4 card" href="/admin/servers">
<div class="card-body"> <div class="card-body">
<div class="row align-items-center gx-0"> <div class="row align-items-center gx-0">
<div class="col"> <div class="col">
@@ -33,15 +34,15 @@
</a> </a>
</div> </div>
<div class="col-12 col-lg-6 col-xl"> <div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/websites"> <a class="mt-4 card" href="/admin/webspaces">
<div class="card-body"> <div class="card-body">
<div class="row align-items-center gx-0"> <div class="row align-items-center gx-0">
<div class="col"> <div class="col">
<h6 class="text-uppercase text-muted mb-2"> <h6 class="text-uppercase text-muted mb-2">
<TL>Websites</TL> <TL>Webspaces</TL>
</h6> </h6>
<span class="h2 mb-0"> <span class="h2 mb-0">
@(WebsiteCount) @(WebSpaceCount)
</span> </span>
</div> </div>
<div class="col-auto"> <div class="col-auto">
@@ -54,7 +55,7 @@
</a> </a>
</div> </div>
<div class="col-12 col-lg-6 col-xl"> <div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/domains"> <a class="mt-4 card" href="/admin/domains">
<div class="card-body"> <div class="card-body">
<div class="row align-items-center gx-0"> <div class="row align-items-center gx-0">
<div class="col"> <div class="col">
@@ -75,7 +76,7 @@
</a> </a>
</div> </div>
<div class="col-12 col-lg-6 col-xl"> <div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/domains"> <a class="mt-4 card" href="/admin/users">
<div class="card-body"> <div class="card-body">
<div class="row align-items-center gx-0"> <div class="row align-items-center gx-0">
<div class="col"> <div class="col">
@@ -104,14 +105,14 @@
private int ServerCount = 0; private int ServerCount = 0;
private int UserCount = 0; private int UserCount = 0;
private int DomainCount = 0; private int DomainCount = 0;
private int WebsiteCount = 0; private int WebSpaceCount = 0;
private Task Load(LazyLoader lazyLoader) private Task Load(LazyLoader lazyLoader)
{ {
ServerCount = ServerRepository.Get().Count(); ServerCount = ServerRepository.Get().Count();
UserCount = UserRepository.Get().Count(); UserCount = UserRepository.Get().Count();
DomainCount = DomainRepository.Get().Count(); DomainCount = DomainRepository.Get().Count();
WebsiteCount = WebsiteRepository.Get().Count(); WebSpaceCount = WebSpaceRepository.Get().Count();
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
@page "/admin/websites/new" @page "/admin/webspaces/new"
@using Moonlight.App.Models.Forms @using Moonlight.App.Models.Forms
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@inject WebsiteService WebsiteService @inject WebSpaceService WebSpaceService
@inject UserRepository UserRepository @inject UserRepository UserRepository
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@@ -46,9 +46,9 @@
private async Task OnValidSubmit() private async Task OnValidSubmit()
{ {
await WebsiteService.Create(Model.BaseDomain, Model.User); await WebSpaceService.Create(Model.BaseDomain, Model.User);
NavigationManager.NavigateTo("/admin/websites"); NavigationManager.NavigateTo("/admin/webspaces");
} }
private Task Load(LazyLoader arg) private Task Load(LazyLoader arg)

View File

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

View File

@@ -1,4 +1,4 @@
@page "/admin/websites/servers" @page "/admin/webspaces/servers"
@using Moonlight.Shared.Components.Navigations @using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services @using Moonlight.App.Services
@@ -7,38 +7,39 @@
@using BlazorTable @using BlazorTable
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
@inject PleskServerRepository PleskServerRepository @inject Repository<CloudPanel> CloudPanelRepository
@inject WebsiteService WebsiteService @inject WebSpaceService WebSpaceService
<OnlyAdmin> <OnlyAdmin>
<AdminWebsitesNavigation Index="1"/> <AdminWebspacesNavigation Index="1"/>
<div class="card"> <div class="card">
<div class="card-header border-0 pt-5"> <div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column"> <h3 class="card-title align-items-start flex-column">
<span class="card-label fw-bold fs-3 mb-1"> <span class="card-label fw-bold fs-3 mb-1">
<TL>Plesk servers</TL> <TL>Cloud panels</TL>
</span> </span>
</h3> </h3>
<div class="card-toolbar"> <div class="card-toolbar">
<a href="/admin/websites/servers/new" class="btn btn-sm btn-light-success"> <a href="/admin/webspaces/servers/new" class="btn btn-sm btn-light-success">
<i class="bx bx-user-plus"></i> <i class="bx bx-user-plus"></i>
<TL>New plesk server</TL> <TL>New cloud panel</TL>
</a> </a>
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load"> <LazyLoader @ref="LazyLoader" Load="Load">
<div class="table-responsive"> <div class="table-responsive">
<Table TableItem="PleskServer" Items="PleskServers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted"> <Table TableItem="CloudPanel" Items="CloudPanels" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/> <Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/> <Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Api url"))" Field="@(x => x.ApiUrl)" Sortable="true" Filterable="true"/> <Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Host)" Sortable="true" Filterable="true"/>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="false" Filterable="false"> <Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Api url"))" Field="@(x => x.ApiUrl)" Sortable="true" Filterable="true"/>
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template> <Template>
@if (OnlineCache.ContainsKey(context)) @if (OnlineCache.TryGetValue(context, out var value))
{ {
if (OnlineCache[context]) if (value)
{ {
<span class="text-success"> <span class="text-success">
<TL>Online</TL> <TL>Online</TL>
@@ -59,14 +60,14 @@
} }
</Template> </Template>
</Column> </Column>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Edit"))" Field="@(x => x.Id)" Sortable="false" Filterable="false"> <Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Edit"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template> <Template>
<a href="/admin/websites/servers/edit/@(context.Id)/"> <a href="/admin/webspaces/servers/edit/@(context.Id)/">
<TL>Manage</TL> <TL>Manage</TL>
</a> </a>
</Template> </Template>
</Column> </Column>
<Column TableItem="PleskServer" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false"> <Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template> <Template>
<DeleteButton Confirm="true" OnClick="() => OnClick(context)"> <DeleteButton Confirm="true" OnClick="() => OnClick(context)">
</DeleteButton> </DeleteButton>
@@ -82,22 +83,22 @@
@code @code
{ {
private PleskServer[] PleskServers; private CloudPanel[] CloudPanels;
private LazyLoader LazyLoader; private LazyLoader LazyLoader;
private Dictionary<PleskServer, bool> OnlineCache = new(); private Dictionary<CloudPanel, bool> OnlineCache = new();
private Task Load(LazyLoader arg) private Task Load(LazyLoader arg)
{ {
PleskServers = PleskServerRepository CloudPanels = CloudPanelRepository
.Get() .Get()
.ToArray(); .ToArray();
Task.Run(async () => Task.Run(async () =>
{ {
foreach (var pleskServer in PleskServers) foreach (var cloudPanel in CloudPanels)
{ {
OnlineCache.Add(pleskServer, await WebsiteService.IsHostUp(pleskServer)); OnlineCache.Add(cloudPanel, await WebSpaceService.IsHostUp(cloudPanel));
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
} }
@@ -106,9 +107,9 @@
return Task.CompletedTask; return Task.CompletedTask;
} }
private async Task OnClick(PleskServer pleskServer) private async Task OnClick(CloudPanel pleskServer)
{ {
PleskServerRepository.Delete(pleskServer); CloudPanelRepository.Delete(pleskServer);
await LazyLoader.Reload(); await LazyLoader.Reload();
} }

View File

@@ -1,8 +1,10 @@
@page "/admin/websites/servers/new" @page "/admin/webspaces/servers/new"
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Models.Forms @using Moonlight.App.Models.Forms
@using Moonlight.App.Database.Entities
@using Mappy.Net
@inject PleskServerRepository PleskServerRepository @inject Repository<CloudPanel> CloudPanelRepository
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<OnlyAdmin> <OnlyAdmin>
@@ -14,6 +16,12 @@
<div class="input-group mb-5"> <div class="input-group mb-5">
<InputText @bind-Value="Model.Name" class="form-control"></InputText> <InputText @bind-Value="Model.Name" class="form-control"></InputText>
</div> </div>
<label class="form-label">
<TL>Host</TL>
</label>
<div class="input-group mb-5">
<InputText @bind-Value="Model.Host" class="form-control"></InputText>
</div>
<label class="form-label"> <label class="form-label">
<TL>Api Url</TL> <TL>Api Url</TL>
</label> </label>
@@ -37,18 +45,13 @@
@code @code
{ {
private PleskServerDataModel Model = new(); private CloudPanelDataModel Model = new();
private Task OnValidSubmit() private Task OnValidSubmit()
{ {
PleskServerRepository.Add(new() CloudPanelRepository.Add(Mapper.Map<CloudPanel>(Model));
{
Name = Model.Name,
ApiUrl = Model.ApiUrl,
ApiKey = Model.ApiKey
});
NavigationManager.NavigateTo("/admin/websites/servers"); NavigationManager.NavigateTo("/admin/webspaces/servers");
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -82,12 +82,12 @@
</a> </a>
</div> </div>
<div class="col-12 col-lg-6 col-xl"> <div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/websites"> <a class="mt-4 card" href="/webspaces">
<div class="card-body"> <div class="card-body">
<div class="row align-items-center gx-0"> <div class="row align-items-center gx-0">
<div class="col"> <div class="col">
<h6 class="text-uppercase text-muted mb-2"> <h6 class="text-uppercase text-muted mb-2">
<TL>Websites</TL> <TL>Webspaces</TL>
</h6> </h6>
<span class="h2 mb-0"> <span class="h2 mb-0">
@(WebsiteCount) @(WebsiteCount)

View File

@@ -1,4 +1,4 @@
@page "/website/{Id:int}/{Route?}" @page "/webspace/{Id:int}/{Route?}"
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Services @using Moonlight.App.Services
@@ -6,22 +6,21 @@
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services.Interop @using Moonlight.App.Services.Interop
@inject WebsiteRepository WebsiteRepository @inject Repository<WebSpace> WebSpaceRepository
@inject WebsiteService WebsiteService @inject WebSpaceService WebSpaceService
@inject ToastService ToastService
<LazyLoader Load="Load"> <LazyLoader Load="Load">
@if (CurrentWebsite == null) @if (CurrentWebspace == null)
{ {
<div class="d-flex justify-content-center flex-center"> <div class="d-flex justify-content-center flex-center">
<div class="card"> <div class="card">
<img src="/assets/media/svg/nodata.svg" class="card-img-top w-50 mx-auto pt-5" alt="Not found image"/> <img src="/assets/media/svg/nodata.svg" class="card-img-top w-50 mx-auto pt-5" alt="Not found image"/>
<div class="card-body text-center"> <div class="card-body text-center">
<h1 class="card-title"> <h1 class="card-title">
<TL>Website not found</TL> <TL>Webspace not found</TL>
</h1> </h1>
<p class="card-text fs-4"> <p class="card-text fs-4">
<TL>A website with that id cannot be found or you have no access for this server</TL> <TL>A webspace with that id cannot be found or you have no access for this webspace</TL>
</p> </p>
</div> </div>
</div> </div>
@@ -31,7 +30,7 @@
{ {
if (HostOnline) if (HostOnline)
{ {
<CascadingValue Value="CurrentWebsite"> <CascadingValue Value="CurrentWebspace">
@{ @{
var index = 0; var index = 0;
@@ -40,7 +39,7 @@
case "files": case "files":
index = 1; index = 1;
break; break;
case "ftp": case "sftp":
index = 2; index = 2;
break; break;
case "databases": case "databases":
@@ -51,21 +50,21 @@
break; break;
} }
<WebsiteNavigation Index="index" Website="CurrentWebsite" /> <WebSpaceNavigation Index="index" WebSpace="CurrentWebspace" />
@switch (Route) @switch (Route)
{ {
case "files": case "files":
<WebsiteFiles /> <WebSpaceFiles />
break; break;
case "ftp": case "sftp":
<WebsiteFtp /> <WebSpaceSftp />
break; break;
case "databases": case "databases":
<WebsiteDatabases /> <WebSpaceDatabases />
break; break;
default: default:
<WebsiteDashboard /> <WebSpaceDashboard />
break; break;
} }
} }
@@ -101,32 +100,28 @@
[CascadingParameter] [CascadingParameter]
public User User { get; set; } public User User { get; set; }
private Website? CurrentWebsite; private WebSpace? CurrentWebspace;
private bool HostOnline = false; private bool HostOnline = false;
private async Task Load(LazyLoader lazyLoader) private async Task Load(LazyLoader lazyLoader)
{ {
CurrentWebsite = WebsiteRepository CurrentWebspace = WebSpaceRepository
.Get() .Get()
.Include(x => x.PleskServer) .Include(x => x.CloudPanel)
.Include(x => x.Owner) .Include(x => x.Owner)
.FirstOrDefault(x => x.Id == Id); .FirstOrDefault(x => x.Id == Id);
if (CurrentWebsite != null) if (CurrentWebspace != null)
{ {
if (CurrentWebsite.Owner.Id != User!.Id && !User.Admin) if (CurrentWebspace.Owner.Id != User!.Id && !User.Admin)
CurrentWebsite = null; CurrentWebspace = null;
} }
if (CurrentWebsite != null) if (CurrentWebspace != null)
{ {
await lazyLoader.SetText("Checking host system online status"); await lazyLoader.SetText("Checking host system online status");
HostOnline = await WebsiteService.IsHostUp(CurrentWebsite); HostOnline = await WebSpaceService.IsHostUp(CurrentWebspace);
if (HostOnline)
{
}
} }
} }
} }

View File

@@ -1,4 +1,4 @@
@page "/websites/create" @page "/webspaces/create"
@using Moonlight.App.Services @using Moonlight.App.Services
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms @using Moonlight.App.Models.Forms
@@ -6,24 +6,24 @@
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@inject SubscriptionService SubscriptionService @inject SubscriptionService SubscriptionService
@inject WebsiteService WebsiteService @inject WebSpaceService WebSpaceService
@inject WebsiteRepository WebsiteRepository @inject Repository<WebSpace> WebSpaceRepository
@inject SmartDeployService SmartDeployService @inject SmartDeployService SmartDeployService
@inject SmartTranslateService SmartTranslateService @inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<LazyLoader Load="Load"> <LazyLoader Load="Load">
@if (PleskServer == null) @if (CloudPanel == null)
{ {
<div class="d-flex justify-content-center flex-center"> <div class="d-flex justify-content-center flex-center">
<div class="card"> <div class="card">
<img src="/assets/media/svg/nodata.svg" class="card-img-top w-25 mx-auto pt-5" alt="Not found image"/> <img src="/assets/media/svg/nodata.svg" class="card-img-top w-25 mx-auto pt-5" alt="Not found image"/>
<div class="card-body text-center"> <div class="card-body text-center">
<h4 class="card-title"> <h4 class="card-title">
<TL>No plesk server found</TL> <TL>No web host found</TL>
</h4> </h4>
<p class="card-text"> <p class="card-text">
<TL>No plesk server found to deploy to</TL> <TL>No web host found to deploy to</TL>
</p> </p>
</div> </div>
</div> </div>
@@ -37,7 +37,7 @@
<div class="card-header"> <div class="card-header">
<div class="card-title"> <div class="card-title">
<h2> <h2>
<TL>Website details</TL> <TL>Webspace details</TL>
</h2> </h2>
</div> </div>
</div> </div>
@@ -45,9 +45,9 @@
<div class="d-flex flex-column gap-10"> <div class="d-flex flex-column gap-10">
<div class="fv-row"> <div class="fv-row">
<label class="form-label"> <label class="form-label">
<TL>Plesk server</TL> <TL>Web host</TL>
</label> </label>
<div class="fw-bold fs-3">@(PleskServer.Name)</div> <div class="fw-bold fs-3">@(CloudPanel.Name)</div>
</div> </div>
@if (AllowOrder) @if (AllowOrder)
{ {
@@ -67,7 +67,7 @@
<div class="card-header"> <div class="card-header">
<div class="card-title"> <div class="card-title">
<h2> <h2>
<TL>Configure your website</TL> <TL>Configure your webspaces</TL>
</h2> </h2>
</div> </div>
</div> </div>
@@ -90,7 +90,7 @@
{ {
<div class="alert alert-warning d-flex align-items-center p-5 mb-10"> <div class="alert alert-warning d-flex align-items-center p-5 mb-10">
<span> <span>
<TL>You reached the maximum amount of websites in your subscription</TL>: @(Subscription == null ? SmartTranslateService.Translate("Default") : Subscription.Name) <TL>You reached the maximum amount of webspaces in your subscription</TL>: @(Subscription == null ? SmartTranslateService.Translate("Default") : Subscription.Name)
</span> </span>
</div> </div>
} }
@@ -108,7 +108,7 @@
public User User { get; set; } public User User { get; set; }
private Subscription? Subscription; private Subscription? Subscription;
private PleskServer? PleskServer; private CloudPanel? CloudPanel;
private bool AllowOrder = false; private bool AllowOrder = false;
private WebsiteOrderDataModel Model = new(); private WebsiteOrderDataModel Model = new();
@@ -121,10 +121,10 @@
await lazyLoader.SetText(SmartTranslateService.Translate("Loading your subscription")); await lazyLoader.SetText(SmartTranslateService.Translate("Loading your subscription"));
Subscription = await SubscriptionService.GetCurrent(); Subscription = await SubscriptionService.GetCurrent();
await lazyLoader.SetText(SmartTranslateService.Translate("Searching for deploy plesk server")); await lazyLoader.SetText(SmartTranslateService.Translate("Searching for deploy web host"));
PleskServer = await SmartDeployService.GetPleskServer(); CloudPanel = await SmartDeployService.GetCloudPanel();
AllowOrder = WebsiteRepository AllowOrder = WebSpaceRepository
.Get() .Get()
.Include(x => x.Owner) .Include(x => x.Owner)
.Count(x => x.Owner.Id == User.Id) < (await SubscriptionService.GetLimit("websites")).Amount; .Count(x => x.Owner.Id == User.Id) < (await SubscriptionService.GetLimit("websites")).Amount;
@@ -132,14 +132,14 @@
private async Task OnValidSubmit() private async Task OnValidSubmit()
{ {
if (WebsiteRepository if (WebSpaceRepository
.Get() .Get()
.Include(x => x.Owner) .Include(x => x.Owner)
.Count(x => x.Owner.Id == User.Id) < (await SubscriptionService.GetLimit("websites")).Amount) .Count(x => x.Owner.Id == User.Id) < (await SubscriptionService.GetLimit("websites")).Amount)
{ {
var website = await WebsiteService.Create(Model.BaseDomain, User, PleskServer); var webSpace = await WebSpaceService.Create(Model.BaseDomain, User, CloudPanel);
NavigationManager.NavigateTo($"/website/{website.Id}"); NavigationManager.NavigateTo($"/webspace/{webSpace.Id}");
} }
} }
} }

View File

@@ -1,17 +1,17 @@
@page "/websites" @page "/webspaces"
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@inject WebsiteRepository WebsiteRepository @inject Repository<WebSpace> WebSpaceRepository
<LazyLoader Load="Load"> <LazyLoader Load="Load">
@if (Websites.Any()) @if (WebSpaces.Any())
{ {
foreach (var website in Websites) foreach (var webSpace in WebSpaces)
{ {
<div class="row px-5 mb-5"> <div class="row px-5 mb-5">
<a class="card card-body" href="/website/@(website.Id)"> <a class="card card-body" href="/webspace/@(webSpace.Id)">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
@@ -20,10 +20,10 @@
</div> </div>
<div class="d-flex justify-content-start flex-column"> <div class="d-flex justify-content-start flex-column">
<span class="text-gray-800 text-hover-primary mb-1 fs-5"> <span class="text-gray-800 text-hover-primary mb-1 fs-5">
@(website.BaseDomain) @(webSpace.Domain)
</span> </span>
<span class="text-gray-400 fw-semibold d-block fs-6"> <span class="text-gray-400 fw-semibold d-block fs-6">
<span class="text-gray-700">@(website.PleskServer.Name)</span> <span class="text-gray-700">@(webSpace.CloudPanel.Name)</span>
</span> </span>
</div> </div>
</div> </div>
@@ -38,10 +38,10 @@
<div class="alert bg-info d-flex flex-column flex-sm-row w-100 p-5"> <div class="alert bg-info d-flex flex-column flex-sm-row w-100 p-5">
<div class="d-flex flex-column pe-0 pe-sm-10"> <div class="d-flex flex-column pe-0 pe-sm-10">
<h4 class="fw-semibold"> <h4 class="fw-semibold">
<TL>You have no websites</TL> <TL>You have no webspaces</TL>
</h4> </h4>
<span> <span>
<TL>We were not able to find any websites associated with your account</TL> <TL>We were not able to find any webspaces associated with your account</TL>
</span> </span>
</div> </div>
</div> </div>
@@ -53,14 +53,14 @@
[CascadingParameter] [CascadingParameter]
public User User { get; set; } public User User { get; set; }
private Website[] Websites; private WebSpace[] WebSpaces;
private Task Load(LazyLoader lazyLoader) private Task Load(LazyLoader lazyLoader)
{ {
Websites = WebsiteRepository WebSpaces = WebSpaceRepository
.Get() .Get()
.Include(x => x.Owner) .Include(x => x.Owner)
.Include(x => x.PleskServer) .Include(x => x.CloudPanel)
.Where(x => x.Owner.Id == User.Id) .Where(x => x.Owner.Id == User.Id)
.ToArray(); .ToArray();