Updated plugin version control. Added notification system to web ui
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
using DiscordBotCore.Utilities.Responses;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement;
|
||||
|
||||
public interface IPluginManager
|
||||
{
|
||||
Task<List<OnlinePlugin>> GetPluginsList();
|
||||
Task<OnlinePlugin?> GetPluginDataByName(string pluginName);
|
||||
Task<OnlinePlugin?> GetPluginDataById(int pluginId);
|
||||
Task AppendPluginToDatabase(LocalPlugin pluginData);
|
||||
Task<IResponse<OnlinePlugin>> GetPluginDataByName(string pluginName);
|
||||
Task<IResponse<OnlinePlugin>> GetPluginDataById(int pluginId);
|
||||
Task<IResponse<bool>> AppendPluginToDatabase(LocalPlugin pluginData);
|
||||
Task<List<LocalPlugin>> GetInstalledPlugins();
|
||||
Task<bool> IsPluginInstalled(string pluginName);
|
||||
Task<string?> GetDependencyLocation(string dependencyName);
|
||||
Task<string?> GetDependencyLocation(string dependencyName, string pluginName);
|
||||
Task<IResponse<string>> GetDependencyLocation(string dependencyName);
|
||||
Task<IResponse<string>> GetDependencyLocation(string dependencyName, string pluginName);
|
||||
string GenerateDependencyRelativePath(string pluginName, string dependencyPath);
|
||||
Task InstallPlugin(OnlinePlugin plugin, IProgress<float> progress);
|
||||
Task<IResponse<bool>> InstallPlugin(OnlinePlugin plugin, IProgress<float> progress);
|
||||
Task SetEnabledStatus(string pluginName, bool status);
|
||||
Task<bool> UninstallPluginByName(string pluginName);
|
||||
Task<LocalPlugin?> GetLocalPluginByName(string pluginName);
|
||||
Task<IResponse<bool>> UninstallPluginByName(string pluginName);
|
||||
Task<IResponse<LocalPlugin>> GetLocalPluginByName(string pluginName);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using DiscordBotCore.Logging;
|
||||
using System.Diagnostics;
|
||||
using DiscordBotCore.Logging;
|
||||
using DiscordBotCore.Networking;
|
||||
using DiscordBotCore.PluginManagement.Helpers;
|
||||
using DiscordBotCore.PluginManagement.Models;
|
||||
using DiscordBotCore.Utilities;
|
||||
using DiscordBotCore.Configuration;
|
||||
using DiscordBotCore.Utilities.Responses;
|
||||
using OperatingSystem = DiscordBotCore.Utilities.OperatingSystem;
|
||||
|
||||
namespace DiscordBotCore.PluginManagement;
|
||||
@@ -36,53 +38,53 @@ public sealed class PluginManager : IPluginManager
|
||||
return onlinePlugins;
|
||||
}
|
||||
|
||||
public async Task<OnlinePlugin?> GetPluginDataByName(string pluginName)
|
||||
public async Task<IResponse<OnlinePlugin>> GetPluginDataByName(string pluginName)
|
||||
{
|
||||
int os = OperatingSystem.GetOperatingSystemInt();
|
||||
var plugin = await _PluginRepository.GetPluginByName(pluginName, os, false);
|
||||
|
||||
if (plugin == null)
|
||||
if (plugin is null)
|
||||
{
|
||||
_Logger.Log($"Plugin {pluginName} not found in the repository for operating system {OperatingSystem.GetOperatingSystemString((OperatingSystem.OperatingSystemEnum)os)}.", LogType.Warning);
|
||||
return null;
|
||||
return Response<OnlinePlugin>.Failure($"Plugin {pluginName} not found in the repository for operating system {OperatingSystem.GetOperatingSystemString((OperatingSystem.OperatingSystemEnum)os)}.");
|
||||
}
|
||||
|
||||
return plugin;
|
||||
return Response<OnlinePlugin>.Success(plugin);
|
||||
}
|
||||
|
||||
public async Task<OnlinePlugin?> GetPluginDataById(int pluginId)
|
||||
public async Task<IResponse<OnlinePlugin>> GetPluginDataById(int pluginId)
|
||||
{
|
||||
var plugin = await _PluginRepository.GetPluginById(pluginId);
|
||||
if (plugin is null)
|
||||
{
|
||||
_Logger.Log($"Plugin {pluginId} not found in the repository.", this, LogType.Warning);
|
||||
return null;
|
||||
return Response<OnlinePlugin>.Failure($"Plugin {pluginId} not found in the repository.");
|
||||
}
|
||||
|
||||
return plugin;
|
||||
return Response<OnlinePlugin>.Success(plugin);
|
||||
}
|
||||
|
||||
private async Task RemovePluginFromDatabase(string pluginName)
|
||||
private async Task<IResponse<bool>> RemovePluginFromDatabase(string pluginName)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
throw new Exception("Plugin database file not found");
|
||||
return Response.Failure("PluginDatabase file path is not present in the config file");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
|
||||
installedPlugins.RemoveAll(p => p.PluginName == pluginName);
|
||||
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
|
||||
|
||||
return Response.Success();
|
||||
}
|
||||
|
||||
public async Task AppendPluginToDatabase(LocalPlugin pluginData)
|
||||
public async Task<IResponse<bool>> AppendPluginToDatabase(LocalPlugin pluginData)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
throw new Exception("Plugin database file not found");
|
||||
return Response.Failure("PluginDatabase file path is not present in the config file");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
@@ -100,6 +102,8 @@ public sealed class PluginManager : IPluginManager
|
||||
|
||||
installedPlugins.Add(pluginData);
|
||||
await JsonManager.SaveToJsonFile(pluginDatabaseFile, installedPlugins);
|
||||
|
||||
return Response.Success();
|
||||
}
|
||||
|
||||
public async Task<List<LocalPlugin>> GetInstalledPlugins()
|
||||
@@ -121,19 +125,7 @@ public sealed class PluginManager : IPluginManager
|
||||
return await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
}
|
||||
|
||||
public async Task<bool> IsPluginInstalled(string pluginName)
|
||||
{
|
||||
string? pluginDatabaseFile = _Configuration.Get<string>("PluginDatabase");
|
||||
if (pluginDatabaseFile is null)
|
||||
{
|
||||
throw new Exception("Plugin database file not found");
|
||||
}
|
||||
|
||||
List<LocalPlugin> installedPlugins = await JsonManager.ConvertFromJson<List<LocalPlugin>>(await File.ReadAllTextAsync(pluginDatabaseFile));
|
||||
return installedPlugins.Any(plugin => plugin.PluginName == pluginName);
|
||||
}
|
||||
|
||||
public async Task<string?> GetDependencyLocation(string dependencyName)
|
||||
public async Task<IResponse<string>> GetDependencyLocation(string dependencyName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
@@ -142,14 +134,14 @@ public sealed class PluginManager : IPluginManager
|
||||
if (plugin.ListOfExecutableDependencies.TryGetValue(dependencyName, out var dependencyPath))
|
||||
{
|
||||
string relativePath = GenerateDependencyRelativePath(plugin.PluginName, dependencyPath);
|
||||
return relativePath;
|
||||
return Response<string>.Success(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Response<string>.Failure($"Dependency {dependencyName} not found in the installed plugins.");
|
||||
}
|
||||
|
||||
public async Task<string?> GetDependencyLocation(string dependencyName, string pluginName)
|
||||
public async Task<IResponse<string>> GetDependencyLocation(string dependencyName, string pluginName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
|
||||
@@ -159,11 +151,11 @@ public sealed class PluginManager : IPluginManager
|
||||
{
|
||||
string dependencyPath = plugin.ListOfExecutableDependencies[dependencyName];
|
||||
string relativePath = GenerateDependencyRelativePath(pluginName, dependencyPath);
|
||||
return relativePath;
|
||||
return Response<string>.Success(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return Response<string>.Failure($"Dependency {dependencyName} not found in the installed plugins.");
|
||||
}
|
||||
|
||||
public string GenerateDependencyRelativePath(string pluginName, string dependencyPath)
|
||||
@@ -172,12 +164,22 @@ public sealed class PluginManager : IPluginManager
|
||||
return relative;
|
||||
}
|
||||
|
||||
public async Task InstallPlugin(OnlinePlugin plugin, IProgress<float> progress)
|
||||
public async Task<IResponse<bool>> InstallPlugin(OnlinePlugin plugin, IProgress<float> progress)
|
||||
{
|
||||
string? pluginsFolder = _Configuration.Get<string>("PluginFolder");
|
||||
if (pluginsFolder is null)
|
||||
{
|
||||
throw new Exception("Plugin folder not found");
|
||||
return Response.Failure("Plugin folder path is not present in the config file");
|
||||
}
|
||||
|
||||
var localPluginResponse = await GetLocalPluginByName(plugin.Name);
|
||||
if (localPluginResponse is { IsSuccess: true, Data: not null })
|
||||
{
|
||||
var response = await IsNewVersion(localPluginResponse.Data.PluginVersion, plugin.Version);
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
List<OnlineDependencyInfo> dependencies = await _PluginRepository.GetDependenciesForPlugin(plugin.Id);
|
||||
@@ -201,7 +203,9 @@ public sealed class PluginManager : IPluginManager
|
||||
await executor.ExecuteAllTasks();
|
||||
|
||||
LocalPlugin localPlugin = LocalPlugin.FromOnlineInfo(plugin, dependencies, downloadLocation);
|
||||
await AppendPluginToDatabase(localPlugin);
|
||||
var result = await AppendPluginToDatabase(localPlugin);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task SetEnabledStatus(string pluginName, bool status)
|
||||
@@ -219,12 +223,19 @@ public sealed class PluginManager : IPluginManager
|
||||
|
||||
}
|
||||
|
||||
public async Task<bool> UninstallPluginByName(string pluginName)
|
||||
public async Task<IResponse<bool>> UninstallPluginByName(string pluginName)
|
||||
{
|
||||
var localPlugin = await GetLocalPluginByName(pluginName);
|
||||
if (localPlugin == null)
|
||||
var localPluginResponse = await GetLocalPluginByName(pluginName);
|
||||
if (!localPluginResponse.IsSuccess)
|
||||
{
|
||||
return false;
|
||||
return Response.Failure(localPluginResponse.Message);
|
||||
}
|
||||
|
||||
var localPlugin = localPluginResponse.Data;
|
||||
|
||||
if (localPlugin is null)
|
||||
{
|
||||
return Response.Failure($"Plugin {pluginName} not found in the database");
|
||||
}
|
||||
|
||||
File.Delete(localPlugin.FilePath);
|
||||
@@ -237,25 +248,49 @@ public sealed class PluginManager : IPluginManager
|
||||
}
|
||||
}
|
||||
|
||||
await RemovePluginFromDatabase(pluginName);
|
||||
|
||||
_Logger.Log($"Plugin {pluginName} uninstalled successfully", this, LogType.Info);
|
||||
|
||||
return true;
|
||||
var response = await RemovePluginFromDatabase(pluginName);
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<LocalPlugin?> GetLocalPluginByName(string pluginName)
|
||||
public async Task<IResponse<LocalPlugin>> GetLocalPluginByName(string pluginName)
|
||||
{
|
||||
List<LocalPlugin> installedPlugins = await GetInstalledPlugins();
|
||||
var plugin = installedPlugins.Find(p => p.PluginName == pluginName);
|
||||
|
||||
if (plugin == null)
|
||||
if (plugin is null)
|
||||
{
|
||||
_Logger.Log($"Plugin {pluginName} not found in the database", this, LogType.Warning);
|
||||
return null;
|
||||
return Response<LocalPlugin>.Failure($"Plugin {pluginName} not found in the database");
|
||||
}
|
||||
|
||||
return plugin;
|
||||
return Response<LocalPlugin>.Success(plugin);
|
||||
}
|
||||
|
||||
private async Task<IResponse<bool>> IsNewVersion(string currentVersion, string newVersion)
|
||||
{
|
||||
// currentVersion = "1.0.0"
|
||||
// newVersion = "1.0.1"
|
||||
|
||||
var currentVersionParts = currentVersion.Split('.').Select(int.Parse).ToArray();
|
||||
var newVersionParts = newVersion.Split('.').Select(int.Parse).ToArray();
|
||||
|
||||
if (currentVersionParts.Length != 3 || newVersionParts.Length != 3)
|
||||
{
|
||||
return Response.Failure("Invalid version format");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (newVersionParts[i] > currentVersionParts[i])
|
||||
{
|
||||
return Response.Success();
|
||||
}
|
||||
else if (newVersionParts[i] < currentVersionParts[i])
|
||||
{
|
||||
return Response.Failure("Current version is newer");
|
||||
}
|
||||
}
|
||||
|
||||
return Response.Failure("Versions are the same");
|
||||
}
|
||||
|
||||
private async Task<bool> CreateEmptyPluginDatabase()
|
||||
|
||||
8
DiscordBotCore.Utilities/Responses/IResponse.cs
Normal file
8
DiscordBotCore.Utilities/Responses/IResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DiscordBotCore.Utilities.Responses;
|
||||
|
||||
public interface IResponse<out T>
|
||||
{
|
||||
public bool IsSuccess { get; }
|
||||
public string Message { get; }
|
||||
public T? Data { get; }
|
||||
}
|
||||
45
DiscordBotCore.Utilities/Responses/Response.cs
Normal file
45
DiscordBotCore.Utilities/Responses/Response.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace DiscordBotCore.Utilities.Responses;
|
||||
|
||||
public class Response : IResponse<bool>
|
||||
{
|
||||
public bool IsSuccess => Data;
|
||||
public string Message { get; }
|
||||
public bool Data { get; }
|
||||
|
||||
private Response(bool result)
|
||||
{
|
||||
Data = result;
|
||||
Message = string.Empty;
|
||||
}
|
||||
|
||||
private Response(string message)
|
||||
{
|
||||
Data = false;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static Response Success() => new Response(true);
|
||||
public static Response Failure(string message) => new Response(message);
|
||||
}
|
||||
|
||||
public class Response<T> : IResponse<T> where T : class
|
||||
{
|
||||
public bool IsSuccess => Data is not null;
|
||||
public string Message { get; }
|
||||
public T? Data { get; }
|
||||
|
||||
private Response(T data)
|
||||
{
|
||||
Data = data;
|
||||
Message = string.Empty;
|
||||
}
|
||||
|
||||
private Response(string message)
|
||||
{
|
||||
Data = null;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static Response<T> Success(T data) => new Response<T>(data);
|
||||
public static Response<T> Failure(string message) => new Response<T>(message);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using WebUI.Components.Notification
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<NotificationDisplay />
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu/>
|
||||
|
||||
55
WebUI/Components/Notification/NotificationDisplay.razor
Normal file
55
WebUI/Components/Notification/NotificationDisplay.razor
Normal file
@@ -0,0 +1,55 @@
|
||||
@using WebUI.Models
|
||||
@using WebUI.Services
|
||||
@inject NotificationService NotificationService
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<link rel="stylesheet" href="Components/Notification/NotificationDisplay.css" />
|
||||
|
||||
<div class="notification-container" style="position: fixed; top: 20px; right: 20px; z-index: 1050; display: flex; flex-direction: column; gap: 10px;">
|
||||
@foreach (var notification in _Notifications)
|
||||
{
|
||||
<div class="notification-box @GetCssClass(notification.Type)">
|
||||
<button class="close-button" @onclick="() => DismissNotification(notification)">×</button>
|
||||
<div class="notification-message">@notification.Message</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@code {
|
||||
private readonly List<Notification> _Notifications = new();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
NotificationService.OnNotify += ShowNotification;
|
||||
}
|
||||
|
||||
private void ShowNotification(Notification notification)
|
||||
{
|
||||
_ = InvokeAsync(async () =>
|
||||
{
|
||||
_Notifications.Add(notification);
|
||||
StateHasChanged();
|
||||
|
||||
await Task.Delay(notification.DelayMs);
|
||||
|
||||
_Notifications.Remove(notification);
|
||||
StateHasChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private string GetCssClass(NotificationType type) => type switch
|
||||
{
|
||||
NotificationType.Success => "alert-success",
|
||||
NotificationType.Error=> "alert-danger",
|
||||
NotificationType.Warning => "alert-warning",
|
||||
NotificationType.Info => "alert-info",
|
||||
_ => "alert-secondary"
|
||||
};
|
||||
|
||||
private void DismissNotification(Notification notification)
|
||||
{
|
||||
_Notifications.Remove(notification);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
@page "/"
|
||||
@using DiscordBotCore.Bot
|
||||
@using DiscordBotCore.PluginManagement.Loading
|
||||
@using WebUI.Models
|
||||
@using WebUI.Services
|
||||
@inject IDiscordBotApplication DiscordBotApplication
|
||||
@inject IPluginLoader PluginLoader
|
||||
@inject DiscordBotCore.Logging.ILogger Logger
|
||||
@inject IJSRuntime JS
|
||||
@inject NotificationService NotificationService
|
||||
@rendermode InteractiveServer
|
||||
<h3>Console Log Viewer</h3>
|
||||
|
||||
@@ -78,6 +81,8 @@
|
||||
Logger.Log("Loading plugins", this);
|
||||
await PluginLoader.LoadPlugins();
|
||||
Logger.Log("Plugins loaded", this);
|
||||
NotificationService.Notify("Plugins Loaded !", NotificationType.Success);
|
||||
|
||||
}
|
||||
|
||||
private string GetLogStyle(string line)
|
||||
|
||||
@@ -81,11 +81,11 @@
|
||||
|
||||
Logger.Log($"Deleting plugin {pluginName}", this);
|
||||
|
||||
bool result = await PluginManager.UninstallPluginByName(pluginName);
|
||||
var response = await PluginManager.UninstallPluginByName(pluginName);
|
||||
|
||||
if (!result)
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
Logger.Log($"Failed to delete plugin {pluginName}", this, LogType.Error);
|
||||
Logger.Log(response.Message, this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,14 +100,14 @@
|
||||
private async Task PluginDetailsButtonClick(string pluginName)
|
||||
{
|
||||
Logger.Log($"Getting plugin details for {pluginName}", this);
|
||||
var pluginDetails = await PluginManager.GetPluginDataByName(pluginName);
|
||||
if (pluginDetails == null)
|
||||
var response = await PluginManager.GetPluginDataByName(pluginName);
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
Logger.Log($"Failed to get details for plugin {pluginName}", this, LogType.Error);
|
||||
Logger.Log(response.Message, this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedPluginDetails = pluginDetails;
|
||||
_selectedPluginDetails = response.Data;
|
||||
_showPluginDetailsModal = true;
|
||||
|
||||
Logger.Log($"Plugin details for {pluginName} retrieved", this);
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
@using DiscordBotCore.Logging
|
||||
@using DiscordBotCore.PluginManagement
|
||||
|
||||
@using WebUI.Models
|
||||
@using WebUI.Services
|
||||
|
||||
@inject NotificationService NotificationService
|
||||
|
||||
|
||||
<h3>Available Plugins</h3>
|
||||
@if (_onlinePlugins.Any())
|
||||
@@ -113,10 +118,18 @@ else
|
||||
|
||||
private async Task InstallPlugin(int pluginId)
|
||||
{
|
||||
var pluginData = await PluginManager.GetPluginDataById(pluginId);
|
||||
if (pluginData == null)
|
||||
var response = await PluginManager.GetPluginDataById(pluginId);
|
||||
if (!response.IsSuccess)
|
||||
{
|
||||
Logger.Log($"Plugin data not found for ID: {pluginId}", this);
|
||||
Logger.Log(response.Message, this);
|
||||
return;
|
||||
}
|
||||
|
||||
var pluginData = response.Data;
|
||||
|
||||
if (pluginData is null)
|
||||
{
|
||||
Logger.Log("Plugin data is null.", this, LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -132,6 +145,9 @@ else
|
||||
await PluginManager.InstallPlugin(pluginData, progress);
|
||||
|
||||
Logger.Log($"Plugin {pluginData.Name} installed successfully.", this);
|
||||
|
||||
NotificationService.Notify($"Plugin {pluginData.Name} installed successfully!", NotificationType.Success);
|
||||
|
||||
CloseInstallPercentageModal();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
9
WebUI/Models/Notification.cs
Normal file
9
WebUI/Models/Notification.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace WebUI.Models;
|
||||
|
||||
public class Notification
|
||||
{
|
||||
public string Message { get; set; }
|
||||
public NotificationType Type { get; set; }
|
||||
public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||
public int DelayMs { get; set; } = 5000;
|
||||
}
|
||||
9
WebUI/Models/NotificationType.cs
Normal file
9
WebUI/Models/NotificationType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace WebUI.Models;
|
||||
|
||||
public enum NotificationType
|
||||
{
|
||||
Success,
|
||||
Error,
|
||||
Warning,
|
||||
Info
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using DiscordBotCore.WebApplication;
|
||||
using WebUI.Components;
|
||||
using WebUI.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -7,6 +8,8 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
builder.AddDiscordBotComponents();
|
||||
builder.Services.AddSingleton<NotificationService>();
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
12
WebUI/Services/NotificationService.cs
Normal file
12
WebUI/Services/NotificationService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using WebUI.Models;
|
||||
namespace WebUI.Services;
|
||||
|
||||
public class NotificationService
|
||||
{
|
||||
public event Action<Notification> OnNotify;
|
||||
|
||||
public void Notify(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
OnNotify?.Invoke(new Notification { Message = message, Type = type });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
.notification-box {
|
||||
position: relative;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
min-width: 250px;
|
||||
max-width: 400px;
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
font-size: 0.95rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.8rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background-color: #17a2b8;
|
||||
}
|
||||
Reference in New Issue
Block a user