Updated web ui to razor

This commit is contained in:
2025-04-22 23:40:00 +03:00
parent c548c6191d
commit 2d319f3d34
108 changed files with 983 additions and 168 deletions

5
.gitignore vendored
View File

@@ -455,4 +455,7 @@ $RECYCLE.BIN/
/DiscordBotWebUI/Data
/DiscordBot/Data
/WebUI/Data
/WebUI/Data
/WebUI_Old/Data
/WebUI/bin
/WebUI_Old/bin

View File

@@ -108,6 +108,7 @@ public sealed class PluginManager : IPluginManager
if (!File.Exists(pluginDatabaseFile))
{
_Logger.Log("Plugin database file not found", this, LogType.Warning);
await CreateEmptyPluginDatabase();
return [];
}
@@ -253,4 +254,25 @@ public sealed class PluginManager : IPluginManager
await AppendPluginToDatabase(plugin);
}
private async Task<bool> CreateEmptyPluginDatabase()
{
string ? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
if (pluginDatabaseFile is null)
{
_Logger.Log("Plugin database file path is not present in the config file", this, LogType.Warning);
return false;
}
if (File.Exists(pluginDatabaseFile))
{
_Logger.Log("Plugin database file already exists", this, LogType.Warning);
return false;
}
List<LocalPlugin> installedPlugins = new List<LocalPlugin>();
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
_Logger.Log("Plugin database file created", this, LogType.Info);
return true;
}
}

View File

@@ -31,12 +31,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.Configuratio
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.PluginManagement.Loading", "DiscordBotCore.PluginManagement.Loading\DiscordBotCore.PluginManagement.Loading.csproj", "{E8ED73E1-F7D9-44E7-9542-21BC86338724}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUI", "WebUI\WebUI.csproj", "{F2E2C2D7-C030-4350-907F-86D5B2AAEA0B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.Database.Sqlite", "DiscordBotCore.Database.Sqlite\DiscordBotCore.Database.Sqlite.csproj", "{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotCore.WebApplication", "DiscordBotCore.WebApplication\DiscordBotCore.WebApplication.csproj", "{A65A1D7A-99E9-463F-A205-F96CA54D5C51}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUI_OLD", "WebUI_Old\WebUI_OLD.csproj", "{1D58D773-9BB0-422D-8819-83AF0DF7CCA8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUI", "WebUI\WebUI.csproj", "{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -87,10 +89,6 @@ Global
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8ED73E1-F7D9-44E7-9542-21BC86338724}.Release|Any CPU.Build.0 = Release|Any CPU
{F2E2C2D7-C030-4350-907F-86D5B2AAEA0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2E2C2D7-C030-4350-907F-86D5B2AAEA0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2E2C2D7-C030-4350-907F-86D5B2AAEA0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2E2C2D7-C030-4350-907F-86D5B2AAEA0B}.Release|Any CPU.Build.0 = Release|Any CPU
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D43E9A7-A295-41AC-8B2A-9A877FABB5DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -99,6 +97,14 @@ Global
{A65A1D7A-99E9-463F-A205-F96CA54D5C51}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A65A1D7A-99E9-463F-A205-F96CA54D5C51}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A65A1D7A-99E9-463F-A205-F96CA54D5C51}.Release|Any CPU.Build.0 = Release|Any CPU
{1D58D773-9BB0-422D-8819-83AF0DF7CCA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D58D773-9BB0-422D-8819-83AF0DF7CCA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D58D773-9BB0-422D-8819-83AF0DF7CCA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D58D773-9BB0-422D-8819-83AF0DF7CCA8}.Release|Any CPU.Build.0 = Release|Any CPU
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE42253E-2ED6-4653-B9CC-C2C2551E1EA8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<base href="/"/>
<link rel="stylesheet" href="bootstrap/bootstrap.min.css"/>
<link rel="stylesheet" href="app.css"/>
<link rel="stylesheet" href="WebUI.styles.css"/>
<link rel="icon" type="image/png" href="favicon.png"/>
<HeadOutlet/>
</head>
<body>
<Routes/>
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu/>
</div>
<main>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

View File

@@ -0,0 +1,96 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View File

@@ -0,0 +1,35 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">WebUI</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="settings">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Settings
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="plugins/online">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Online Plugins
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="plugins/local">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Local Plugins
</NavLink>
</div>
</nav>
</div>

View File

@@ -0,0 +1,105 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter] private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@@ -0,0 +1,7 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.

