Просмотр исходного кода

Merge pull request #53 from Moonlight-Panel/NewTypeahead

Removed old typeahead. Added own solution. Lang file, notes
Marcel Baumgartner 2 лет назад
Родитель
Сommit
4d144d072b

+ 1 - 1
Moonlight/App/Helpers/WingsServerConverter.cs

@@ -46,7 +46,7 @@ public class WingsServerConverter
         }
         
         // Build
-        wingsServer.Settings.Build.Swap = server.Memory * 2;
+        wingsServer.Settings.Build.Swap = server.Memory * 2; //TODO: Add config option
         wingsServer.Settings.Build.Threads = null!;
         wingsServer.Settings.Build.Cpu_Limit = server.Cpu;
         wingsServer.Settings.Build.Disk_Space = server.Disk;

+ 18 - 0
Moonlight/App/Models/Forms/DomainDataModel.cs

@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+using Moonlight.App.Database.Entities;
+
+namespace Moonlight.App.Models.Forms;
+
+public class DomainDataModel
+{
+    [Required(ErrorMessage = "You need to specify a name")]
+    [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")]
+    public string Name { get; set; } = "";
+    
+    [Required(ErrorMessage = "You need to specify a shared domain")]
+    public SharedDomain SharedDomain { get; set; }
+    
+    [Required(ErrorMessage = "You need to specify a owner")]
+    public User Owner { get; set; }
+}

+ 30 - 0
Moonlight/App/Models/Forms/ServerDataModel.cs

@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+using Moonlight.App.Database.Entities;
+
+namespace Moonlight.App.Models.Forms;
+
+public class ServerDataModel
+{
+    [Required(ErrorMessage = "You need to enter a name")]
+    [MaxLength(32, ErrorMessage = "The name cannot be longer that 32 characters")]
+    public string Name { get; set; }
+    
+    [Required(ErrorMessage = "You need to specify a owner")]
+    public User Owner { get; set; }
+
+    [Required(ErrorMessage = "You need to specify cpu amount")]
+    public int Cpu { get; set; } = 100;
+
+    [Required(ErrorMessage = "You need to specify a memory amount")]
+    public int Memory { get; set; } = 1024;
+    
+    [Required(ErrorMessage = "You need to specify a disk amount")]
+    public int Disk { get; set; } = 1024;
+    
+    [Required(ErrorMessage = "You need to specify a image")]
+    public Image Image { get; set; }
+
+    public string OverrideStartup { get; set; } = "";
+    
+    public int DockerImageIndex { get; set; }
+}

+ 10 - 0
Moonlight/App/Models/Forms/TestDataModel.cs

@@ -0,0 +1,10 @@
+using System.ComponentModel.DataAnnotations;
+using Moonlight.App.Database.Entities;
+
+namespace Moonlight.App.Models.Forms;
+
+public class TestDataModel
+{
+    [Required]
+    public User User { get; set; }
+}

+ 1 - 2
Moonlight/Moonlight.csproj

@@ -10,12 +10,10 @@
 
   <ItemGroup>
     <PackageReference Include="Blazor-ApexCharts" Version="0.9.16-beta" />
-    <PackageReference Include="aaPanelSharp" Version="1.0.0" />
     <PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
     <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
     <PackageReference Include="Blazor.ContextMenu" Version="1.15.0" />
     <PackageReference Include="BlazorDownloadFile" Version="2.4.0.2" />
-    <PackageReference Include="Blazored.Typeahead" Version="4.7.0" />
     <PackageReference Include="BlazorMonaco" Version="2.1.0" />
     <PackageReference Include="BlazorTable" Version="1.17.0" />
     <PackageReference Include="CloudFlare.Client" Version="6.1.4" />
@@ -25,6 +23,7 @@
     <PackageReference Include="GravatarSharp.Core" Version="1.0.1.2" />
     <PackageReference Include="JWT" Version="10.0.2" />
     <PackageReference Include="Logging.Net" Version="1.1.0" />
+    <PackageReference Include="Mappy.Net" Version="1.0.2" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.3">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

+ 1 - 3
Moonlight/Pages/_Layout.cshtml

@@ -45,8 +45,7 @@
     <link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
     <link rel="stylesheet" type="text/css" href="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css"/>
     <link rel="stylesheet" type="text/css" href="/_content/Blazor.ContextMenu/blazorContextMenu.min.css"/>
-    <link rel="stylesheet" type="text/css" href="/_content/Blazored.Typeahead/blazored-typeahead.css" />
-    
+
     <link href="/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css"/>
 
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
@@ -87,7 +86,6 @@
 <script src="/_content/BlazorInputFile/inputfile.js"></script>
 <script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
 <script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
-<script src="/_content/Blazored.Typeahead/blazored-typeahead.js"></script>
 
 <script src="https://www.google.com/recaptcha/api.js"></script>
 

+ 93 - 0
Moonlight/Shared/Components/Forms/SmartDropdown.razor

@@ -0,0 +1,93 @@
+@typeparam T
+@using Logging.Net
+@inherits InputBase<T>
+
+<div class="dropdown w-100">
+    <div class="input-group">
+        @if (CurrentValue == null)
+        {
+            <input class="form-control" type="text" @bind-value="SearchTerm" @bind-value:event="oninput" placeholder="Search...">
+        }
+        else
+        {
+            <input class="form-control" type="text" value="@(DisplayFunc(CurrentValue))">
+            <button class="btn btn-primary" @onclick="() => SelectItem(default(T)!)">
+                <i class="bx bx-md bx-x"></i>
+            </button>
+        }
+    </div>
+
+    @{
+        var anyItems = FilteredItems.Any();
+    }
+
+    <div class="dropdown-menu w-100 @(anyItems ? "show" : "")" style="max-height: 200px; overflow-y: auto;">
+        @if (anyItems)
+        {
+            foreach (var item in FilteredItems)
+            {
+                <button class="dropdown-item py-2" type="button" @onclick="(() => SelectItem(item))">@DisplayFunc(item)</button>
+            }
+        }
+    </div>
+</div>
+
+@code {
+
+    [Parameter]
+    public IEnumerable<T> Items { get; set; }
+
+    [Parameter]
+    public Func<T, string> DisplayFunc { get; set; }
+
+    [Parameter]
+    public Func<T, string> SearchProp { get; set; }
+
+    private string SearchTerm
+    {
+        get => searchTerm;
+        set
+        {
+            FilteredItems = Items.Where(i => SearchProp(i).Contains(SearchTerm, StringComparison.OrdinalIgnoreCase)).ToList();
+            searchTerm = value;
+        }
+    }
+
+    private string searchTerm = "";
+
+    private List<T> FilteredItems = new();
+
+    private void SelectItem(T item)
+    {
+        CurrentValue = item;
+        SearchTerm = "";
+        FilteredItems.Clear();
+    }
+
+    protected override bool TryParseValueFromString(string? value, out T result, out string? validationErrorMessage)
+    {
+    // Check if the value is null or empty
+        if (string.IsNullOrEmpty(value))
+        {
+            result = default(T)!;
+            validationErrorMessage = "Value cannot be null or empty";
+            return false;
+        }
+
+    // Try to find an item that matches the search term
+        var item = FilteredItems.FirstOrDefault(i => SearchProp(i).Equals(value, StringComparison.OrdinalIgnoreCase));
+        if (item != null)
+        {
+            result = item;
+            validationErrorMessage = null;
+            return true;
+        }
+        else
+        {
+            result = default(T)!;
+            validationErrorMessage = $"No item found for search term '{value}'";
+            return false;
+        }
+    }
+
+}

+ 5 - 1
Moonlight/Shared/Components/Forms/SmartSelect.razor

@@ -11,10 +11,13 @@
 @code
 {
     [Parameter]
-    public TField[] Items { get; set; }
+    public IEnumerable<TField> Items { get; set; }
     
     [Parameter]
     public Func<TField, string> DisplayField { get; set; }
+    
+    [Parameter]
+    public Func<Task>? OnChange { get; set; }
 
     protected override void OnInitialized()
     {
@@ -53,6 +56,7 @@
             {
                 Value = i;
                 ValueChanged.InvokeAsync(i);
+                OnChange?.Invoke();
             }
         }
     }

+ 39 - 81
Moonlight/Shared/Views/Admin/Domains/New.razor

@@ -1,66 +1,51 @@
 @page "/admin/domains/new"
 @using Moonlight.App.Services
 @using Moonlight.App.Database.Entities
-@using Blazored.Typeahead
+@using Moonlight.App.Models.Forms
 @using Moonlight.App.Repositories
 @using Moonlight.App.Repositories.Domains
+@using Mappy.Net
 
 @inject SmartTranslateService SmartTranslateService
 @inject SharedDomainRepository SharedDomainRepository
 @inject DomainRepository DomainRepository
 @inject UserRepository UserRepository
+@inject NavigationManager NavigationManager
 
 <OnlyAdmin>
     <div class="row mb-5">
         <div class="card card-body p-10">
             <LazyLoader Load="Load">
-                <label class="form-label">
-                    <TL>Domain name</TL>
-                </label>
-                <div class="input-group mb-5">
-                    <span class="input-group-text">
-                        <i class="bx bx-purchase-tag-alt"></i>
-                    </span>
-                    <input @bind="Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))" aria-label="Servername">
-                </div>
-                <div class="mb-5">
+                <SmartForm Model="Model" OnValidSubmit="Add">
                     <label class="form-label">
-                        <TL>Shared domain</TL>
+                        <TL>Domain name</TL>
                     </label>
-                    <select @bind="SharedDomainId" class="form-select">
-                        @if (SharedDomains.Any())
-                        {
-                            foreach (var sharedDomain in SharedDomains)
-                            {
-                                <option value="@(sharedDomain.Id)">@(sharedDomain.Name)</option>
-                            }
-                        }
-                        else
-                        {
-                            <option value="">
-                                <TL>No shared domains available</TL>
-                            </option>
-                        }
-                    </select>
-                </div>
-                <div class="input-group mb-5">
-                    <div class="form-select">
-                        <BlazoredTypeahead SearchMethod="SearchUsers"
-                                           @bind-Value="User">
-                            <SelectedTemplate>
-                                @(context.Email)
-                            </SelectedTemplate>
-                            <ResultTemplate>
-                                @(context.Email)
-                            </ResultTemplate>
-                        </BlazoredTypeahead>
+                    <div class="input-group mb-5">
+                        <span class="input-group-text">
+                            <i class="bx bx-purchase-tag-alt"></i>
+                        </span>
+                        <InputText @bind-Value="Model.Name" class="form-control" placeholder="@(SmartTranslateService.Translate("Domain name"))"></InputText>
                     </div>
-                </div>
-                <WButton Text="@(SmartTranslateService.Translate("Add"))"
-                         WorkingText="@(SmartTranslateService.Translate("Adding"))"
-                         CssClasses="btn-success"
-                         OnClick="Add">
-                </WButton>
+                    <div class="mb-5">
+                        <label class="form-label">
+                            <TL>Shared domain</TL>
+                        </label>
+                        <SmartSelect @bind-Value="Model.SharedDomain"
+                                     Items="SharedDomains"
+                                     DisplayField="@(x => x.Name)">
+                        </SmartSelect>
+                    </div>
+                    <div class="input-group mb-5">
+                        <SmartDropdown @bind-Value="Model.Owner"
+                                       Items="Users"
+                                       DisplayFunc="@(x => x.Email)"
+                                       SearchProp="@(x => x.Email)">
+                        </SmartDropdown>
+                    </div>
+                    <button class="btn btn-success" type="submit">
+                        <TL>Create</TL>
+                    </button>
+                </SmartForm>
             </LazyLoader>
         </div>
     </div>
@@ -68,54 +53,27 @@
 
 @code
 {
-    private string Name;
+    private DomainDataModel Model = new();
 
-    private User? User;
     private User[] Users;
-
-    private List<SharedDomain> SharedDomains;
-
-    private SharedDomain? SharedDomain;
-
-    private int SharedDomainId
-    {
-        get => SharedDomain?.Id ?? -1;
-        set
-        {
-            SharedDomain = SharedDomains.FirstOrDefault(x => x.Id == value);
-            InvokeAsync(StateHasChanged);
-        }
-    }
-
-    private Task<IEnumerable<User>> SearchUsers(string input)
-    {
-        if (string.IsNullOrEmpty(input))
-        {
-            return Task.FromResult(Array.Empty<User>().Cast<User>());
-        }
-        else
-        {
-            return Task.FromResult(Users.Where(x => x.Email.ToLower().StartsWith(input)));
-        }
-    }
+    private SharedDomain[] SharedDomains;
 
     private Task Load(LazyLoader lazyLoader)
     {
         Users = UserRepository.Get().ToArray();
-        SharedDomains = SharedDomainRepository.Get().ToList();
+        SharedDomains = SharedDomainRepository.Get().ToArray();
 
         return Task.CompletedTask;
     }
 
     private Task Add()
     {
-        DomainRepository.Add(new()
-        {
-            Name = Name.ToLower(),
-            Owner = User!,
-            SharedDomain = SharedDomain!
-        });
-        
+        var domain = Mapper.Map<Domain>(Model);
+
+        DomainRepository.Add(domain);
+
+        NavigationManager.NavigateTo("/admin/domains");
+
         return Task.CompletedTask;
     }
 }

+ 145 - 189
Moonlight/Shared/Views/Admin/Servers/New.razor

@@ -6,7 +6,7 @@
 @using Moonlight.App.Exceptions
 @using Moonlight.App.Services.Interop
 @using Logging.Net
-@using Blazored.Typeahead
+@using Moonlight.App.Models.Forms
 
 @inject NodeRepository NodeRepository
 @inject ImageRepository ImageRepository
@@ -19,226 +19,182 @@
 
 <OnlyAdmin>
     <LazyLoader Load="Load">
-        <div class="row mb-5">
-            <div class="card card-body p-10">
-                <label class="form-label">
-                    <TL>Server name</TL>
-                </label>
-                <div class="input-group mb-5">
-                    <span class="input-group-text">
-                        <i class="bx bx-purchase-tag-alt"></i>
-                    </span>
-                    <input @bind="Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))" aria-label="Servername">
-                </div>
-                <label class="form-label">
-                    <TL>Server name</TL>
-                </label>
-                <div class="input-group mb-5">
-                    <div class="form-select">
-                        <BlazoredTypeahead SearchMethod="SearchUsers"
-                                           @bind-Value="User">
-                            <SelectedTemplate>
-                                @(context.Email)
-                            </SelectedTemplate>
-                            <ResultTemplate>
-                                @(context.Email)
-                            </ResultTemplate>
-                        </BlazoredTypeahead>
+        <SmartForm Model="Model" OnValidSubmit="Create">
+            <div class="row mb-5">
+                <div class="card card-body p-10">
+                    <label class="form-label">
+                        <TL>Server name</TL>
+                    </label>
+                    <div class="input-group mb-5">
+                        <span class="input-group-text">
+                            <i class="bx bx-purchase-tag-alt"></i>
+                        </span>
+                        <InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
+                    </div>
+                    <label class="form-label">
+                        <TL>Owner</TL>
+                    </label>
+                    <div class="input-group mb-5">
+                        <SmartDropdown
+                            @bind-Value="Model.Owner"
+                            Items="Users"
+                            DisplayFunc="@(x => x.Email)"
+                            SearchProp="@(x => x.Email)">
+                        </SmartDropdown>
                     </div>
                 </div>
             </div>
-        </div>
-        <div class="row mb-5">
-            <div class="card card-body p-10">
-                <label class="form-label">
-                    <TL>Cpu cores</TL>
-                </label>
-                <div class="input-group mb-5">
-                    <span class="input-group-text">
-                        <i class="bx bx-chip"></i>
-                    </span>
-                    <input @bind="Cpu" type="number" class="form-control">
-                    <span class="input-group-text">
-                        <TL>CPU Cores (100% = 1 Core)</TL>
-                    </span>
-                </div>
-                <label class="form-label">
-                    <TL>Memory</TL>
-                </label>
-                <div class="input-group mb-5">
-                    <span class="input-group-text">
-                        <i class="bx bx-microchip"></i>
-                    </span>
-                    <input @bind="Memory" type="number" class="form-control">
-                    <span class="input-group-text">
-                        MB
-                    </span>
-                </div>
-                <label class="form-label">
-                    <TL>Disk</TL>
-                </label>
-                <div class="input-group mb-5">
-                    <span class="input-group-text">
-                        <i class="bx bx-hdd"></i>
-                    </span>
-                    <input @bind="Disk" type="number" class="form-control">
-                    <span class="input-group-text">
-                        MB
-                    </span>
-                </div>
-            </div>
-        </div>
-        <div class="row mb-5">
-            <div class="card card-body p-10">
-                <label class="form-label">
-                    <TL>Image</TL>
-                </label>
-                <select @bind="ImageIndex" class="form-select mb-5">
-                    @foreach (var image in Images)
-                    {
-                        <option value="@(Images.IndexOf(image))">@(image.Name)</option>
-                    }
-                </select>
-                @if (Image != null)
-                {
+            <div class="row mb-5">
+                <div class="card card-body p-10">
                     <label class="form-label">
-                        <TL>Override startup</TL>
+                        <TL>Cpu cores</TL>
                     </label>
                     <div class="input-group mb-5">
                         <span class="input-group-text">
-                            <i class="bx bx-terminal"></i>
+                            <i class="bx bx-chip"></i>
+                        </span>
+                        <InputNumber @bind-Value="Model.Cpu" class="form-control"></InputNumber>
+                        <span class="input-group-text">
+                            <TL>CPU Cores (100% = 1 Core)</TL>
                         </span>
-                        <input @bind="OverrideStartup" type="text" class="form-control" placeholder="@(Image.Startup)">
                     </div>
                     <label class="form-label">
-                        <TL>Docker image</TL>
+                        <TL>Memory</TL>
                     </label>
-                    <select @bind="DockerImageIndex" class="form-select">
-                        @foreach (var image in Image.DockerImages)
-                        {
-                            <option value="@(Image.DockerImages.IndexOf(image))">@(image.Name)</option>
-                        }
-                    </select>
-                }
+                    <div class="input-group mb-5">
+                        <span class="input-group-text">
+                            <i class="bx bx-microchip"></i>
+                        </span>
+                        <InputNumber @bind-Value="Model.Memory" class="form-control"></InputNumber>
+                        <span class="input-group-text">
+                            MB
+                        </span>
+                    </div>
+                    <label class="form-label">
+                        <TL>Disk</TL>
+                    </label>
+                    <div class="input-group mb-5">
+                        <span class="input-group-text">
+                            <i class="bx bx-hdd"></i>
+                        </span>
+                        <InputNumber @bind-Value="Model.Disk" class="form-control"></InputNumber>
+                        <span class="input-group-text">
+                            MB
+                        </span>
+                    </div>
+                </div>
+            </div>
+            <div class="row mb-5">
+                <div class="card card-body p-10">
+                    <label class="form-label">
+                        <TL>Image</TL>
+                    </label>
+                    <div class="mb-5">
+                        <SmartSelect TField="Image" @bind-Value="Model.Image" Items="Images" DisplayField="@(x => x.Name)" OnChange="OnChange"></SmartSelect>
+                    </div>
+                    @if (Model.Image != null)
+                    {
+                        <label class="form-label">
+                            <TL>Override startup</TL>
+                        </label>
+                        <div class="input-group mb-5">
+                            <span class="input-group-text">
+                                <i class="bx bx-terminal"></i>
+                            </span>
+                            <InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Model.Image.Startup)"></InputText>
+                        </div>
+                        <label class="form-label">
+                            <TL>Docker image</TL>
+                        </label>
+                        <InputSelect TValue="int" @bind-Value="Model.DockerImageIndex" class="form-control">
+                            @foreach (var image in Model.Image.DockerImages)
+                            {
+                                <option value="@(Model.Image.DockerImages.IndexOf(image))">@(image.Name)</option>
+                            }
+                        </InputSelect>
+                    }
+                </div>
             </div>
