mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +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:
parent
8230a408ac
commit
dc5d8a6cb7
9 changed files with 148 additions and 44 deletions
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
|
@ -226,6 +226,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="Views\Authorization\Authorize.cshtml" />
|
||||
<_ContentIncludedByDefault Remove="Views\Components\NotificationsDropdown\Default.cshtml" />
|
||||
</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>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
|
@ -14,6 +15,22 @@ using Microsoft.AspNetCore.Mvc;
|
|||
|
||||
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)]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Route("[controller]/[action]")]
|
||||
|
@ -24,18 +41,63 @@ namespace BTCPayServer.Controllers
|
|||
private readonly NotificationSender _notificationSender;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly NotificationManager _notificationManager;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
|
||||
public NotificationsController(BTCPayServerEnvironment env,
|
||||
ApplicationDbContext db,
|
||||
NotificationSender notificationSender,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
NotificationManager notificationManager)
|
||||
NotificationManager notificationManager,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_env = env;
|
||||
_db = db;
|
||||
_notificationSender = notificationSender;
|
||||
_userManager = userManager;
|
||||
_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]
|
||||
|
|
|
@ -37,7 +37,10 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
|||
NotificationViewModel vm)
|
||||
{
|
||||
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),
|
||||
"Invoice",
|
||||
new {invoiceId = notification.InvoiceId}, _options.RootPath);
|
||||
|
|
|
@ -20,15 +20,17 @@ namespace BTCPayServer.Services.Notifications
|
|||
private readonly ApplicationDbContextFactory _factory;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly Dictionary<string, INotificationHandler> _handlersByNotificationType;
|
||||
private readonly Dictionary<Type, INotificationHandler> _handlersByBlobType;
|
||||
|
||||
public NotificationManager(ApplicationDbContextFactory factory, UserManager<ApplicationUser> userManager,
|
||||
IMemoryCache memoryCache, IEnumerable<INotificationHandler> handlers)
|
||||
IMemoryCache memoryCache, IEnumerable<INotificationHandler> handlers, EventAggregator eventAggregator)
|
||||
{
|
||||
_factory = factory;
|
||||
_userManager = userManager;
|
||||
_memoryCache = memoryCache;
|
||||
_eventAggregator = eventAggregator;
|
||||
_handlersByNotificationType = handlers.ToDictionary(h => h.NotificationType);
|
||||
_handlersByBlobType = handlers.ToDictionary(h => h.NotificationBlobType);
|
||||
}
|
||||
|
@ -52,6 +54,8 @@ namespace BTCPayServer.Services.Notifications
|
|||
public void InvalidateNotificationCache(string userId)
|
||||
{
|
||||
_memoryCache.Remove(GetNotificationsCacheId(userId));
|
||||
|
||||
_eventAggregator.Publish(new UserNotificationsUpdatedEvent() {UserId = userId});
|
||||
}
|
||||
|
||||
private static string GetNotificationsCacheId(string userId)
|
||||
|
|
|
@ -1,28 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Services.Notifications.Blobs;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Notifications
|
||||
{
|
||||
public class UserNotificationsUpdatedEvent
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
public class NotificationSender
|
||||
{
|
||||
private readonly ApplicationDbContextFactory _contextFactory;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
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;
|
||||
_userManager = userManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
_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 notificationModel = await notificationManager.GetSummaryNotifications(User);
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
|
||||
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)
|
||||
{
|
||||
<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>
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -6,7 +6,18 @@
|
|||
var dateString = localDate.toLocaleDateString() + " " + localDate.toLocaleTimeString();
|
||||
$(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
|
||||
$(".flatdtpicker").each(function () {
|
||||
var element = $(this);
|
||||
|
|
Loading…
Add table
Reference in a new issue