View File

@@ -0,0 +1,142 @@
@page "/plugins/local"
@using DiscordBotCore.Logging
@using DiscordBotCore.PluginManagement
@using DiscordBotCore.PluginManagement.Models
<h3>Installed Plugins</h3>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Version</th>
<th>Offline Added</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var plugin in _installedPlugins)
{
<tr>
<td>@plugin.Name</td>
<td>@plugin.Version</td>
<td>@(plugin.IsOfflineAdded ? "Yes" : "No")</td>
<td>
<button class="btn btn-danger btn-sm" @onclick="async () => await DeletePluginButtonClick(plugin.Name)">Delete</button>
<button class="btn btn-info btn-sm" @onclick="async () => await PluginDetailsButtonClick(plugin.Name)">Details</button>
</td>
</tr>
}
</tbody>
</table>
@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>
<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>
<div class="modal-footer">
<button class="btn btn-secondary" @onclick="ClosePluginDetailsModal">Close</button>
</div>
</div>
</div>
</div>
}
@code {
[Inject]
public IPluginManager PluginManager { get; set; }
[Inject]
public ILogger Logger { get; set; }
private readonly List<InstalledPlugin> _installedPlugins = new List<InstalledPlugin>();
private bool _showPluginDetailsModal;
private OnlinePlugin? _selectedPluginDetails;
private async Task DeletePluginButtonClick(string pluginName)
{
Logger.Log($"Deleting plugin {pluginName}", this);
bool result = await PluginManager.MarkPluginToUninstall(pluginName);
if (!result)
{
Logger.Log($"Failed to delete plugin {pluginName}", this, LogType.Error);
return;
}
_installedPlugins.RemoveAll(p => p.Name == pluginName);
Logger.Log($"Plugin {pluginName} deleted", this);
StateHasChanged();
}
private async Task PluginDetailsButtonClick(string pluginName)
{
Logger.Log($"Getting plugin details for {pluginName}", this);
var pluginDetails = await PluginManager.GetPluginDataByName(pluginName);
if (pluginDetails == null)
{
Logger.Log($"Failed to get details for plugin {pluginName}", this, LogType.Error);
return;
}
_selectedPluginDetails = pluginDetails;
_showPluginDetailsModal = true;
Logger.Log($"Plugin details for {pluginName} retrieved", this);
StateHasChanged();
}
private void ClosePluginDetailsModal()
{
Logger.Log("Closing plugin details modal", this);
_showPluginDetailsModal = false;
_selectedPluginDetails = null;
StateHasChanged();
}
protected override async Task OnInitializedAsync()
{
Logger.Log("Local plugins page initialized", this);
var plugins = await PluginManager.GetInstalledPlugins();
if (!plugins.Any())
{
Logger.Log("No plugins found", this, LogType.Warning);
return;
}
Logger.Log($"Found {plugins.Count} plugins", this);
_installedPlugins.Clear();
foreach (var plugin in plugins)
{
var installedPlugin = new InstalledPlugin
{
Name = plugin.PluginName,
Version = plugin.PluginVersion,
IsOfflineAdded = plugin.IsOfflineAdded
};
_installedPlugins.Add(installedPlugin);
}
StateHasChanged();
}
private class InstalledPlugin
{
public string Name { get; set; }
public string Version { get; set; }
public bool IsOfflineAdded { get; set; }
}
}

View File