-        </div>
 
-        <div class="row mb-5">
-            <div class="card card-body">
-                @if (Image != null)
-                {
-                    <div class="mt-9 row d-flex">
-                        @foreach (var vars in ServerVariables.Chunk(4))
-                        {
-                            <div class="row mb-3">
-                                @foreach (var variable in vars)
-                                {
-                                    <div class="col">
-                                        <div class="card card-body">
-                                            <label class="form-label">
-                                                <TL>Name</TL>
-                                            </label>
-                                            <div class="input-group mb-5">
-                                                <input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
-                                            </div>
-                                            <label class="form-label">
-                                                <TL>Value</TL>
-                                            </label>
-                                            <div class="input-group mb-5">
-                                                <input @bind="variable.Value" type="text" class="form-control">
+            <div class="row mb-5">
+                <div class="card card-body">
+                    @if (Model.Image != null)
+                    {
+                        <div class="mt-9 row d-flex">
+                            @foreach (var vars in ServerVariables.Chunk(3))
+                            {
+                                <div class="row row-cols-3 mb-3">
+                                    @foreach (var variable in vars)
+                                    {
+                                        <div class="col">
+                                            <div class="card card-body border">
+                                                <label class="form-label">
+                                                    <TL>Name</TL>
+                                                </label>
+                                                <div class="input-group mb-5">
+                                                    <input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
+                                                </div>
+                                                <label class="form-label">
+                                                    <TL>Value</TL>
+                                                </label>
+                                                <div class="input-group mb-5">
+                                                    <input @bind="variable.Value" type="text" class="form-control">
+                                                </div>
                                             </div>
                                         </div>
-                                    </div>
-                                }
-                            </div>
-                        }
-                    </div>
-                }
+                                    }
+                                </div>
+                            }
+                        </div>
+                    }
+                </div>
             </div>
