mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Allow disabling notifications per user and disabling specific notifications per user (#1991)
* Allow disabling notifications per user and disabling specific notifications per use closes #1974 * Add disable notifs for all users * fix term generator for notifications * sow checkboxes instead of multiselect when js is enabled * remove js dependency * fix notif conditions
This commit is contained in:
parent
933e0c30bf
commit
4d0b402e8b
20 changed files with 315 additions and 64 deletions
|
@ -2,10 +2,17 @@ using System;
|
|||
|
||||
namespace BTCPayServer.Contracts
|
||||
{
|
||||
public abstract class BaseNotification
|
||||
{
|
||||
public abstract string Identifier { get; }
|
||||
public abstract string NotificationType { get; }
|
||||
}
|
||||
|
||||
public interface INotificationHandler
|
||||
{
|
||||
string NotificationType { get; }
|
||||
Type NotificationBlobType { get; }
|
||||
public (string identifier, string name)[] Meta { get; }
|
||||
void FillViewModel(object notification, NotificationViewModel vm);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,5 +28,6 @@ namespace BTCPayServer.Data
|
|||
public List<U2FDevice> U2FDevices { get; set; }
|
||||
public List<APIKeyData> APIKeys { get; set; }
|
||||
public DateTimeOffset? Created { get; set; }
|
||||
public string DisabledNotifications { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20201015151438_AddDisabledNotificationsToUser")]
|
||||
public partial class AddDisabledNotificationsToUser : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DisabledNotifications",
|
||||
table: "AspNetUsers",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DisabledNotifications",
|
||||
table: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -111,6 +111,9 @@ namespace BTCPayServer.Migrations
|
|||
b.Property<DateTimeOffset?>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DisabledNotifications")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(256);
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
@inject LinkGenerator linkGenerator
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject CssThemeManager CssThemeManager
|
||||
@using BTCPayServer.HostedServices
|
||||
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
|
||||
|
||||
@if (Model.UnseenCount > 0)
|
||||
|
@ -32,36 +35,47 @@ else
|
|||
</a>
|
||||
</li>
|
||||
}
|
||||
<script type="text/javascript">
|
||||
@{
|
||||
var disabled = CssThemeManager.Policies.DisableInstantNotifications;
|
||||
if (!disabled)
|
||||
{
|
||||
var user = await UserManager.GetUserAsync(User);
|
||||
disabled = user.DisabledNotifications == "all";
|
||||
}
|
||||
}
|
||||
@if (!disabled)
|
||||
{
|
||||
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
<script type="text/javascript">
|
||||
|
||||
var supportsWebSockets = 'WebSocket' in window && window.WebSocket.CLOSING === 2;
|
||||
|
||||
if (supportsWebSockets) {
|
||||
|
||||
var loc = window.location, ws_uri;
|
||||
if (loc.protocol === "https:") {
|
||||
ws_uri = "wss:";
|
||||
} else {
|
||||
ws_uri = "ws:";
|
||||
}
|
||||
ws_uri += "//" + loc.host;
|
||||
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
|
||||
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
|
||||
|
||||
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);
|
||||
var loc = window.location, ws_uri;
|
||||
if (loc.protocol === "https:") {
|
||||
ws_uri = "wss:";
|
||||
} else {
|
||||
ws_uri = "ws:";
|
||||
}
|
||||
ws_uri += "//" + loc.host;
|
||||
ws_uri += "@linkGenerator.GetPathByAction("SubscribeUpdates", "Notifications")";
|
||||
var newDataEndpoint = "@linkGenerator.GetPathByAction("GetNotificationDropdownUI", "Notifications")";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
68
BTCPayServer/Controllers/ManageController.Notifications.cs
Normal file
68
BTCPayServer/Controllers/ManageController.Notifications.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class ManageController
|
||||
{
|
||||
[HttpGet("notifications")]
|
||||
public async Task<IActionResult> NotificationSettings([FromServices] IEnumerable<INotificationHandler> notificationHandlers)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user.DisabledNotifications == "all")
|
||||
{
|
||||
return View(new NotificationSettingsViewModel() {All = true});
|
||||
}
|
||||
var disabledNotifications =
|
||||
user.DisabledNotifications?.Split(';', StringSplitOptions.RemoveEmptyEntries)?.ToList() ??
|
||||
new List<string>();
|
||||
var notifications = notificationHandlers.SelectMany(handler => handler.Meta.Select(tuple =>
|
||||
new SelectListItem(tuple.name, tuple.identifier,
|
||||
disabledNotifications.Contains(tuple.identifier, StringComparer.InvariantCultureIgnoreCase))))
|
||||
.ToList();
|
||||
|
||||
return View(new NotificationSettingsViewModel() {DisabledNotifications = notifications});
|
||||
}
|
||||
|
||||
[HttpPost("notifications")]
|
||||
public async Task<IActionResult> NotificationSettings(NotificationSettingsViewModel vm, string command)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (command == "disable-all")
|
||||
{
|
||||
user.DisabledNotifications = "all";
|
||||
}
|
||||
else if (command == "enable-all")
|
||||
{
|
||||
user.DisabledNotifications = "";
|
||||
}
|
||||
else if (command == "update")
|
||||
{
|
||||
var disabled = vm.DisabledNotifications.Where(item => item.Selected).Select(item => item.Value)
|
||||
.ToArray();
|
||||
user.DisabledNotifications = disabled.Any() is true
|
||||
? string.Join(';', disabled) + ";"
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
await _userManager.UpdateAsync(user);
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Updated successfully.", Severity = StatusMessageModel.StatusSeverity.Success
|
||||
});
|
||||
return RedirectToAction("NotificationSettings");
|
||||
}
|
||||
|
||||
public class NotificationSettingsViewModel
|
||||
{
|
||||
public bool All { get; set; }
|
||||
public List<SelectListItem> DisabledNotifications { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -89,11 +89,11 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
#if DEBUG
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateJunk(int x = 100)
|
||||
public async Task<IActionResult> GenerateJunk(int x = 100, bool admin=true)
|
||||
{
|
||||
for (int i = 0; i < x; i++)
|
||||
{
|
||||
await _notificationSender.SendNotification(new AdminScope(), new JunkNotification());
|
||||
await _notificationSender.SendNotification(admin? (NotificationScope) new AdminScope(): new UserScope(_userManager.GetUserId(User)), new JunkNotification());
|
||||
}
|
||||
|
||||
return RedirectToAction("Index");
|
||||
|
|
|
@ -9,8 +9,9 @@ using Microsoft.AspNetCore.Routing;
|
|||
|
||||
namespace BTCPayServer.Services.Notifications.Blobs
|
||||
{
|
||||
internal class InvoiceEventNotification
|
||||
internal class InvoiceEventNotification:BaseNotification
|
||||
{
|
||||
private const string TYPE = "invoicestate";
|
||||
internal class Handler : NotificationHandler<InvoiceEventNotification>
|
||||
{
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
@ -22,7 +23,16 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
|||
_options = options;
|
||||
}
|
||||
|
||||
public override string NotificationType => "invoicestate";
|
||||
public override string NotificationType => TYPE;
|
||||
|
||||
public override (string identifier, string name)[] Meta
|
||||
{
|
||||
get
|
||||
{
|
||||
return new (string identifier, string name)[] {(TYPE, "All invoice updates"),}
|
||||
.Concat(TextMapping.Select(pair => ($"{TYPE}_{pair.Key}", $"Invoice {pair.Value}"))).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static Dictionary<string, string> TextMapping = new Dictionary<string, string>()
|
||||
{
|
||||
|
@ -65,5 +75,7 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
|||
|
||||
public string InvoiceId { get; set; }
|
||||
public string Event { get; set; }
|
||||
public override string Identifier => $"{TYPE}_{Event}";
|
||||
public override string NotificationType => TYPE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,31 @@
|
|||
#if DEBUG
|
||||
using System.Data;
|
||||
using BTCPayServer.Contracts;
|
||||
|
||||
namespace BTCPayServer.Services.Notifications.Blobs
|
||||
{
|
||||
internal class JunkNotification
|
||||
internal class JunkNotification: BaseNotification
|
||||
{
|
||||
private const string TYPE = "junk";
|
||||
internal class Handler : NotificationHandler<JunkNotification>
|
||||
{
|
||||
public override string NotificationType => "junk";
|
||||
public override string NotificationType => TYPE;
|
||||
public override (string identifier, string name)[] Meta
|
||||
{
|
||||
get
|
||||
{
|
||||
return new (string identifier, string name)[] {(TYPE, "Junk")};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FillViewModel(JunkNotification notification, NotificationViewModel vm)
|
||||
{
|
||||
vm.Body = $"All your junk r belong to us!";
|
||||
}
|
||||
}
|
||||
|
||||
public override string Identifier => NotificationType;
|
||||
public override string NotificationType => TYPE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -3,11 +3,20 @@ using BTCPayServer.Models.NotificationViewModels;
|
|||
|
||||
namespace BTCPayServer.Services.Notifications.Blobs
|
||||
{
|
||||
internal class NewVersionNotification
|
||||
internal class NewVersionNotification:BaseNotification
|
||||
{
|
||||
private const string TYPE = "newversion";
|
||||
internal class Handler : NotificationHandler<NewVersionNotification>
|
||||
{
|
||||
public override string NotificationType => "newversion";
|
||||
public override string NotificationType => TYPE;
|
||||
public override (string identifier, string name)[] Meta
|
||||
{
|
||||
get
|
||||
{
|
||||
return new (string identifier, string name)[] {(TYPE, "New version")};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FillViewModel(NewVersionNotification notification, NotificationViewModel vm)
|
||||
{
|
||||
vm.Body = $"New version {notification.Version} released!";
|
||||
|
@ -23,5 +32,7 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
|||
Version = version;
|
||||
}
|
||||
public string Version { get; set; }
|
||||
public override string Identifier => TYPE;
|
||||
public override string NotificationType => TYPE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ using Microsoft.AspNetCore.Routing;
|
|||
|
||||
namespace BTCPayServer.Services.Notifications.Blobs
|
||||
{
|
||||
public class PayoutNotification
|
||||
public class PayoutNotification : BaseNotification
|
||||
{
|
||||
private const string TYPE = "payout";
|
||||
|
||||
internal class Handler : NotificationHandler<PayoutNotification>
|
||||
{
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
@ -18,18 +20,30 @@ namespace BTCPayServer.Services.Notifications.Blobs
|
|||
_linkGenerator = linkGenerator;
|
||||
_options = options;
|
||||
}
|
||||
public override string NotificationType => "payout";
|
||||
|
||||
public override string NotificationType => TYPE;
|
||||
public override (string identifier, string name)[] Meta
|
||||
{
|
||||
get
|
||||
{
|
||||
return new (string identifier, string name)[] {(TYPE, "Payouts")};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FillViewModel(PayoutNotification notification, NotificationViewModel vm)
|
||||
{
|
||||
vm.Body = "A new payout is awaiting for approval";
|
||||
vm.ActionLink = _linkGenerator.GetPathByAction(nameof(WalletsController.Payouts),
|
||||
"Wallets",
|
||||
new { walletId = new WalletId(notification.StoreId, notification.PaymentMethod) }, _options.RootPath);
|
||||
new {walletId = new WalletId(notification.StoreId, notification.PaymentMethod)}, _options.RootPath);
|
||||
}
|
||||
}
|
||||
|
||||
public string PayoutId { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public string PaymentMethod { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public override string Identifier => TYPE;
|
||||
public override string NotificationType => TYPE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Models.NotificationViewModels;
|
||||
|
||||
namespace BTCPayServer.Services.Notifications
|
||||
{
|
||||
|
@ -9,6 +8,8 @@ namespace BTCPayServer.Services.Notifications
|
|||
{
|
||||
public abstract string NotificationType { get; }
|
||||
Type INotificationHandler.NotificationBlobType => typeof(TNotification);
|
||||
public abstract (string identifier, string name)[] Meta { get; }
|
||||
|
||||
void INotificationHandler.FillViewModel(object notification, NotificationViewModel vm)
|
||||
{
|
||||
FillViewModel((TNotification)notification, vm);
|
||||
|
|
|
@ -21,7 +21,6 @@ namespace BTCPayServer.Services.Notifications
|
|||
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, EventAggregator eventAggregator)
|
||||
|
@ -31,7 +30,6 @@ namespace BTCPayServer.Services.Notifications
|
|||
_memoryCache = memoryCache;
|
||||
_eventAggregator = eventAggregator;
|
||||
_handlersByNotificationType = handlers.ToDictionary(h => h.NotificationType);
|
||||
_handlersByBlobType = handlers.ToDictionary(h => h.NotificationBlobType);
|
||||
}
|
||||
|
||||
private const int _cacheExpiryMs = 5000;
|
||||
|
@ -118,12 +116,5 @@ namespace BTCPayServer.Services.Notifications
|
|||
return h;
|
||||
throw new InvalidOperationException($"No INotificationHandler found for {notificationId}");
|
||||
}
|
||||
|
||||
public INotificationHandler GetHandler(Type blobType)
|
||||
{
|
||||
if (_handlersByBlobType.TryGetValue(blobType, out var h))
|
||||
return h;
|
||||
throw new InvalidOperationException($"No INotificationHandler found for {blobType.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,16 @@ namespace BTCPayServer.Services.Notifications
|
|||
}
|
||||
public string StoreId { get; }
|
||||
}
|
||||
public class UserScope : NotificationScope
|
||||
{
|
||||
public UserScope(string userId)
|
||||
{
|
||||
if (userId == null)
|
||||
throw new ArgumentNullException(nameof(userId));
|
||||
UserId = userId;
|
||||
}
|
||||
public string UserId { get; }
|
||||
}
|
||||
|
||||
public interface NotificationScope
|
||||
{
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Contracts;
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Services.Notifications
|
||||
|
@ -24,13 +27,13 @@ namespace BTCPayServer.Services.Notifications
|
|||
_notificationManager = notificationManager;
|
||||
}
|
||||
|
||||
public async Task SendNotification(NotificationScope scope, object notification)
|
||||
public async Task SendNotification(NotificationScope scope, BaseNotification notification)
|
||||
{
|
||||
if (scope == null)
|
||||
throw new ArgumentNullException(nameof(scope));
|
||||
if (notification == null)
|
||||
throw new ArgumentNullException(nameof(notification));
|
||||
var users = await GetUsers(scope);
|
||||
var users = await GetUsers(scope, notification.Identifier);
|
||||
using (var db = _contextFactory.CreateContext())
|
||||
{
|
||||
foreach (var uid in users)
|
||||
|
@ -41,7 +44,7 @@ namespace BTCPayServer.Services.Notifications
|
|||
Id = Guid.NewGuid().ToString(),
|
||||
Created = DateTimeOffset.UtcNow,
|
||||
ApplicationUserId = uid,
|
||||
NotificationType = _notificationManager.GetHandler(notification.GetType()).NotificationType,
|
||||
NotificationType = notification.NotificationType,
|
||||
Blob = ZipUtils.Zip(obj),
|
||||
Seen = false
|
||||
};
|
||||
|
@ -55,22 +58,47 @@ namespace BTCPayServer.Services.Notifications
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<string[]> GetUsers(NotificationScope scope)
|
||||
private async Task<string[]> GetUsers(NotificationScope scope, string notificationIdentifier)
|
||||
{
|
||||
if (scope is AdminScope)
|
||||
await using var ctx = _contextFactory.CreateContext();
|
||||
|
||||
var split = notificationIdentifier.Split('_', StringSplitOptions.None);
|
||||
var terms = new List<string>();
|
||||
foreach (var t in split)
|
||||
{
|
||||
var admins = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||
return admins.Select(a => a.Id).ToArray();
|
||||
terms.Add(terms.Any() ? $"{terms.Last().TrimEnd(';')}_{t};" : $"{t};");
|
||||
}
|
||||
if (scope is StoreScope s)
|
||||
IQueryable<ApplicationUser> query;
|
||||
switch (scope)
|
||||
{
|
||||
using var ctx = _contextFactory.CreateContext();
|
||||
return ctx.UserStore
|
||||
.Where(u => u.StoreDataId == s.StoreId)
|
||||
.Select(u => u.ApplicationUserId)
|
||||
.ToArray();
|
||||
case AdminScope _:
|
||||
{
|
||||
query = _userManager.GetUsersInRoleAsync(Roles.ServerAdmin).Result.AsQueryable();
|
||||
|
||||
break;
|
||||
}
|
||||
case StoreScope s:
|
||||
query = ctx.UserStore
|
||||
.Include(store => store.ApplicationUser)
|
||||
.Where(u => u.StoreDataId == s.StoreId)
|
||||
.Select(u => u.ApplicationUser);
|
||||
break;
|
||||
case UserScope userScope:
|
||||
query = ctx.Users
|
||||
.Where(user => user.Id == userScope.UserId);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Notification scope not supported");
|
||||
|
||||
|
||||
}
|
||||
throw new NotSupportedException("Notification scope not supported");
|
||||
query = query.Where(store => store.DisabledNotifications != "all");
|
||||
foreach (string term in terms)
|
||||
{
|
||||
query = query.Where(user => user.DisabledNotifications == null || !user.DisabledNotifications.Contains(term));
|
||||
}
|
||||
|
||||
return query.Select(user => user.Id).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,9 @@ namespace BTCPayServer.Services
|
|||
[Display(Name = "Allow non-admins to import their hot wallets to the node wallet")]
|
||||
public bool AllowHotWalletRPCImportForAll { get; set; }
|
||||
[Display(Name = "Check releases on GitHub and alert when new BTCPayServer version is available")]
|
||||
public bool CheckForNewVersions { get; set; }
|
||||
public bool CheckForNewVersions { get; set; }
|
||||
[Display(Name = "Disable notifications automatically showing (no websockets)")]
|
||||
public bool DisableInstantNotifications { get; set; }
|
||||
|
||||
[Display(Name = "Display app on website root")]
|
||||
public string RootAppId { get; set; }
|
||||
|
|
|
@ -2,6 +2,6 @@ namespace BTCPayServer.Views.Manage
|
|||
{
|
||||
public enum ManageNavPages
|
||||
{
|
||||
Index, ChangePassword, TwoFactorAuthentication, U2F, APIKeys
|
||||
Index, ChangePassword, TwoFactorAuthentication, U2F, APIKeys, Notifications
|
||||
}
|
||||
}
|
||||
|
|
45
BTCPayServer/Views/Manage/NotificationSettings.cshtml
Normal file
45
BTCPayServer/Views/Manage/NotificationSettings.cshtml
Normal file
|
@ -0,0 +1,45 @@
|
|||
@using BTCPayServer.Contracts
|
||||
@model BTCPayServer.Controllers.ManageController.NotificationSettingsViewModel
|
||||
@inject IEnumerable<INotificationHandler> NotificationHandlers
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ManageNavPages.Notifications, "Notification preferences");
|
||||
}
|
||||
|
||||
<partial name="_StatusMessage"/>
|
||||
<form method="post" asp-action="NotificationSettings">
|
||||
@if (Model.All)
|
||||
{
|
||||
<div>
|
||||
All notifications are disabled.
|
||||
<button type="submit" class="btn btn-primary" name="command" value="enable-all">Enable</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
|
||||
<label> Do not receive notifications for</label>
|
||||
|
||||
<div class="card ">
|
||||
|
||||
<ul class="list-group list-group-flush">
|
||||
@for (var index = 0; index < Model.DisabledNotifications.Count; index++)
|
||||
{
|
||||
var item = Model.DisabledNotifications[index];
|
||||
<li class="list-group-item">
|
||||
<input type="hidden" asp-for="DisabledNotifications[index].Value"/>
|
||||
<input type="checkbox" asp-for="DisabledNotifications[index].Selected" class="form-check-inline"/>
|
||||
<label class="mb-0 cursor-pointer" asp-for="DisabledNotifications[index].Selected">
|
||||
@item.Text
|
||||
</label>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-secondary" name="command" value="disable-all">Disable all</button>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="update">Save</button>
|
||||
</div>
|
||||
}
|
||||
</form>
|
|
@ -6,5 +6,6 @@
|
|||
<a id="@ManageNavPages.TwoFactorAuthentication.ToString()" class="nav-link @ViewData.IsActivePage(ManageNavPages.TwoFactorAuthentication)" asp-action="TwoFactorAuthentication">Two-factor authentication</a>
|
||||
<a id="@ManageNavPages.U2F.ToString()" class="nav-link @ViewData.IsActivePage(ManageNavPages.U2F)" asp-action="U2FAuthentication">U2F Authentication</a>
|
||||
<a id="@ManageNavPages.APIKeys.ToString()" class="nav-link @ViewData.IsActivePage(ManageNavPages.APIKeys)" asp-action="APIKeys">API Keys</a>
|
||||
<a id="@ManageNavPages.Notifications.ToString()" class="nav-link @ViewData.IsActivePage(ManageNavPages.Notifications)" asp-action="NotificationSettings">Notifications</a>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -42,6 +42,11 @@
|
|||
<label asp-for="AllowHotWalletRPCImportForAll" class="form-check-label"></label>
|
||||
<span asp-validation-for="AllowHotWalletRPCImportForAll" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input asp-for="DisableInstantNotifications" type="checkbox" class="form-check-input"/>
|
||||
<label asp-for="DisableInstantNotifications" class="form-check-label"></label>
|
||||
<span asp-validation-for="DisableInstantNotifications" class="text-danger"></span>
|
||||
</div>
|
||||
@if (ViewBag.UpdateUrlPresent)
|
||||
{
|
||||
<div class="form-check">
|
||||
|
|
Loading…
Add table
Reference in a new issue