Improved UI experience for the Main page

This commit is contained in:
2025-04-06 18:36:20 +03:00
parent 8aaefac706
commit 87c889266b
8 changed files with 176 additions and 48 deletions

View File

@@ -1,3 +1,4 @@
using Discord.WebSocket;
using DiscordBotCore.PluginCore;
using DiscordBotCore.PluginCore.Interfaces;
@@ -9,4 +10,6 @@ public interface IPluginLoader
List<IDbEvent> Events { get; }
List<IDbSlashCommand> SlashCommands { get; }
Task LoadPlugins();
void SetClient(DiscordSocketClient client);
}

View File

@@ -13,27 +13,19 @@ namespace DiscordBotCore.PluginManagement.Loading;
public sealed class PluginLoader : IPluginLoader
{
private readonly DiscordSocketClient _DiscordClient;
private readonly IPluginManager _PluginManager;
private readonly ILogger _Logger;
private readonly IConfiguration _Configuration;
public delegate void CommandLoaded(IDbCommand eCommand);
public delegate void EventLoaded(IDbEvent eEvent);
public delegate void SlashCommandLoaded(IDbSlashCommand eSlashCommand);
public CommandLoaded? OnCommandLoaded;
public EventLoaded? OnEventLoaded;
public SlashCommandLoaded? OnSlashCommandLoaded;
public List<IDbCommand> Commands { get; private set; } = new List<IDbCommand>();
public List<IDbEvent> Events { get; private set; } = new List<IDbEvent>();
public List<IDbSlashCommand> SlashCommands { get; private set; } = new List<IDbSlashCommand>();
public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration, DiscordSocketClient discordSocketDiscordClient)
private DiscordSocketClient? _discordClient;
public PluginLoader(IPluginManager pluginManager, ILogger logger, IConfiguration configuration)
{
_PluginManager = pluginManager;
_DiscordClient = discordSocketDiscordClient;
_Logger = logger;
_Configuration = configuration;
}
@@ -54,6 +46,24 @@ public sealed class PluginLoader : IPluginLoader
await loader.Load();
}
public void SetClient(DiscordSocketClient client)
{
if (_discordClient is not null)
{
_Logger.Log("A client is already set. Please set the client only once.", this, LogType.Error);
return;
}
if (client.LoginState != LoginState.LoggedIn)
{
_Logger.Log("Client is not logged in. Retry after the client is logged in", this, LogType.Error);
return;
}
_Logger.Log("Client is set to the plugin loader", this);
_discordClient = client;
}
private void FileLoadedException(string fileName, Exception exception)
{
_Logger.LogException(exception, this);
@@ -62,7 +72,7 @@ public sealed class PluginLoader : IPluginLoader
private void InitializeDbCommand(IDbCommand command)
{
Commands.Add(command);
OnCommandLoaded?.Invoke(command);
_Logger.Log("Command loaded: " + command.Command, this);
}
private void InitializeEvent(IDbEvent eEvent)
@@ -73,7 +83,7 @@ public sealed class PluginLoader : IPluginLoader
}
Events.Add(eEvent);
OnEventLoaded?.Invoke(eEvent);
_Logger.Log("Event loaded: " + eEvent, this);
}
private async void InitializeSlashCommand(IDbSlashCommand slashCommand)
@@ -83,9 +93,9 @@ public sealed class PluginLoader : IPluginLoader
() =>
{
if (slashCommand.HasInteraction)
_DiscordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction);
_discordClient.InteractionCreated += interaction => slashCommand.ExecuteInteraction(_Logger, interaction);
SlashCommands.Add(slashCommand);
OnSlashCommandLoaded?.Invoke(slashCommand);
_Logger.Log("Slash command loaded: " + slashCommand.Name, this);
},
HandleError
);
@@ -116,7 +126,7 @@ public sealed class PluginLoader : IPluginLoader
}
IDbEventExecutingArgument args = new DbEventExecutingArgument(
_Logger,
_DiscordClient,
_discordClient,
_Configuration.Get<string>("prefix"),
new DirectoryInfo(Path.Combine(_Configuration.Get<string>("ResourcesPath"), dbEvent.Name)));
dbEvent.Start(args);
@@ -139,7 +149,7 @@ public sealed class PluginLoader : IPluginLoader
return Result.Failure(new Exception("dbSlashCommand is null"));
}
if (_DiscordClient.Guilds.Count == 0)
if (_discordClient.Guilds.Count == 0)
{
return Result.Failure(new Exception("No guilds found"));
}
@@ -166,7 +176,7 @@ public sealed class PluginLoader : IPluginLoader
}
}
await _DiscordClient.CreateGlobalApplicationCommandAsync(builder.Build());
await _discordClient.CreateGlobalApplicationCommandAsync(builder.Build());
return Result.Success();
}
@@ -178,7 +188,7 @@ public sealed class PluginLoader : IPluginLoader
private async Task<bool> EnableSlashCommandPerGuild(ulong guildId, SlashCommandBuilder builder)
{
SocketGuild? guild = _DiscordClient.GetGuild(guildId);
SocketGuild? guild = _discordClient.GetGuild(guildId);
if (guild is null)
{
_Logger.Log("Failed to get guild with ID " + guildId, typeof(PluginLoader), LogType.Error);

View File

@@ -20,7 +20,7 @@ public class DiscordBotApplication : IDiscordBotApplication
private readonly IConfiguration _Configuration;
private readonly IPluginLoader _PluginLoader;
private bool IsReady { get; set; }
public bool IsReady { get; private set; }
public DiscordSocketClient Client { get; private set; }
@@ -34,6 +34,28 @@ public class DiscordBotApplication : IDiscordBotApplication
this._PluginLoader = pluginLoader;
}
public async Task StopAsync()
{
if (!IsReady)
{
_Logger.Log("Can not stop the bot. It is not yet initialized.", this, LogType.Error);
return;
}
await Client.LogoutAsync();
await Client.StopAsync();
Client.Log -= Log;
Client.LoggedIn -= LoggedIn;
Client.Ready -= Ready;
Client.Disconnected -= Client_Disconnected;
await Client.DisposeAsync();
IsReady = false;
}
/// <summary>
/// The start method for the bot. This method is used to load the bot
/// </summary>
@@ -91,6 +113,7 @@ public class DiscordBotApplication : IDiscordBotApplication
private Task LoggedIn()
{
_Logger.Log("Successfully Logged In", this);
_PluginLoader.SetClient(Client);
return Task.CompletedTask;
}

View File

@@ -5,10 +5,16 @@ namespace DiscordBotCore.Bot;
public interface IDiscordBotApplication
{
public bool IsReady { get; }
public DiscordSocketClient Client { get; }
/// <summary>
/// The start method for the bot. This method is used to load the bot
/// </summary>
Task StartAsync();
/// <summary>
/// Stops the bot and cleans up resources.
/// </summary>
Task StopAsync();
}

View File

@@ -1,6 +1,6 @@
using System.Diagnostics;
using DiscordBotCore.Bot;
using DiscordBotCore.PluginManagement.Loading;
using Microsoft.AspNetCore.Mvc;
using WebUI.Models;
using ILogger = DiscordBotCore.Logging.ILogger;
namespace WebUI.Controllers;
@@ -8,25 +8,64 @@ namespace WebUI.Controllers;
public class HomeController : Controller
{
private readonly ILogger _logger;
public HomeController(ILogger logger)
private readonly IDiscordBotApplication _discordBotApplication;
private readonly IPluginLoader _pluginLoader;
public HomeController(ILogger logger, IDiscordBotApplication discordBotApplication, IPluginLoader pluginLoader)
{
_logger = logger;
_discordBotApplication = discordBotApplication;
_pluginLoader = pluginLoader;
}
[HttpGet]
public IActionResult Index()
{
_logger.Log("Index page loaded", this);
ViewBag.IsRunning = _discordBotApplication.IsReady;
return View();
}
public IActionResult Privacy()
[HttpPost]
public async Task<IActionResult> StartApplication()
{
return View();
}
if (_discordBotApplication.IsReady)
{
_logger.Log("Application already started", this);
return RedirectToAction("Index");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
await _discordBotApplication.StartAsync();
return RedirectToAction("Index");
}
[HttpPost]
public async Task<IActionResult> StopApplication()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
if (!_discordBotApplication.IsReady)
{
_logger.Log("Application already stopped", this);
return RedirectToAction("Index");
}
await _discordBotApplication.StopAsync();
return RedirectToAction("Index");
}
[HttpGet]
public JsonResult GetLogs()
{
var logText = _logger.GetLogsHistory();
var logs = logText.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
return Json(logs);
}
[HttpPost]
public async Task<IActionResult> LoadPlugins()
{
_logger.Log("Loading plugins", this);
await _pluginLoader.LoadPlugins();
_logger.Log("Plugins loaded", this);
return RedirectToAction("Index");
}
}