-        </div>
 
-        <div class="row">
-            <div class="card card-body">
-                <div class="d-flex justify-content-end">
-                    <a href="/admin/servers" class="btn btn-danger me-3">
-                        <TL>Cancel</TL>
-                    </a>
-                    <WButton Text="@(SmartTranslateService.Translate("Create"))"
-                             WorkingText="@(SmartTranslateService.Translate("Creating"))"
-                             CssClasses="btn-success"
-                             OnClick="Create">
-                    </WButton>
+            <div class="row">
+                <div class="card card-body">
+                    <div class="d-flex justify-content-end">
+                        <a href="/admin/servers" class="btn btn-danger me-3">
+                            <TL>Cancel</TL>
+                        </a>
+                        <button class="btn btn-success" type="submit">
+                            <TL>Create</TL>
+                        </button>
+                    </div>
                 </div>
             </div>
-        </div>
+        </SmartForm>
     </LazyLoader>
 </OnlyAdmin>
 
 @code
 {
+    private ServerDataModel Model = new();
+
     private List<Image> Images;
     private Node[] Nodes;
     private User[] Users;
 
-    private string Name = "";
-
-    private int Cpu = 100;
-    private int Memory = 4096;
-    private int Disk = 10240;
-
-    private string OverrideStartup = "";
-    private int DockerImageIndex = 0;
-
-    private Image? Image;
-    private User? User;
-
     private ServerVariable[] ServerVariables = Array.Empty<ServerVariable>();
 
-    private int ImageIndex
-    {
-        get => Image == null ? 0 : Images.IndexOf(Image);
-        set
-        {
-            Image = Images[value];
-
-            if (Image == null)
-                ServerVariables = Array.Empty<ServerVariable>();
-            else
-                RebuildVariables();
-
-
-            InvokeAsync(StateHasChanged);
-        }
-    }
-
     private void RebuildVariables()
     {
         var list = new List<ServerVariable>();
 
-        foreach (var variable in Image.Variables)
+        if (Model.Image != null)
         {
-            list.Add(new()
+            foreach (var variable in Model.Image.Variables)
             {
-                Key = variable.Key,
-                Value = variable.DefaultValue
-            });
+                list.Add(new()
+                {
+                    Key = variable.Key,
+                    Value = variable.DefaultValue
+                });
+            }
         }
 
         ServerVariables = list.ToArray();
     }
 
