mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Refresh UI notifications automatically on update (#1680)
* Refresh UI notifications automatically on update * make notif timeago live * pass cancellation token * Update InvoiceEventData.cs
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|||||||
@@ -226,6 +226,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
||||||
|
<_ContentIncludedByDefault Remove="Views\Components\NotificationsDropdown\Default.cshtml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
<ProjectExtensions><VisualStudio><UserProperties wwwroot_4swagger_4v1_4swagger_1template_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1pull-payments_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1serverinfo_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" wwwroot_4swagger_4v1_4swagger_1template_1stores_1json__JsonSchema="https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json" /></VisualStudio></ProjectExtensions>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Filters;
|
using BTCPayServer.Filters;
|
||||||
@@ -14,6 +15,22 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public class NotificationsDropdown : ViewComponent
|
||||||
|
{
|
||||||
|
private readonly NotificationManager _notificationManager;
|
||||||
|
|
||||||
|
public NotificationsDropdown(NotificationManager notificationManager)
|
||||||
|
{
|
||||||
|
_notificationManager = notificationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IViewComponentResult> InvokeAsync(int noOfEmployee)
|
||||||
|
{
|
||||||
|
return View(await _notificationManager.GetSummaryNotifications(UserClaimsPrincipal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[BitpayAPIConstraint(false)]
|
[BitpayAPIConstraint(false)]
|
||||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||||
[Route("[controller]/[action]")]
|
[Route("[controller]/[action]")]
|
||||||
@@ -24,18 +41,63 @@ namespace BTCPayServer.Controllers
|
|||||||
private readonly NotificationSender _notificationSender;
|
private readonly NotificationSender _notificationSender;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly NotificationManager _notificationManager;
|
private readonly NotificationManager _notificationManager;
|
||||||
|
private readonly EventAggregator _eventAggregator;
|
||||||
|
|
||||||
public NotificationsController(BTCPayServerEnvironment env,
|
public NotificationsController(BTCPayServerEnvironment env,
|
||||||
ApplicationDbContext db,
|
ApplicationDbContext db,
|
||||||
NotificationSender notificationSender,
|
NotificationSender notificationSender,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
NotificationManager notificationManager)
|
NotificationManager notificationManager,
|
||||||
|
EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_env = env;
|
_env = env;
|
||||||
_db = db;
|
_db = db;
|
||||||
_notificationSender = notificationSender;
|
_notificationSender = notificationSender;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_notificationManager = notificationManager;
|
_notificationManager = notificationManager;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> GetNotificationDropdownUI()
|
||||||
|
{
|
||||||
|
return ViewComponent("NotificationsDropdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> SubscribeUpdates(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||||
|
{
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||||
|
var userId = _userManager.GetUserId(User);
|
||||||
|
var websocketHelper = new WebSocketHelper(websocket);
|
||||||
|
IEventAggregatorSubscription subscription = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
subscription = _eventAggregator.Subscribe<UserNotificationsUpdatedEvent>(async evt =>
|
||||||
|
{
|
||||||
|
if (evt.UserId == userId)
|
||||||
|
{
|
||||||
|
await websocketHelper.Send("update");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
subscription?.Dispose();
|
||||||
|
await websocketHelper.DisposeAsync(CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EmptyResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
|||||||
NotificationViewModel vm)
|
NotificationViewModel vm)
|
||||||
{
|
{
|
||||||
var baseStr = $"Invoice {notification.InvoiceId.Substring(0, 5)}..";
|
var baseStr = $"Invoice {notification.InvoiceId.Substring(0, 5)}..";
|
||||||
vm.Body = $"{baseStr} {TextMapping[notification.Event]}";
|
if (TextMapping.ContainsKey(notification.Event))
|
||||||
|
{
|
||||||
|
vm.Body = $"{baseStr} {TextMapping[notification.Event]}";
|
||||||
|
}
|
||||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(InvoiceController.Invoice),
|
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(InvoiceController.Invoice),
|
||||||
"Invoice",
|
"Invoice",
|
||||||
new {invoiceId = notification.InvoiceId}, _options.RootPath);
|
new {invoiceId = notification.InvoiceId}, _options.RootPath);
|
||||||
|
|||||||
@@ -20,15 +20,17 @@ namespace BTCPayServer.Services.Notifications
|
|||||||
private readonly ApplicationDbContextFactory _factory;
|
private readonly ApplicationDbContextFactory _factory;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
private readonly EventAggregator _eventAggregator;
|
||||||
private readonly Dictionary<string, INotificationHandler> _handlersByNotificationType;
|
private readonly Dictionary<string, INotificationHandler> _handlersByNotificationType;
|
||||||
private readonly Dictionary<Type, INotificationHandler> _handlersByBlobType;
|
private readonly Dictionary<Type, INotificationHandler> _handlersByBlobType;
|
||||||
|
|
||||||
public NotificationManager(ApplicationDbContextFactory factory, UserManager<ApplicationUser> userManager,
|
public NotificationManager(ApplicationDbContextFactory factory, UserManager<ApplicationUser> userManager,
|
||||||
IMemoryCache memoryCache, IEnumerable<INotificationHandler> handlers)
|
IMemoryCache memoryCache, IEnumerable<INotificationHandler> handlers, EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_factory = factory;
|
_factory = factory;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_memoryCache = memoryCache;
|
_memoryCache = memoryCache;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
_handlersByNotificationType = handlers.ToDictionary(h => h.NotificationType);
|
_handlersByNotificationType = handlers.ToDictionary(h => h.NotificationType);
|
||||||
_handlersByBlobType = handlers.ToDictionary(h => h.NotificationBlobType);
|
_handlersByBlobType = handlers.ToDictionary(h => h.NotificationBlobType);
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,8 @@ namespace BTCPayServer.Services.Notifications
|
|||||||
public void InvalidateNotificationCache(string userId)
|
public void InvalidateNotificationCache(string userId)
|
||||||
{
|
{
|
||||||
_memoryCache.Remove(GetNotificationsCacheId(userId));
|
_memoryCache.Remove(GetNotificationsCacheId(userId));
|
||||||
|
|
||||||
|
_eventAggregator.Publish(new UserNotificationsUpdatedEvent() {UserId = userId});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetNotificationsCacheId(string userId)
|
private static string GetNotificationsCacheId(string userId)
|
||||||
|
|||||||
@@ -1,28 +1,26 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Events;
|
|
||||||
using BTCPayServer.Services.Notifications.Blobs;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Notifications
|
namespace BTCPayServer.Services.Notifications
|
||||||
{
|
{
|
||||||
|
public class UserNotificationsUpdatedEvent
|
||||||
|
{
|
||||||
|
public string UserId { get; set; }
|
||||||
|
}
|
||||||
public class NotificationSender
|
public class NotificationSender
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContextFactory _contextFactory;
|
private readonly ApplicationDbContextFactory _contextFactory;
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly EventAggregator _eventAggregator;
|
|
||||||
private readonly NotificationManager _notificationManager;
|
private readonly NotificationManager _notificationManager;
|
||||||
|
|
||||||
public NotificationSender(ApplicationDbContextFactory contextFactory, UserManager<ApplicationUser> userManager, EventAggregator eventAggregator, NotificationManager notificationManager)
|
public NotificationSender(ApplicationDbContextFactory contextFactory, UserManager<ApplicationUser> userManager,NotificationManager notificationManager)
|
||||||
{
|
{
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_eventAggregator = eventAggregator;
|
|
||||||
_notificationManager = notificationManager;
|
_notificationManager = notificationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
@model BTCPayServer.Services.Notifications.NotificationSummaryViewModel
|
||||||
|
|
||||||
|
@if (Model.UnseenCount > 0)
|
||||||
|
{
|
||||||
|
<li class="nav-item dropdown" id="notifications-nav-item">
|
||||||
|
<a class="nav-link js-scroll-trigger" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
|
||||||
|
<i class="fa fa-bell"></i>
|
||||||
|
</a>
|
||||||
|
<span class="alerts-badge badge badge-pill badge-danger">@Model.UnseenCount</span>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right text-center notification-items" aria-labelledby="navbarDropdown">
|
||||||
|
@foreach (var notif in Model.Last5)
|
||||||
|
{
|
||||||
|
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@notif.Id" class="dropdown-item border-bottom">
|
||||||
|
<div class="text-left" style="width: 200px; white-space:normal;">
|
||||||
|
@notif.Body
|
||||||
|
</div>
|
||||||
|
<div class="text-left">
|
||||||
|
<small class="text-muted" data-timeago-unixms="@notif.Created.ToUnixTimeMilliseconds()">@notif.Created.ToTimeAgo()</small>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<a class="dropdown-item text-info" asp-controller="Notifications" asp-action="Index">See All</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<li class="nav-item" id="notifications-nav-item">
|
||||||
|
<a asp-controller="Notifications" asp-action="Index" title="Notifications" class="nav-link js-scroll-trigger" id="Notifications">
|
||||||
|
<i class="fa fa-bell"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
@@ -1,35 +1,27 @@
|
|||||||
@inject BTCPayServer.Services.Notifications.NotificationManager notificationManager
|
<div></div>
|
||||||
|
@await Component.InvokeAsync("NotificationsDropdown")
|
||||||
|
<script>
|
||||||
|
|
||||||
@{
|
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||||
var notificationModel = await notificationManager.GetSummaryNotifications(User);
|
|
||||||
|
if (supportsWebSockets) {
|
||||||
|
var ws_uri = "@Url.Action("SubscribeUpdates", "Notifications", new {}, Context.Request.Scheme)".replace("http", "ws");
|
||||||
|
var newDataEndpoint = "@Url.Action("GetNotificationDropdownUI", "Notifications", new {}, Context.Request.Scheme)";
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket = new WebSocket(ws_uri);
|
||||||
|
socket.onmessage = function (e) {
|
||||||
|
$.get(newDataEndpoint, function(data){
|
||||||
|
$("#notifications-nav-item").replaceWith($(data));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
socket.onerror = function (e) {
|
||||||
|
console.error("Error while connecting to websocket for notifications (callback)", e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.error("Error while connecting to websocket for notifications", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (notificationModel.UnseenCount > 0)
|
</script>
|
||||||
{
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link js-scroll-trigger" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
|
|
||||||
<i class="fa fa-bell"></i>
|
|
||||||
</a>
|
|
||||||
<span class="alerts-badge badge badge-pill badge-danger">@notificationModel.UnseenCount</span>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right text-center" aria-labelledby="navbarDropdown">
|
|
||||||
@foreach (var notif in notificationModel.Last5)
|
|
||||||
{
|
|
||||||
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@notif.Id" class="dropdown-item border-bottom">
|
|
||||||
<div class="text-left" style="width: 200px; white-space:normal;">
|
|
||||||
@notif.Body
|
|
||||||
</div>
|
|
||||||
<div class="text-left">
|
|
||||||
<small class="text-muted">@notif.Created.ToTimeAgo()</small>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
<a class="dropdown-item text-info" asp-controller="Notifications" asp-action="Index">See All</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<li class="nav-item">
|
|
||||||
<a asp-controller="Notifications" asp-action="Index" title="Notifications" class="nav-link js-scroll-trigger" id="Notifications"><i class="fa fa-bell"></i></a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,17 @@
|
|||||||
$(this).text(dateString);
|
$(this).text(dateString);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function updateTimeAgo(){
|
||||||
|
var timeagoElements = $("[data-timeago-unixms]");
|
||||||
|
timeagoElements.each(function () {
|
||||||
|
var elem = $(this);
|
||||||
|
elem.text(moment(elem.data("timeago-unixms")).fromNow());
|
||||||
|
});
|
||||||
|
setTimeout(updateTimeAgo, 1000);
|
||||||
|
}
|
||||||
|
updateTimeAgo();
|
||||||
|
|
||||||
// intializing date time pickers throughts website
|
// intializing date time pickers throughts website
|
||||||
$(".flatdtpicker").each(function () {
|
$(".flatdtpicker").each(function () {
|
||||||
var element = $(this);
|
var element = $(this);
|
||||||
|
|||||||
Reference in New Issue
Block a user