Updated web ui to razor
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -455,4 +455,7 @@ $RECYCLE.BIN/
|
||||
|
||||
/DiscordBotWebUI/Data
|
||||
/DiscordBot/Data
|
||||
/WebUI/Data
|
||||
/WebUI/Data
|
||||
/WebUI_Old/Data
|
||||
/WebUI/bin
|
||||
/WebUI_Old/bin
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
20
WebUI/Components/App.razor
Normal file
20
WebUI/Components/App.razor
Normal 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>
|
||||
19
WebUI/Components/Layout/MainLayout.razor
Normal file
19
WebUI/Components/Layout/MainLayout.razor
Normal 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>
|
||||
96
WebUI/Components/Layout/MainLayout.razor.css
Normal file
96
WebUI/Components/Layout/MainLayout.razor.css
Normal 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;
|
||||
}
|
||||
35
WebUI/Components/Layout/NavMenu.razor
Normal file
35
WebUI/Components/Layout/NavMenu.razor
Normal 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>
|
||||
105
WebUI/Components/Layout/NavMenu.razor.css
Normal file
105
WebUI/Components/Layout/NavMenu.razor.css
Normal 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;
|
||||
}
|
||||
}
|
||||
36
WebUI/Components/Pages/Error.razor
Normal file
36
WebUI/Components/Pages/Error.razor
Normal 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;
|
||||
|
||||
}
|
||||
7
WebUI/Components/Pages/Home.razor
Normal file
7
WebUI/Components/Pages/Home.razor
Normal file
@@ -0,0 +1,7 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
142
WebUI/Components/Pages/Plugins/Local.razor
Normal file
142
WebUI/Components/Pages/Plugins/Local.razor
Normal 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; }
|
||||
}
|
||||
}
|
||||
148
WebUI/Components/Pages/Plugins/Online.razor
Normal file
148
WebUI/Components/Pages/Plugins/Online.razor
Normal 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; }
|
||||
}
|
||||
}
|
||||
11
WebUI/Components/Routes.razor
Normal file
11
WebUI/Components/Routes.razor
Normal 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>
|
||||
10
WebUI/Components/_Imports.razor
Normal file
10
WebUI/Components/_Imports.razor
Normal 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
|
||||
108
WebUI/Program.cs
108
WebUI/Program.cs
@@ -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();
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
51
WebUI/wwwroot/app.css
Normal 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
BIN
WebUI/wwwroot/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
{
|
||||
@@ -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
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace WebUI.Models;
|
||||
namespace WebUI_OLD.Models;
|
||||
|
||||
public class ErrorViewModel
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace WebUI.Models;
|
||||
namespace WebUI_OLD.Models;
|
||||
|
||||
public class InstalledPluginViewModel
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace WebUI.Models;
|
||||
namespace WebUI_OLD.Models;
|
||||
|
||||
public class OnlinePluginViewModel
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace WebUI.Models;
|
||||
namespace WebUI_OLD.Models;
|
||||
|
||||
public class PluginDetailsViewModel
|
||||
{
|
||||
@@ -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
120
WebUI_Old/Program.cs
Normal 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();
|
||||
38
WebUI_Old/Properties/launchSettings.json
Normal file
38
WebUI_Old/Properties/launchSettings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
© 2025 - WebUI - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
© 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>
|
||||
@@ -1,3 +1,3 @@
|
||||
@using WebUI
|
||||
@using WebUI.Models
|
||||
@using WebUI_OLD
|
||||
@using WebUI_OLD.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
21
WebUI_Old/WebUI_OLD.csproj
Normal file
21
WebUI_Old/WebUI_OLD.csproj
Normal 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>
|
||||
8
WebUI_Old/appsettings.Development.json
Normal file
8
WebUI_Old/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
14
WebUI_Old/appsettings.json
Normal file
14
WebUI_Old/appsettings.json
Normal 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": "*"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
7
WebUI_Old/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
vendored
Normal file
7
WebUI_Old/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
WebUI_Old/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
vendored
Normal file
1
WebUI_Old/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
vendored
Normal file
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
Reference in New Issue
Block a user