-    private Task<IEnumerable<User>> SearchUsers(string input)
-    {
-        if (string.IsNullOrEmpty(input))
-        {
-            return Task.FromResult(Array.Empty<User>().Cast<User>());
-        }
-        else
-        {
-            return Task.FromResult(Users.Where(x => x.Email.ToLower().StartsWith(input)));
-        }
-    }
-
     private async Task Load(LazyLoader lazyLoader)
     {
         await lazyLoader.SetText("Loading images");
@@ -256,24 +212,18 @@
         await lazyLoader.SetText("Loading users");
 
         Users = UserRepository.Get().ToArray();
-        User = Users.FirstOrDefault();
-
-        Image = Images.FirstOrDefault();
 
         RebuildVariables();
-
-        if (Image != null)
-            DockerImageIndex = Image.DockerImages.Count - 1;
     }
 
     private async Task Create()
     {
         try
         {
-            await ServerService.Create(Name, Cpu, Memory, Disk, User, Image, null, server =>
+            await ServerService.Create(Model.Name, Model.Cpu, Model.Memory, Model.Disk, Model.Owner, Model.Image, null, server =>
             {
-                server.OverrideStartup = OverrideStartup;
-                server.DockerImageIndex = DockerImageIndex;
+                server.OverrideStartup = Model.OverrideStartup;
+                server.DockerImageIndex = Model.DockerImageIndex;
 
                 foreach (var serverVariable in ServerVariables)
                 {
@@ -303,4 +253,10 @@
                 );
         }
     }
+
+    private async Task OnChange()
+    {
+        RebuildVariables();
+        await InvokeAsync(StateHasChanged);
+    }
 }

+ 5 - 22
Moonlight/Shared/Views/Admin/Websites/New.razor

@@ -2,7 +2,6 @@
 
 @using Moonlight.App.Models.Forms
 @using Moonlight.App.Services
-@using Blazored.Typeahead
 @using Moonlight.App.Database.Entities
 @using Moonlight.App.Repositories
 
@@ -24,15 +23,11 @@
                     <TL>Owner</TL>
                 </label>
                 <div class="input-group mb-5">
-                    <BlazoredTypeahead SearchMethod="SearchUsers"
-                                       @bind-Value="Model.User">
-                        <SelectedTemplate>
-                            @(context.Email)
-                        </SelectedTemplate>
-                        <ResultTemplate>
-                            @(context.Email)
-                        </ResultTemplate>
-                    </BlazoredTypeahead>
+                    <SmartDropdown @bind-Value="Model.User"
+                                   Items="Users"
+                                   DisplayFunc="@(x => x.Email)"
+                                   SearchProp="@(x => x.Email)">
+                    </SmartDropdown>
                 </div>
                 <div>
                     <button type="submit" class="btn btn-primary float-end">
@@ -64,16 +59,4 @@
 
         return Task.CompletedTask;
     }
-
-    private Task<IEnumerable<User>> SearchUsers(string input)
-    {
-        if (string.IsNullOrEmpty(input))
-        {
-            return Task.FromResult(Array.Empty<User>().Cast<User>());
-        }
-        else
-        {
-            return Task.FromResult(Users.Where(x => x.Email.ToLower().StartsWith(input)));
-        }
-    }
 }