@@ -0,0 +1,148 @@
@page "/plugins/online"
@using DiscordBotCore.Logging
@using DiscordBotCore.PluginManagement
<h3>Available Plugins</h3>
@if (_onlinePlugins.Any())
{
<table class="table table-bordered">
<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 type="button" class="btn btn-primary" @onmousedown="async () => await InstallPlugin(plugin.Id)">Download</button>
</td>
</tr>
}
</tbody>
</table>
}
else
{
<p>Loading...</p>
}
@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">
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @onclick="CloseInstallPercentageModal">Cancel</button>
</div>
</div>
</div>
</div>
}
@code {
[Inject]
public IPluginManager PluginManager { get; set; }
[Inject]
public ILogger Logger { get; set; }
private bool _showInstallPercentage;
private float _installPercentage = 0f;
private readonly List<OnlinePluginModel> _onlinePlugins = new();
protected override async Task OnInitializedAsync()
{
Logger.Log("Getting online plugins...", this);
var plugins = await PluginManager.GetPluginsList();
if (!plugins.Any())
{
Logger.Log("No online plugins found.", this);
return;
}
_onlinePlugins.Clear();
foreach (var plugin in plugins)
{
var onlinePlugin = new OnlinePluginModel
{
Id = plugin.Id,
Name = plugin.Name,
Description = plugin.Description,
Author = plugin.Author,
Version = plugin.Version,
};
_onlinePlugins.Add(onlinePlugin);
}
}
private async Task InstallPlugin(int pluginId)
{
var pluginData = await PluginManager.GetPluginDataById(pluginId);
if (pluginData == null)
{
Logger.Log($"Plugin data not found for ID: {pluginId}", this);
return;
}
Logger.Log($"Installing plugin {pluginData.Name}...", this);
_showInstallPercentage = true;
IProgress<float> progress = new Progress<float>(percent =>
{
_installPercentage = percent;
StateHasChanged();
});
await PluginManager.InstallPlugin(pluginData, progress);
Logger.Log($"Plugin {pluginData.Name} installed successfully.", this);
CloseInstallPercentageModal();
}
private void CloseInstallPercentageModal()
{
Logger.Log("Closing install percentage modal", this);
_showInstallPercentage = false;
_installPercentage = 0f;
}
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; }
}
}

View File

@@ -0,0 +1,11 @@
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"/>
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
</Found>
<NotFound>
<LayoutView Layout="@typeof(Layout.MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

View File

@@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using WebUI
@using WebUI.Components

View File

@@ -1,120 +1,28 @@
using System.Reflection;
using DiscordBotCore.Bot;
using DiscordBotCore.Configuration;
using DiscordBotCore.Logging;
using DiscordBotCore.PluginManagement;
using DiscordBotCore.PluginManagement.Helpers;
using DiscordBotCore.PluginManagement.Loading;
using DiscordBotCore.WebApplication;
using IConfiguration = DiscordBotCore.Configuration.IConfiguration;
using ILogger = DiscordBotCore.Logging.ILogger;
#region Load External (Unmanaged) Assemblies
// This code is used to load external (unmanaged) assemblies from the same folder as the executing assembly.
// It handles the AssemblyResolve event to search for the requested assembly in a specific folder structure.
// The folder structure is expected to be: <ExecutingAssemblyDirectory>/Libraries/<RequestingAssemblyName>/<AssemblyName>.<Extension>
// The extensions to search for are specified in the 'extensions' parameter.
var currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += (sender, args) => LoadFromSameFolder(sender,args, [".dll", ".so", ".dylib"]);
static Assembly? LoadFromSameFolder(object? sender, ResolveEventArgs args, string[] extensions)
{
string? requestingAssemblyName = args.RequestingAssembly?.GetName().Name;
string executingAssemblyLocation = Assembly.GetExecutingAssembly().Location;
string? executingAssemblyDirectory = Path.GetDirectoryName(executingAssemblyLocation);
if (string.IsNullOrEmpty(executingAssemblyDirectory))
{
Console.WriteLine($"Error: Could not determine the directory of the executing assembly.");
return null;
}
string librariesFolder = Path.Combine(executingAssemblyDirectory, "Libraries", requestingAssemblyName ?? "");
string requestedAssemblyNameWithoutExtension = new AssemblyName(args.Name).Name;
Console.WriteLine($"Requesting Assembly: {requestingAssemblyName}");
Console.WriteLine($"Requested Assembly Name (without extension): {requestedAssemblyNameWithoutExtension}");
Console.WriteLine($"Searching in folder: {librariesFolder}");
Console.WriteLine($"Searching for extensions: {string.Join(", ", extensions)}");
foreach (string extension in extensions)
{
string assemblyFileName = requestedAssemblyNameWithoutExtension + extension;
string assemblyPath = Path.Combine(librariesFolder, assemblyFileName);
Console.WriteLine($"Attempting to load from: {assemblyPath}");
if (File.Exists(assemblyPath))
{
try
{
var fileAssembly = Assembly.LoadFrom(assemblyPath);
Console.WriteLine($"Successfully loaded Assembly: {fileAssembly.FullName}");
return fileAssembly;
}
catch (Exception ex)
{
Console.WriteLine($"Error loading assembly from '{assemblyPath}': {ex.Message}");
// Optionally log the full exception for debugging
}
}
else
{
Console.WriteLine($"File not found: {assemblyPath}");
}
}
Console.WriteLine($"Failed to load assembly '{args.Name}' from the specified locations.");
return null;
}
#endregion
using WebUI.Components;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.AddDiscordBotComponents();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// Force eager creation of all required services
using (var scope = app.Services.CreateScope())
{
var provider = scope.ServiceProvider;
// Manually resolve all your singletons here
provider.GetRequiredService<ILogger>();
IConfiguration config = provider.GetRequiredService<IConfiguration>();
provider.GetRequiredService<IPluginRepositoryConfiguration>();
provider.GetRequiredService<IPluginRepository>();
provider.GetRequiredService<IPluginManager>();
provider.GetRequiredService<IPluginLoader>();
provider.GetRequiredService<IDiscordBotApplication>();
// Optional: Log that all services were initialized
provider.GetRequiredService<ILogger>().Log("All core services have been initialized at startup.");
}
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();