View File

@@ -142,6 +142,15 @@ builder.Services.AddSingleton<IPluginManager>(sp =>
IPluginManager pluginManager = new PluginManager(pluginRepository, logger, configuration);
return pluginManager;
});
builder.Services.AddSingleton<IPluginLoader>(sp =>
{
IPluginManager pluginManager = sp.GetRequiredService<IPluginManager>();
ILogger logger = sp.GetRequiredService<ILogger>();
IConfiguration configuration = sp.GetRequiredService<IConfiguration>();
return new PluginLoader(pluginManager, logger, configuration);
});
builder.Services.AddSingleton<IDiscordBotApplication>(sp =>
{
ILogger logger = sp.GetRequiredService<ILogger>();
@@ -150,14 +159,7 @@ builder.Services.AddSingleton<IDiscordBotApplication>(sp =>
return new DiscordBotApplication(logger, configuration, pluginLoader);
});
builder.Services.AddSingleton<IPluginLoader>(sp =>
{
IPluginManager pluginManager = sp.GetRequiredService<IPluginManager>();
ILogger logger = sp.GetRequiredService<ILogger>();
IConfiguration configuration = sp.GetRequiredService<IConfiguration>();
IDiscordBotApplication discordBotApplication = sp.GetRequiredService<IDiscordBotApplication>();
return new PluginLoader(pluginManager, logger, configuration, discordBotApplication.Client);
});
var app = builder.Build();

View File

@@ -1,8 +1,59 @@
@{
ViewData["Title"] = "Home Page";
var isRunning = ViewBag.IsRunning ?? false;
ViewBag.Title = "Console Log Viewer";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div class="container mt-5">
<div class="row">
<div class="col-md-8">
<h4>Console Log</h4>
<div id="consoleLog" class="border p-3 bg-dark text-white" style="height: 400px; overflow-y: scroll; font-family: monospace;"></div>
</div>
<div class="col-md-4">
<h4>Controls</h4>
<form method="post" asp-action="StartApplication">
<button type="submit" class="btn btn-success mb-2">Start Application</button>
</form>
@if (isRunning)
{
<form method="post" asp-action="StopApplication">
<button type="submit" class="btn btn-danger mb-2">Stop Application</button>
</form>
<form method="post" asp-action="LoadPlugins">
<button type="submit" class="btn btn-info mb-2">Load Plugins</button>
</form>
}
</div>
</div>
</div>
@section Scripts {
<script>
let lastLogCount = 0;
function fetchLogs() {
fetch("/Home/GetLogs")
.then(res => res.json())
.then(logs => {
const logDiv = document.getElementById("consoleLog");
if (logs.length !== lastLogCount) {
logDiv.innerHTML = logs.map(line => formatLog(line)).join('');
logDiv.scrollTop = logDiv.scrollHeight;
lastLogCount = logs.length;
}
});
}
function formatLog(line) {
if (line.includes("[Error]")) return `<div style="color: #ff4d4d;">${line}</div>`;
if (line.includes("[Warning]")) return `<div style="color: #ffa500;">${line}</div>`;
if (line.includes("[Debug]")) return `<div style="color: #88f;">${line}</div>`;
return `<div>${line}</div>`;
}
setInterval(fetchLogs, 2000);
fetchLogs();
</script>
}

View File

@@ -1,6 +0,0 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>