+ 25 - 20
Moonlight/Shared/Views/Test.razor

@@ -1,34 +1,39 @@
 @page "/test"
+@using Moonlight.App.Repositories
+@using Moonlight.App.Database.Entities
+@using Moonlight.App.Models.Forms
 
-@using Moonlight.Shared.Components.FileManagerPartials
-@using Moonlight.App.Repositories.Servers
-@using Moonlight.App.Helpers.Files
-@using Microsoft.EntityFrameworkCore
-@using Moonlight.App.Helpers
-@using Moonlight.App.Services
-@using User = Moonlight.App.Database.Entities.User
-
-@inject ServerRepository ServerRepository
-@inject WingsApiHelper WingsApiHelper
-@inject WingsJwtHelper WingsJwtHelper
-@inject ConfigService ConfigService
+@inject UserRepository UserRepository
 
 <LazyLoader Load="Load">
-    <FileManager Access="FileAccess">
-    </FileManager>
+    <SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
+        <div class="mb-3">
+            <SmartDropdown 
+                Items="Users" 
+                @bind-Value="Model.User" 
+                DisplayFunc="@(x => x.Email)" 
+                SearchProp="@(x => x.Email)" />
+        </div>
+        <div>
+            <button class="btn btn-primary" type="submit">Submit</button>
+        </div>
+    </SmartForm>
 </LazyLoader>
 
 @code
 {
-    [CascadingParameter]
-    public User User { get; set; }
-    
-    private FileAccess FileAccess;
+    private User[] Users;
+    private TestDataModel Model = new();
 
     private Task Load(LazyLoader arg)
     {
-        FileAccess = new FtpFileAccess("vps01.so.host.endelon.link", 21, "example.com", "61P8JZzfjSNyhtZl");
+        Users = UserRepository.Get().ToArray();
+        
+        return Task.CompletedTask;
+    }
 
+    private Task OnValidSubmit()
+    {
         return Task.CompletedTask;
     }
-}
+}

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

@@ -513,6 +513,18 @@ Error from plesk;Error from plesk
 Host;Host
 Username;Username
 SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one;SRV records cannot be updated thanks to the cloudflare api client. Please delete the record and create a new one
+The User field is required.;The User field is required.
+You need to specify a owner;You need to specify a owner
+You need to specify a image;You need to specify a image
+Api Url;Api Url
+Api Key;Api Key
+Duration;Duration
+Enter duration of subscription;Enter duration of subscription
+Copied code to clipboard;Copied code to clipboard
+Invalid or expired subscription code;Invalid or expired subscription code
+Current subscription;Current subscription
+You need to specify a server image;You need to specify a server image
+CPU;CPU
 Hour;Hour
 Day;Day
 Month;Month