View File

@@ -1,38 +1,38 @@
{
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52426",
"sslPort": 44369
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5111",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:41815",
"sslPort": 44333
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7222;http://localhost:5111",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5028",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7127;http://localhost:5028",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
}

View File

@@ -4,17 +4,9 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DiscordBotCore.Database.Sqlite\DiscordBotCore.Database.Sqlite.csproj" />
<ProjectReference Include="..\DiscordBotCore.WebApplication\DiscordBotCore.WebApplication.csproj" />
</ItemGroup>

View File

@@ -5,10 +5,5 @@
"Microsoft.AspNetCore": "Warning"
}
},
"Logger": {
"LogFormat": "{ThrowTime} {SenderName} {Message}",
"LogFolder": "./Data/Logs"
},
"ConfigFile": "./Data/Resources/config.json",
"AllowedHosts": "*"
}

51
WebUI/wwwroot/app.css Normal file
View File

@@ -0,0 +1,51 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}

BIN
WebUI/wwwroot/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -3,7 +3,7 @@ using DiscordBotCore.PluginManagement.Loading;
using Microsoft.AspNetCore.Mvc;
using ILogger = DiscordBotCore.Logging.ILogger;
namespace WebUI.Controllers;
namespace WebUI_OLD.Controllers;
public class HomeController : Controller
{

View File

@@ -1,9 +1,9 @@
using DiscordBotCore.PluginManagement;
using Microsoft.AspNetCore.Mvc;
using WebUI.Models;
using WebUI_OLD.Models;
using ILogger = DiscordBotCore.Logging.ILogger;
namespace WebUI.Controllers;
namespace WebUI_OLD.Controllers;
public class PluginsController : Controller
{

View File

@@ -1,9 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using WebUI.Models;
using WebUI_OLD.Models;
using IConfiguration = DiscordBotCore.Configuration.IConfiguration;
using ILogger = DiscordBotCore.Logging.ILogger;
namespace WebUI.Controllers;
namespace WebUI_OLD.Controllers;
public class SettingsController : Controller
{

View File

@@ -1,4 +1,4 @@
namespace WebUI.Models;
namespace WebUI_OLD.Models;
public class ErrorViewModel
{

View File

@@ -1,4 +1,4 @@
namespace WebUI.Models;
namespace WebUI_OLD.Models;
public class InstalledPluginViewModel
{

View File

@@ -1,4 +1,4 @@
namespace WebUI.Models;
namespace WebUI_OLD.Models;
public class OnlinePluginViewModel
{

View File

@@ -1,4 +1,4 @@
namespace WebUI.Models;
namespace WebUI_OLD.Models;
public class PluginDetailsViewModel
{

View File

@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
namespace WebUI.Models;
namespace WebUI_OLD.Models;
public class SettingsViewModel
{

120
WebUI_Old/Program.cs Normal file
View File

@@ -0,0 +1,120 @@
using System.Reflection;
using DiscordBotCore.Bot;
using DiscordBotCore.Configuration;
using DiscordBotCore.Logging;
using DiscordBotCore.PluginManagement;
using DiscordBotCore.PluginManagement.Helpers;
using DiscordBotCore.PluginManagement.Loading;
using DiscordBotCore.WebApplication;
using IConfiguration = DiscordBotCore.Configuration.IConfiguration;
using ILogger = DiscordBotCore.Logging.ILogger;
#region Load External (Unmanaged) Assemblies
// This code is used to load external (unmanaged) assemblies from the same folder as the executing assembly.
// It handles the AssemblyResolve event to search for the requested assembly in a specific folder structure.
// The folder structure is expected to be: <ExecutingAssemblyDirectory>/Libraries/<RequestingAssemblyName>/<AssemblyName>.<Extension>
// The extensions to search for are specified in the 'extensions' parameter.
var currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += (sender, args) => LoadFromSameFolder(sender,args, [".dll", ".so", ".dylib"]);
static Assembly? LoadFromSameFolder(object? sender, ResolveEventArgs args, string[] extensions)
{
string? requestingAssemblyName = args.RequestingAssembly?.GetName().Name;
string executingAssemblyLocation = Assembly.GetExecutingAssembly().Location;
string? executingAssemblyDirectory = Path.GetDirectoryName(executingAssemblyLocation);
if (string.IsNullOrEmpty(executingAssemblyDirectory))
{
Console.WriteLine($"Error: Could not determine the directory of the executing assembly.");
return null;
}
string librariesFolder = Path.Combine(executingAssemblyDirectory, "Libraries", requestingAssemblyName ?? "");
string requestedAssemblyNameWithoutExtension = new AssemblyName(args.Name).Name;
Console.WriteLine($"Requesting Assembly: {requestingAssemblyName}");
Console.WriteLine($"Requested Assembly Name (without extension): {requestedAssemblyNameWithoutExtension}");
Console.WriteLine($"Searching in folder: {librariesFolder}");
Console.WriteLine($"Searching for extensions: {string.Join(", ", extensions)}");
foreach (string extension in extensions)
{
string assemblyFileName = requestedAssemblyNameWithoutExtension + extension;
string assemblyPath = Path.Combine(librariesFolder, assemblyFileName);
Console.WriteLine($"Attempting to load from: {assemblyPath}");
if (File.Exists(assemblyPath))
{
try
{
var fileAssembly = Assembly.LoadFrom(assemblyPath);
Console.WriteLine($"Successfully loaded Assembly: {fileAssembly.FullName}");
return fileAssembly;
}
catch (Exception ex)
{
Console.WriteLine($"Error loading assembly from '{assemblyPath}': {ex.Message}");
// Optionally log the full exception for debugging
}
}
else
{
Console.WriteLine($"File not found: {assemblyPath}");
}
}
Console.WriteLine($"Failed to load assembly '{args.Name}' from the specified locations.");
return null;
}
#endregion
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.AddDiscordBotComponents();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// Force eager creation of all required services
using (var scope = app.Services.CreateScope())
{
var provider = scope.ServiceProvider;
// Manually resolve all your singletons here
provider.GetRequiredService<ILogger>();
IConfiguration config = provider.GetRequiredService<IConfiguration>();
provider.GetRequiredService<IPluginRepositoryConfiguration>();
provider.GetRequiredService<IPluginRepository>();
provider.GetRequiredService<IPluginManager>();
provider.GetRequiredService<IPluginLoader>();
provider.GetRequiredService<IDiscordBotApplication>();
// Optional: Log that all services were initialized
provider.GetRequiredService<ILogger>().Log("All core services have been initialized at startup.");
}
app.Run();

View File

@@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52426",
"sslPort": 44369
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5111",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7222;http://localhost:5111",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -3,16 +3,16 @@
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - WebUI</title>
<title>@ViewData["Title"] - WebUI_OLD</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/WebUI.styles.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/WebUI_OLD.styles.css" asp-append-version="true"/>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WebUI</a>
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">WebUI_OLD</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@@ -41,7 +41,7 @@
<footer class="border-top footer text-muted">
<div class="container">
&copy; 2025 - WebUI - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
&copy; 2025 - WebUI_OLD - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>

View File

@@ -1,3 +1,3 @@
@using WebUI
@using WebUI.Models
@using WebUI_OLD
@using WebUI_OLD.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DiscordBotCore.Database.Sqlite\DiscordBotCore.Database.Sqlite.csproj" />
<ProjectReference Include="..\DiscordBotCore.WebApplication\DiscordBotCore.WebApplication.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Logger": {
"LogFormat": "{ThrowTime} {SenderName} {Message}",
"LogFolder": "./Data/Logs"
},
"ConfigFile": "./Data/Resources/config.json",
"AllowedHosts": "*"
}

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More