Browse Source

Implemented databases

Marcel Baumgartner 2 năm trước cách đây
mục cha
commit
4a8605a554

+ 17 - 0
Moonlight/App/Models/Forms/DatabaseDataModel.cs

@@ -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; } = "";
+}

+ 23 - 0
Moonlight/App/Models/Plesk/Requests/CreateDatabase.cs

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

+ 15 - 0
Moonlight/App/Models/Plesk/Requests/CreateDatabaseUser.cs

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

+ 15 - 0
Moonlight/App/Models/Plesk/Resources/Database.cs

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

+ 30 - 0
Moonlight/App/Models/Plesk/Resources/DatabaseServer.cs

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

+ 15 - 0
Moonlight/App/Models/Plesk/Resources/DatabaseUser.cs

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

+ 96 - 0
Moonlight/App/Services/WebsiteService.cs

@@ -130,10 +130,21 @@ public class WebsiteService
         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)
     {
@@ -147,6 +158,7 @@ public class WebsiteService
         return user.Id;
     }
 
+    #region SSL
     public async Task<string[]> GetSslCertificates(Website w)
     {
         var website = EnsureData(w);
@@ -241,6 +253,90 @@ public class WebsiteService
             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)
     {

+ 4 - 0
Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor

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

+ 135 - 0
Moonlight/Shared/Components/WebsiteControl/WebsiteDatabases.razor

@@ -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();
+    }
+}

+ 1 - 0
Moonlight/Shared/Views/Website/Index.razor

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

+ 6 - 0
Moonlight/resources/lang/de_de.lang

@@ -506,3 +506,9 @@ Api url;Api url
 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
 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

+ 21 - 0
Moonlight/resources/lang/en_us.lang

@@ -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