Implemented databases

This commit is contained in:
Marcel Baumgartner
2023-04-06 17:30:26 +02:00
parent 06c280704c
commit 4a8605a554
12 changed files with 378 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -130,10 +130,21 @@ public class WebsiteService
return false; return false;
} }
#region Get host
public async Task<string> GetHost(PleskServer pleskServer) public async Task<string> GetHost(PleskServer pleskServer)
{ {
return (await PleskApiHelper.Get<ServerStatus>(pleskServer, "server")).Hostname; 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) private async Task<int> GetAdminAccount(PleskServer pleskServer)
{ {
@@ -147,6 +158,7 @@ public class WebsiteService
return user.Id; return user.Id;
} }
#region SSL
public async Task<string[]> GetSslCertificates(Website w) public async Task<string[]> GetSslCertificates(Website w)
{ {
var website = EnsureData(w); var website = EnsureData(w);
@@ -241,6 +253,90 @@ public class WebsiteService
throw new DisplayException("An unknown error occured while disabling ssl certificate"); 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) public async Task<FileAccess> CreateFileAccess(Website w)
{ {

View File

@@ -43,6 +43,10 @@
pleskException.Message pleskException.Message
); );
} }
else if (exception is NotImplementedException)
{
await AlertService.Error(SmartTranslateService.Translate("This function is not implemented"));
}
else else
{ {
throw exception; throw exception;

View File

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

View File

@@ -62,6 +62,7 @@
<WebsiteFtp /> <WebsiteFtp />
break; break;
case "databases": case "databases":
<WebsiteDatabases />
break; break;
default: default:
<WebsiteDashboard /> <WebsiteDashboard />

View File

@@ -506,3 +506,9 @@ Api url;Api url
Host system offline;Host system offline Host system offline;Host system offline
The host system the website is running on is currently offline;The host system the website is running on is currently offline The host system the website is running on is currently offline;The host system the website is running on is currently offline
No SSL certificates found;No SSL certificates found No SSL certificates found;No SSL certificates found
No databases found for this website;No databases found for this website
The name should be at least 8 characters long;The name should be at least 8 characters long
The name should only contain of lower case characters and numbers;The name should only contain of lower case characters and numbers
Error from plesk;Error from plesk
Host;Host
Username;Username

View File

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