Updated WebUI with Radzen and tailwind css
This commit is contained in:
@@ -5,74 +5,181 @@
|
||||
@using DiscordBotCore.Logging
|
||||
@using DiscordBotCore.PluginManagement
|
||||
@using DiscordBotCore.PluginManagement.Models
|
||||
@using WebUI.Models
|
||||
@using WebUI.Services
|
||||
@using WebUI.Components.Shared
|
||||
|
||||
@inject NavigationManager Navigation
|
||||
@inject NotificationService NotificationService
|
||||
|
||||
@inject IPluginManager PluginManager
|
||||
@inject IConfiguration Configuration
|
||||
@inject ILogger Logger
|
||||
@inject IJSRuntime JS
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<CenteredCard Title="Upload plugin">
|
||||
<EditForm Model="@pluginModel" OnValidSubmit="HandleValidSubmit" FormName="pluginForm">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
<PageTitle>Add Local Plugin - Seth Bot</PageTitle>
|
||||
|
||||
<div class="form-group">
|
||||
<ModernLabel Text="Plugin Name"></ModernLabel>
|
||||
<RoundedTextBox @bind-Value="pluginModel.Name" Placeholder="Example Plugin"></RoundedTextBox>
|
||||
<div class="max-w-xl mx-auto space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="text-center space-y-4">
|
||||
<div class="w-20 h-20 mx-auto rounded-2xl bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center shadow-xl shadow-emerald-500/40">
|
||||
<RadzenIcon Icon="upload" class="text-white text-3xl" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<ModernLabel Text="Plugin version"></ModernLabel>
|
||||
<RoundedTextBox @bind-Value="pluginModel.Version" Placeholder="1.0.0"></RoundedTextBox>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-white">Add Local Plugin</h2>
|
||||
<p class="text-slate-400 text-sm">Upload a plugin file from your computer</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<ModernCheckbox @bind-Checked="pluginModel.IsEnabled" Label="Enable Plugin"></ModernCheckbox>
|
||||
</div>
|
||||
<div class="bg-dark-secondary border border-slate-700/50 rounded-2xl p-6">
|
||||
<RadzenTemplateForm TItem="PluginUploadModel" Data="@_pluginModel" Submit="@HandleValidSubmit">
|
||||
<div class="space-y-6">
|
||||
<!-- Plugin Name -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-slate-300">Plugin Name</label>
|
||||
<RadzenTextBox @bind-Value="@_pluginModel.Name"
|
||||
Name="Name"
|
||||
Placeholder="My Awesome Plugin"
|
||||
class="!w-full !bg-dark-tertiary !border-slate-600 hover:!border-primary focus-within:!border-primary !rounded-xl" />
|
||||
<RadzenRequiredValidator Component="Name" Text="Plugin name is required" class="!text-red-400 !text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<ModernFileUploader OnFileUploaded="HandleUploadedFile" AllowedFileTypes="@allowedFileTypes"/>
|
||||
@if (_message is not null)
|
||||
{
|
||||
<p class="mt-2 text-green-600">@_message</p>
|
||||
}
|
||||
<!-- Plugin Version -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-slate-300">Version</label>
|
||||
<RadzenTextBox @bind-Value="@_pluginModel.Version"
|
||||
Name="Version"
|
||||
Placeholder="1.0.0"
|
||||
class="!w-full !bg-dark-tertiary !border-slate-600 hover:!border-primary focus-within:!border-primary !rounded-xl !font-mono !transition-all !duration-300" />
|
||||
<p class="text-slate-500 text-xs">Format: major.minor.patch (e.g., 1.0.0)</p>
|
||||
<RadzenRequiredValidator Component="Version" Text="Version is required" class="!text-red-400 !text-xs" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Enable Plugin Toggle -->
|
||||
<div class="p-4 rounded-xl border border-slate-700/50 bg-dark-tertiary/30">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 flex items-center justify-center shadow-md shadow-amber-500/20">
|
||||
<RadzenIcon Icon="power_settings_new" class="text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-white">Enable Plugin</p>
|
||||
<p class="text-slate-400 text-xs">Activate this plugin after upload</p>
|
||||
</div>
|
||||
</div>
|
||||
<RadzenSwitch @bind-Value="@_pluginModel.IsEnabled" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" type="submit">Upload</button>
|
||||
</EditForm>
|
||||
</CenteredCard>
|
||||
<!-- File Upload -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-slate-300">Plugin File (.dll)</label>
|
||||
<div @onclick="TriggerFileInput" class="cursor-pointer">
|
||||
<div class="@FileUploadCardClass">
|
||||
<div class="flex flex-col items-center gap-4 py-6">
|
||||
@if (_selectedFile != null)
|
||||
{
|
||||
<div class="w-14 h-14 rounded-xl bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center shadow-lg shadow-emerald-500/30 animate-bounce-in">
|
||||
<RadzenIcon Icon="check" class="text-white text-xl" />
|
||||
</div>
|
||||
<div class="text-center space-y-1">
|
||||
<p class="font-semibold text-emerald-400">@_selectedFile.Name</p>
|
||||
<p class="text-slate-400 text-sm">@((_selectedFile.Size / 1024.0).ToString("F1")) KB</p>
|
||||
<p class="text-slate-500 text-xs">Click to change file</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="w-14 h-14 rounded-xl bg-slate-700 flex items-center justify-center">
|
||||
<RadzenIcon Icon="cloud_upload" class="text-slate-400 text-xl" />
|
||||
</div>
|
||||
<div class="text-center space-y-1">
|
||||
<p class="font-semibold text-white">Drop your plugin file here</p>
|
||||
<p class="text-slate-400 text-sm">or click to browse</p>
|
||||
<p class="text-slate-500 text-xs mt-2">Accepts .dll files up to 10MB</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InputFile id="pluginFileInput" OnChange="HandleFileChange" accept=".dll" style="display: none;" />
|
||||
|
||||
@if (!string.IsNullOrEmpty(_uploadMessage))
|
||||
{
|
||||
<div class="p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-sm">
|
||||
@_uploadMessage
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button type="button"
|
||||
@onclick="@(() => Navigation.NavigateTo("/plugins/local"))"
|
||||
class="flex-1 py-2.5 px-4 rounded-xl bg-slate-700 hover:bg-slate-600 text-white font-medium transition-all duration-200">
|
||||
Cancel
|
||||
</button>
|
||||
<RadzenButton Text="Upload Plugin"
|
||||
Icon="upload"
|
||||
ButtonType="ButtonType.Submit"
|
||||
ButtonStyle="ButtonStyle.Success"
|
||||
Disabled="@(_selectedFile == null || !IsValidVersion)"
|
||||
class="flex-1 !bg-gradient-to-r !from-emerald-500 !to-teal-500 hover:!shadow-lg hover:!shadow-emerald-500/30 !transition-all !duration-300 !rounded-xl" />
|
||||
</div>
|
||||
</div>
|
||||
</RadzenTemplateForm>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private PluginUploadModel pluginModel = new();
|
||||
private IBrowserFile? selectedFile;
|
||||
|
||||
private List<string> allowedFileTypes { get; set; } = new List<string> { ".dll"};
|
||||
private PluginUploadModel _pluginModel = new();
|
||||
private IBrowserFile? _selectedFile;
|
||||
private string? _uploadMessage;
|
||||
private const long MaxFileSize = 10 * 1024 * 1024;
|
||||
|
||||
private string? _message;
|
||||
private bool IsValidVersion => System.Text.RegularExpressions.Regex.IsMatch(_pluginModel.Version, @"^\d+\.\d+\.\d+$");
|
||||
|
||||
private bool IsValidVersion => System.Text.RegularExpressions.Regex.IsMatch(pluginModel.Version ?? "", @"^\d+\.\d+\.\d+$");
|
||||
private string FileUploadCardClass => _selectedFile != null
|
||||
? "rounded-xl border-2 border-dashed border-emerald-500 bg-emerald-500/5 transition-all duration-300"
|
||||
: "rounded-xl border-2 border-dashed border-slate-600 bg-dark-tertiary/30 hover:border-primary/50 hover:bg-primary/5 transition-all duration-300";
|
||||
|
||||
private async Task HandleUploadedFile(IBrowserFile file)
|
||||
private async Task TriggerFileInput()
|
||||
{
|
||||
var buffer = new byte[file.Size];
|
||||
await file.OpenReadStream().ReadAsync(buffer);
|
||||
_message = $"Uploaded file: {file.Name} ({file.Size} bytes)";
|
||||
selectedFile = file;
|
||||
await JS.InvokeVoidAsync("eval", "document.getElementById('pluginFileInput').click()");
|
||||
}
|
||||
|
||||
private void HandleFileChange(InputFileChangeEventArgs e)
|
||||
{
|
||||
var file = e.File;
|
||||
_uploadMessage = null;
|
||||
|
||||
if (!file.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_uploadMessage = "Invalid file type. Only .dll files are allowed.";
|
||||
_selectedFile = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.Size > MaxFileSize)
|
||||
{
|
||||
_uploadMessage = "File too large. Maximum size is 10 MB.";
|
||||
_selectedFile = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedFile = file;
|
||||
|
||||
// Auto-fill plugin name from file if empty
|
||||
if (string.IsNullOrEmpty(_pluginModel.Name))
|
||||
{
|
||||
_pluginModel.Name = Path.GetFileNameWithoutExtension(file.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleValidSubmit()
|
||||
{
|
||||
if (!IsValidVersion || selectedFile is null || string.IsNullOrEmpty(selectedFile.Name))
|
||||
if (!IsValidVersion || _selectedFile is null || string.IsNullOrEmpty(_selectedFile.Name))
|
||||
{
|
||||
NotificationService.Notify("Invalid field values. Please check the form.", NotificationType.Error);
|
||||
NotificationService.Notify(NotificationSeverity.Error, "Error", "Invalid field values. Please check the form.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,32 +188,32 @@
|
||||
if (string.IsNullOrEmpty(pluginsFolder))
|
||||
{
|
||||
Logger.Log("Plugins folder is not set in the configuration.", this, LogType.Error);
|
||||
NotificationService.Notify("Plugins folder is not set in the configuration.", NotificationType.Error);
|
||||
NotificationService.Notify(NotificationSeverity.Error, "Error", "Plugins folder is not set in the configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
string filePath = Path.Combine(pluginsFolder, selectedFile.Name);
|
||||
string filePath = Path.Combine(pluginsFolder, _selectedFile.Name);
|
||||
|
||||
await using var stream = selectedFile.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
|
||||
await using var stream = _selectedFile.OpenReadStream(maxAllowedSize: MaxFileSize);
|
||||
await using var fileStream = File.Create(filePath);
|
||||
await stream.CopyToAsync(fileStream);
|
||||
|
||||
LocalPlugin plugin = new LocalPlugin(pluginModel.Name, pluginModel.Version, filePath, new(), true, pluginModel.IsEnabled);
|
||||
LocalPlugin plugin = new LocalPlugin(_pluginModel.Name, _pluginModel.Version, filePath, new(), true, _pluginModel.IsEnabled);
|
||||
await PluginManager.AppendPluginToDatabase(plugin);
|
||||
|
||||
NotificationService.Notify($"Plugin {pluginModel.Name} uploaded successfully!", NotificationType.Success);
|
||||
NotificationService.Notify(NotificationSeverity.Success, "Success", $"Plugin {_pluginModel.Name} uploaded successfully!");
|
||||
|
||||
Navigation.NavigateTo("/");
|
||||
Navigation.NavigateTo("/plugins/local");
|
||||
}
|
||||
|
||||
public class PluginUploadModel
|
||||
private class PluginUploadModel
|
||||
{
|
||||
[Required]
|
||||
[Required(ErrorMessage = "Plugin name is required.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[Required(ErrorMessage = "Version is required.")]
|
||||
public string Version { get; set; } = string.Empty;
|
||||
|
||||
public bool IsEnabled { get; set; }
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,74 +9,172 @@
|
||||
@inject IPluginManager PluginManager
|
||||
@inject ILogger Logger
|
||||
@inject IPluginLoader PluginLoader
|
||||
|
||||
@inject NavigationManager Navigation
|
||||
@inject NotificationService NotificationService
|
||||
@inject DialogService DialogService
|
||||
|
||||
<h3>Installed Plugins</h3>
|
||||
<button class="btn btn-outline-success" @onclick="NavigateToAddPlugin">
|
||||
<span class="bi me-1"></span> Add Local
|
||||
</button>
|
||||
<table class="table table-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Version</th>
|
||||
<th>Offline Added</th>
|
||||
<th>Actions</th>
|
||||
@* <th> *@
|
||||
@* <button class="btn btn-outline-success" @onclick="NavigateToAddPlugin"> *@
|
||||
@* <span class="bi me-1"></span> Add Local *@
|
||||
@* </button> *@
|
||||
@* </th> *@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var plugin in _installedPlugins)
|
||||
<PageTitle>Local Plugins - Seth Bot</PageTitle>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-amber-500 to-orange-500 flex items-center justify-center shadow-lg shadow-amber-500/30">
|
||||
<RadzenIcon Icon="folder" class="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-white">Local Plugins</h2>
|
||||
<p class="text-slate-400 text-sm">Manage your installed plugins</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<RadzenButton Text="Add Local Plugin"
|
||||
Icon="add"
|
||||
ButtonStyle="ButtonStyle.Success"
|
||||
Click="@NavigateToAddPlugin"
|
||||
class="!bg-gradient-to-r !from-emerald-500 !to-teal-500 !rounded-xl" />
|
||||
</div>
|
||||
|
||||
@if (_installedPlugins.Any())
|
||||
{
|
||||
<tr>
|
||||
<td>@plugin.Name</td>
|
||||
<td>@plugin.Version</td>
|
||||
<td>@(plugin.IsOfflineAdded ? "Yes" : "No")</td>
|
||||
<td >
|
||||
<button class="btn btn-danger btn-sm m-1" @onclick="async () => await DeletePluginButtonClick(plugin)">Delete</button>
|
||||
@if (!plugin.IsOfflineAdded)
|
||||
{
|
||||
<button class="btn btn-info btn-sm m-1" @onclick="async () => await PluginDetailsButtonClick(plugin)">Details</button>
|
||||
}
|
||||
</td>
|
||||
@* <td></td> *@
|
||||
</tr>
|
||||
<!-- Plugins DataGrid -->
|
||||
<div class="bg-dark-secondary border border-slate-700/50 rounded-2xl overflow-hidden shadow-xl">
|
||||
<RadzenDataGrid Data="@_installedPlugins" TItem="InstalledPlugin" AllowSorting="true">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn TItem="InstalledPlugin" Title="Plugin" Width="auto">
|
||||
<Template Context="plugin">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-md shadow-primary/20">
|
||||
<RadzenIcon Icon="extension" class="text-white text-sm" />
|
||||
</div>
|
||||
<span class="font-semibold text-white">@plugin.Name</span>
|
||||
</div>
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
|
||||
<RadzenDataGridColumn TItem="InstalledPlugin" Property="Version" Title="Version" Width="120px" TextAlign="TextAlign.Center">
|
||||
<Template Context="plugin">
|
||||
<span class="px-2.5 py-1 text-xs font-medium rounded-lg bg-slate-700/50 text-slate-300">v@plugin.Version</span>
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
|
||||
<RadzenDataGridColumn TItem="InstalledPlugin" Property="IsOfflineAdded" Title="Source" Width="120px" TextAlign="TextAlign.Center">
|
||||
<Template Context="plugin">
|
||||
@if (plugin.IsOfflineAdded)
|
||||
{
|
||||
<span class="px-2.5 py-1 text-xs font-medium rounded-lg bg-amber-500/15 text-amber-400">Local</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="px-2.5 py-1 text-xs font-medium rounded-lg bg-emerald-500/15 text-emerald-400">Online</span>
|
||||
}
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
|
||||
<RadzenDataGridColumn TItem="InstalledPlugin" Title="Actions" Width="150px" TextAlign="TextAlign.End">
|
||||
<Template Context="plugin">
|
||||
<div class="flex gap-2 justify-end">
|
||||
@if (!plugin.IsOfflineAdded)
|
||||
{
|
||||
<button @onclick="@(async () => await PluginDetailsButtonClick(plugin))"
|
||||
class="p-2 rounded-lg bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 transition-colors duration-200">
|
||||
<RadzenIcon Icon="info" class="text-sm" />
|
||||
</button>
|
||||
}
|
||||
<button @onclick="@(async () => await DeletePluginButtonClick(plugin))"
|
||||
class="p-2 rounded-lg bg-red-500/10 text-red-400 hover:bg-red-500/20 transition-colors duration-200">
|
||||
<RadzenIcon Icon="delete" class="text-sm" />
|
||||
</button>
|
||||
</div>
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
</Columns>
|
||||
</RadzenDataGrid>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
else
|
||||
{
|
||||
<!-- Empty State -->
|
||||
<div class="bg-dark-secondary border border-slate-700/50 rounded-2xl p-12">
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-amber-500/30 to-orange-500/30 flex items-center justify-center opacity-60">
|
||||
<RadzenIcon Icon="extension" class="text-amber-400 text-3xl" />
|
||||
</div>
|
||||
<div class="text-center space-y-2">
|
||||
<h3 class="text-lg font-semibold text-white">No plugins installed</h3>
|
||||
<p class="text-slate-400 text-sm">Get started by installing plugins from the marketplace or adding local ones.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3 justify-center">
|
||||
<RadzenButton Text="Browse Online Plugins"
|
||||
Icon="cloud_download"
|
||||
ButtonStyle="ButtonStyle.Primary"
|
||||
Click="@(() => Navigation.NavigateTo("/plugins/online"))"
|
||||
class="!bg-gradient-to-r !from-primary !to-accent hover:!shadow-lg hover:!shadow-primary/30 !transition-all !duration-300 !rounded-xl" />
|
||||
<button @onclick="NavigateToAddPlugin"
|
||||
class="px-4 py-2 rounded-xl bg-slate-700 hover:bg-slate-600 text-white font-medium transition-all duration-200 flex items-center gap-2">
|
||||
<RadzenIcon Icon="add" class="text-sm" />
|
||||
Add Local Plugin
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Plugin Details Dialog -->
|
||||
@if (_showPluginDetailsModal && _selectedPluginDetails != null)
|
||||
{
|
||||
<div class="modal show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5);">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Plugin Details: @_selectedPluginDetails.Name</h5>
|
||||
<button type="button" class="btn-close" @onclick="ClosePluginDetailsModal"></button>
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm animate-fade-in">
|
||||
<div class="bg-dark-secondary border border-slate-700/50 rounded-2xl max-w-md w-full mx-4 overflow-hidden shadow-2xl animate-scale-in">
|
||||
<!-- Header -->
|
||||
<div class="bg-gradient-to-r from-pink-500 to-rose-500 p-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-white/20 flex items-center justify-center backdrop-blur-sm">
|
||||
<RadzenIcon Icon="extension" class="text-white text-xl" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-white">@_selectedPluginDetails.Name</h3>
|
||||
<p class="text-white/70 text-sm">v@_selectedPluginDetails.Version</p>
|
||||
</div>
|
||||
</div>
|
||||
<button @onclick="ClosePluginDetailsModal"
|
||||
class="p-2 rounded-lg hover:bg-white/10 text-white transition-colors duration-200">
|
||||
<RadzenIcon Icon="close" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><strong>Version:</strong> @_selectedPluginDetails.Version</p>
|
||||
<p><strong>Author:</strong> @_selectedPluginDetails.Author</p>
|
||||
<p><strong>Description:</strong> @_selectedPluginDetails.Description</p>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-5 space-y-4">
|
||||
<div class="space-y-1">
|
||||
<p class="text-slate-400 text-xs uppercase tracking-wider">Author</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<RadzenIcon Icon="person" class="text-pink-400" />
|
||||
<span class="text-white">@_selectedPluginDetails.Author</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @onclick="ClosePluginDetailsModal">Close</button>
|
||||
|
||||
<div class="space-y-1">
|
||||
<p class="text-slate-400 text-xs uppercase tracking-wider">Description</p>
|
||||
<p class="text-white">@_selectedPluginDetails.Description</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="p-5 pt-0">
|
||||
<button @onclick="ClosePluginDetailsModal"
|
||||
class="w-full py-2.5 px-4 rounded-xl bg-slate-700 hover:bg-slate-600 text-white font-medium transition-all duration-200">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
|
||||
private readonly List<InstalledPlugin> _installedPlugins = new List<InstalledPlugin>();
|
||||
|
||||
private readonly List<InstalledPlugin> _installedPlugins = new();
|
||||
private bool _showPluginDetailsModal;
|
||||
private OnlinePlugin? _selectedPluginDetails;
|
||||
|
||||
@@ -91,14 +189,15 @@
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
Logger.Log(response.Message, this, LogType.Error);
|
||||
NotificationService.Notify(NotificationSeverity.Error, "Error", response.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
_installedPlugins.RemoveAll(p => p.Name == plugin.Name);
|
||||
Logger.Log($"Plugin {plugin.Name} deleted", this);
|
||||
NotificationService.Notify(NotificationSeverity.Success, "Success", $"Plugin {plugin.Name} deleted successfully!");
|
||||
|
||||
await PluginLoader.LoadPlugins();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
@@ -121,7 +220,6 @@
|
||||
|
||||
private void ClosePluginDetailsModal()
|
||||
{
|
||||
Logger.Log("Closing plugin details modal", this);
|
||||
_showPluginDetailsModal = false;
|
||||
_selectedPluginDetails = null;
|
||||
StateHasChanged();
|
||||
@@ -156,8 +254,8 @@
|
||||
|
||||
private class InstalledPlugin
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Version { get; set; } = string.Empty;
|
||||
public bool IsOfflineAdded { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -4,87 +4,121 @@
|
||||
@using DiscordBotCore.Logging
|
||||
@using DiscordBotCore.PluginManagement
|
||||
|
||||
@using WebUI.Models
|
||||
@using WebUI.Services
|
||||
|
||||
@inject NotificationService NotificationService
|
||||
@inject DialogService DialogService
|
||||
|
||||
<PageTitle>Online Plugins - Seth Bot</PageTitle>
|
||||
|
||||
<h3>Available Plugins</h3>
|
||||
@if (_onlinePlugins.Any())
|
||||
{
|
||||
<table class="table table-responsive text-center align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Author</th>
|
||||
<th>Version</th>
|
||||
<th>Download</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var plugin in _onlinePlugins)
|
||||
{
|
||||
<tr>
|
||||
<td>@plugin.Name</td>
|
||||
<td>@plugin.Description</td>
|
||||
<td>@plugin.Author</td>
|
||||
<td>@plugin.Version</td>
|
||||
<td>
|
||||
<button class="btn btn-outline-dark" @onclick="async () => await InstallPlugin(plugin.Id)">
|
||||
<span>Download</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center shadow-lg shadow-emerald-500/30">
|
||||
<RadzenIcon Icon="cloud_download" class="text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-white">Online Plugins</h2>
|
||||
<p class="text-slate-400 text-sm">Browse and install plugins from the marketplace</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="inline-flex items-center px-3 py-1.5 rounded-full text-sm font-medium bg-emerald-500/15 text-emerald-400">
|
||||
@_onlinePlugins.Count available
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@if (_showInstallPercentage)
|
||||
{
|
||||
<div class="modal show d-block" tabindex="-1" style="background-color: rgba(0, 0, 0, 0.5);">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Installing Plugin...</h5>
|
||||
<button type="button" class="btn-close" @onclick="CloseInstallPercentageModal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Progress: @($"{_installPercentage:F0}")%</p>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar"
|
||||
style="width: @_installPercentage%"
|
||||
aria-valuenow="@_installPercentage"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
@if (_onlinePlugins.Any())
|
||||
{
|
||||
<!-- Plugins Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
@foreach (var plugin in _onlinePlugins)
|
||||
{
|
||||
<div @key="plugin.Id" class="bg-dark-secondary border border-slate-700/50 rounded-2xl p-5 hover:border-primary/50 transition-colors">
|
||||
<div class="flex flex-col h-full gap-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-lg shadow-primary/30">
|
||||
<RadzenIcon Icon="extension" class="text-white" />
|
||||
</div>
|
||||
<span class="px-2.5 py-1 text-xs font-medium rounded-lg bg-slate-700/50 text-slate-300">
|
||||
v@plugin.Version
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 space-y-1">
|
||||
<h3 class="font-semibold text-white">@plugin.Name</h3>
|
||||
<p class="text-sm text-slate-400 line-clamp-2">@plugin.Description</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between pt-2 border-t border-slate-700/50">
|
||||
<div class="flex items-center gap-2 text-slate-500 text-sm">
|
||||
<RadzenIcon Icon="person" class="text-xs" />
|
||||
<span>@plugin.Author</span>
|
||||
</div>
|
||||
|
||||
<RadzenButton Text="Install"
|
||||
Icon="download"
|
||||
ButtonStyle="ButtonStyle.Primary"
|
||||
Size="ButtonSize.Small"
|
||||
Click="@(async () => await InstallPlugin(plugin.Id))"
|
||||
class="!bg-gradient-to-r !from-primary !to-accent !rounded-lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @onclick="CloseInstallPercentageModal">Cancel</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Loading State -->
|
||||
<div class="bg-dark-secondary border border-slate-700/50 rounded-2xl p-12">
|
||||
<div class="flex flex-col items-center justify-center gap-4">
|
||||
<RadzenProgressBarCircular ShowValue="false" Mode="ProgressBarMode.Indeterminate" Size="ProgressBarCircularSize.Large" />
|
||||
<p class="text-slate-400">Loading available plugins...</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Install Progress Dialog -->
|
||||
@if (_showInstallPercentage)
|
||||
{
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm animate-fade-in">
|
||||
<div class="bg-dark-secondary border border-slate-700/50 rounded-2xl max-w-md w-full mx-4 p-6 shadow-2xl animate-scale-in">
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-primary to-accent flex items-center justify-center shadow-xl shadow-primary/40 animate-pulse">
|
||||
<RadzenIcon Icon="cloud_download" class="text-white text-2xl" />
|
||||
</div>
|
||||
|
||||
<div class="text-center space-y-1">
|
||||
<h3 class="text-lg font-semibold text-white">Installing Plugin</h3>
|
||||
<p class="text-slate-400 text-sm">Please wait while we download and install...</p>
|
||||
</div>
|
||||
|
||||
<div class="w-full space-y-2">
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-slate-400">Progress</span>
|
||||
<span class="font-semibold text-white">@($"{_installPercentage:F0}%")</span>
|
||||
</div>
|
||||
<div class="h-2 bg-slate-700 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-gradient-to-r from-primary to-accent rounded-full transition-all duration-300 ease-out" style="width: @_installPercentage%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button @onclick="CloseInstallPercentageModal"
|
||||
class="w-full py-2.5 px-4 rounded-xl bg-slate-700 hover:bg-slate-600 text-white font-medium transition-all duration-200">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Inject]
|
||||
public IPluginManager PluginManager { get; set; }
|
||||
|
||||
[Inject]
|
||||
public ILogger Logger { get; set; }
|
||||
[Inject] public IPluginManager PluginManager { get; set; } = default!;
|
||||
[Inject] public ILogger Logger { get; set; } = default!;
|
||||
|
||||
private bool _showInstallPercentage;
|
||||
private float _installPercentage = 0f;
|
||||
private float _installPercentage;
|
||||
|
||||
private readonly List<OnlinePluginModel> _onlinePlugins = new();
|
||||
|
||||
@@ -132,7 +166,7 @@ else
|
||||
if (pluginData is null)
|
||||
{
|
||||
Logger.Log("Plugin data is null.", this, LogType.Error);
|
||||
NotificationService.Notify("Plugin data is null.", NotificationType.Error);
|
||||
NotificationService.Notify(NotificationSeverity.Error, "Error", "Plugin data is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,8 +182,7 @@ else
|
||||
await PluginManager.InstallPlugin(pluginData, progress);
|
||||
|
||||
Logger.Log($"Plugin {pluginData.Name} installed successfully.", this);
|
||||
|
||||
NotificationService.Notify($"Plugin {pluginData.Name} installed successfully!", NotificationType.Success);
|
||||
NotificationService.Notify(NotificationSeverity.Success, "Success", $"Plugin {pluginData.Name} installed successfully!");
|
||||
|
||||
CloseInstallPercentageModal();
|
||||
StateHasChanged();
|
||||
@@ -165,9 +198,9 @@ else
|
||||
private class OnlinePluginModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Author { get; set; } = string.Empty;
|
||||
public string Version { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user