From 27c22d5e33a0251292c70e473e1cd837fa026721 Mon Sep 17 00:00:00 2001 From: d11n Date: Thu, 2 Nov 2023 08:12:28 +0100 Subject: [PATCH] Unify list views (#5399) --- BTCPayServer.Tests/GreenfieldAPITests.cs | 2 +- BTCPayServer.Tests/SeleniumTests.cs | 55 ++--- .../Controllers/UIInvoiceController.UI.cs | 96 +++++---- .../Shared/Crowdfund/UpdateCrowdfund.cshtml | 1 - BTCPayServer/Views/Shared/ListRoles.cshtml | 1 - .../PointOfSale/UpdatePointOfSale.cshtml | 1 - BTCPayServer/Views/UIApps/ListApps.cshtml | 2 +- .../ViewCustodianAccount.cshtml | 1 - BTCPayServer/Views/UIForms/FormsList.cshtml | 1 - .../Views/UIInvoice/CreateInvoice.cshtml | 1 - BTCPayServer/Views/UIInvoice/Invoice.cshtml | 1 - .../Views/UIInvoice/ListInvoices.cshtml | 191 ++++++++---------- .../Views/UILNURL/EditLightningAddress.cshtml | 1 - BTCPayServer/Views/UIManage/APIKeys.cshtml | 1 - .../UIManage/TwoFactorAuthentication.cshtml | 1 - BTCPayServer/Views/UIManage/_Nav.cshtml | 1 - .../Views/UINotifications/Index.cshtml | 123 +++++------ .../EditPaymentRequest.cshtml | 1 - .../GetPaymentRequests.cshtml | 101 ++++----- .../UIPullPayment/EditPullPayment.cshtml | 1 - .../Views/UIReports/StoreReports.cshtml | 63 +++--- .../Views/UIServer/DynamicDnsServices.cshtml | 2 +- BTCPayServer/Views/UIServer/ListUsers.cshtml | 1 - BTCPayServer/Views/UIServer/Policies.cshtml | 1 - BTCPayServer/Views/UIServer/_Nav.cshtml | 1 - .../UIStorePullPayments/NewPullPayment.cshtml | 1 - .../Views/UIStorePullPayments/Payouts.cshtml | 98 +++++---- .../UIStorePullPayments/PullPayments.cshtml | 78 +++---- BTCPayServer/Views/UIStores/ListTokens.cshtml | 1 - .../Views/UIStores/StoreEmails.cshtml | 1 - BTCPayServer/Views/UIStores/StoreUsers.cshtml | 2 +- BTCPayServer/Views/UIStores/Webhooks.cshtml | 1 - BTCPayServer/Views/UIStores/_Nav.cshtml | 1 - .../Views/UIWallets/WalletTransactions.cshtml | 75 +++---- BTCPayServer/Views/UIWallets/_Nav.cshtml | 1 - .../UIWallets/_WalletTransactionsList.cshtml | 24 +-- BTCPayServer/wwwroot/main/layout.css | 3 + BTCPayServer/wwwroot/main/site.css | 93 ++++++++- BTCPayServer/wwwroot/main/site.js | 49 ++++- BTCPayServer/wwwroot/main/utils.js | 6 + 40 files changed, 581 insertions(+), 504 deletions(-) diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index 47e8117dd..dff4275d4 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -1457,7 +1457,7 @@ namespace BTCPayServer.Tests [Trait("Integration", "Integration")] public async Task CanUseWebhooks() { - void AssertHook(FakeServer fakeServer, Client.Models.StoreWebhookData hook) + void AssertHook(FakeServer fakeServer, StoreWebhookData hook) { Assert.True(hook.Enabled); Assert.True(hook.AuthorizedEvents.Everything); diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 846ba2dc3..a986f45f0 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -196,7 +196,6 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click(); s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click(); Assert.Equal(4, new SelectElement(s.Driver.FindElement(By.Id("FormId"))).Options.Count); - } [Fact(Timeout = TestTimeout)] @@ -216,8 +215,7 @@ namespace BTCPayServer.Tests s.GoToInvoices(s.StoreId); } // Let's CPFP from the invoices page - s.Driver.SetCheckbox(By.Id("selectAllCheckbox"), true); - s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); + s.Driver.SetCheckbox(By.CssSelector(".mass-action-select-all"), true); s.Driver.FindElement(By.Id("BumpFee")).Click(); s.Driver.FindElement(By.Id("BroadcastTransaction")).Click(); s.FindAlertMessage(); @@ -225,16 +223,14 @@ namespace BTCPayServer.Tests // CPFP again should fail because all invoices got bumped s.GoToInvoices(); - s.Driver.SetCheckbox(By.Id("selectAllCheckbox"), true); - s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); + s.Driver.SetCheckbox(By.CssSelector(".mass-action-select-all"), true); s.Driver.FindElement(By.Id("BumpFee")).Click(); Assert.Contains($"/stores/{s.StoreId}/invoices", s.Driver.Url); Assert.Contains("any UTXO available", s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error).Text); // But we should be able to bump from the wallet's page s.GoToWallet(navPages: WalletsNavPages.Transactions); - s.Driver.SetCheckbox(By.Id("selectAllCheckbox"), true); - s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); + s.Driver.SetCheckbox(By.CssSelector(".mass-action-select-all"), true); s.Driver.FindElement(By.Id("BumpFee")).Click(); s.Driver.FindElement(By.Id("BroadcastTransaction")).Click(); Assert.Contains($"/wallets/{s.WalletId}", s.Driver.Url); @@ -730,9 +726,8 @@ namespace BTCPayServer.Tests Assert.Contains(invoiceId, s.Driver.PageSource); // archive via list - s.Driver.FindElement(By.CssSelector($".selector[value=\"{invoiceId}\"]")).Click(); - s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); - s.Driver.FindElement(By.Id("ActionsDropdownArchive")).Click(); + s.Driver.FindElement(By.CssSelector($".mass-action-select[value=\"{invoiceId}\"]")).Click(); + s.Driver.FindElement(By.Id("ArchiveSelected")).Click(); Assert.Contains("1 invoice archived", s.FindAlertMessage().Text); Assert.DoesNotContain(invoiceId, s.Driver.PageSource); @@ -740,9 +735,8 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id("StatusOptionsToggle")).Click(); s.Driver.FindElement(By.Id("StatusOptionsIncludeArchived")).Click(); Assert.Contains(invoiceId, s.Driver.PageSource); - s.Driver.FindElement(By.CssSelector($".selector[value=\"{invoiceId}\"]")).Click(); - s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); - s.Driver.FindElement(By.Id("ActionsDropdownUnarchive")).Click(); + s.Driver.FindElement(By.CssSelector($".mass-action-select[value=\"{invoiceId}\"]")).Click(); + s.Driver.FindElement(By.Id("UnarchiveSelected")).Click(); Assert.Contains("1 invoice unarchived", s.FindAlertMessage().Text); Assert.Contains(invoiceId, s.Driver.PageSource); @@ -1377,7 +1371,7 @@ namespace BTCPayServer.Tests TestLogs.LogInformation("Let's try to update one of them"); s.Driver.FindElement(By.LinkText("Modify")).Click(); - using FakeServer server = new FakeServer(); + using var server = new FakeServer(); await server.Start(); s.Driver.FindElement(By.Name("PayloadUrl")).Clear(); s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys(server.ServerUri.AbsoluteUri); @@ -1419,7 +1413,7 @@ namespace BTCPayServer.Tests server.Done(); TestLogs.LogInformation("Let's make a failed event"); - s.CreateInvoice(); + var invoiceId = s.CreateInvoice(); request = await server.GetNextRequest(); request.Response.StatusCode = 404; server.Done(); @@ -1444,7 +1438,7 @@ namespace BTCPayServer.Tests CanBrowseContent(s); s.GoToInvoices(); - s.Driver.FindElement(By.LinkText("Details")).Click(); + s.Driver.FindElement(By.LinkText(invoiceId)).Click(); CanBrowseContent(s); var element = s.Driver.FindElement(By.ClassName("redeliver")); element.Click(); @@ -1697,13 +1691,14 @@ namespace BTCPayServer.Tests // no previous page in the wizard, hence no back button Assert.True(s.Driver.ElementDoesNotExist(By.Id("GoBack"))); s.Driver.FindElement(By.Id("CancelWizard")).Click(); - Assert.Equal(settingsUri.ToString(), s.Driver.Url); - - // Transactions list contains export and action, ensure functions are present. + Assert.Equal(settingsUri.ToString(), s.Driver.Url); + + // Transactions list contains export, ensure functions are present. s.Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click(); - s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click(); + + s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click(); s.Driver.FindElement(By.Id("BumpFee")); - + // JSON export s.Driver.FindElement(By.Id("ExportDropdownToggle")).Click(); s.Driver.FindElement(By.Id("ExportJSON")).Click(); @@ -1859,8 +1854,7 @@ namespace BTCPayServer.Tests payouts[0].Click(); Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout"))); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click(); + s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click(); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click(); s.Driver.FindElement(By.Id("SignTransaction")).Click(); @@ -1935,8 +1929,7 @@ namespace BTCPayServer.Tests Assert.Contains(PayoutState.AwaitingApproval.GetStateString(), s.Driver.PageSource); s.GoToStore(s.StoreId, StoreNavPages.Payouts); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click(); + s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click(); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve")).Click(); s.FindAlertMessage(); var tx = await s.Server.ExplorerNode.SendToAddressAsync(address, Money.FromUnit(0.001m, MoneyUnit.BTC)); @@ -1945,8 +1938,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click(); Assert.Contains(PayoutState.AwaitingPayment.GetStateString(), s.Driver.PageSource); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click(); + s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click(); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click(); s.FindAlertMessage(); @@ -2006,8 +1998,7 @@ namespace BTCPayServer.Tests s.GoToStore(newStore.storeId, StoreNavPages.Payouts); s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click(); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-view")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click(); + s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click(); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click(); Assert.Contains(bolt, s.Driver.PageSource); Assert.Contains($"{payoutAmount.ToString()} BTC", s.Driver.PageSource); @@ -2023,8 +2014,7 @@ namespace BTCPayServer.Tests s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-view")).Click(); Assert.Contains(bolt, s.Driver.PageSource); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-selectAllCheckbox")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-actions")).Click(); + s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click(); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingPayment}-mark-paid")).Click(); s.Driver.FindElement(By.Id($"{new PaymentMethodId("BTC", PaymentTypes.LightningLike)}-view")).Click(); @@ -2553,8 +2543,7 @@ namespace BTCPayServer.Tests payouts[0].Click(); s.Driver.FindElement(By.Id("BTC_LightningLike-view")).Click(); Assert.NotEmpty(s.Driver.FindElements(By.ClassName("payout"))); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-selectAllCheckbox")).Click(); - s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-actions")).Click(); + s.Driver.FindElement(By.ClassName("mass-action-select-all")).Click(); s.Driver.FindElement(By.Id($"{PayoutState.AwaitingApproval}-approve-pay")).Click(); Assert.Contains(lnurl, s.Driver.PageSource); diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs index 6dee7840c..b9f57bd52 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs @@ -634,58 +634,56 @@ namespace BTCPayServer.Controllers [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanViewInvoices)] public async Task MassAction(string command, string[] selectedItems, string? storeId = null) { - if (selectedItems != null) + IActionResult NotSupported(string err) { - switch (command) - { - case "archive": - await _InvoiceRepository.MassArchive(selectedItems); - TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} archived."; - break; + TempData[WellKnownTempData.ErrorMessage] = err; + return RedirectToAction(nameof(ListInvoices), new { storeId }); + } + if (selectedItems.Length == 0) + return NotSupported("No invoice has been selected"); - case "unarchive": - await _InvoiceRepository.MassArchive(selectedItems, false); - TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} unarchived."; - break; - case "cpfp": - if (selectedItems.Length == 0) - return NotSupported("No invoice has been selected"); - var network = _NetworkProvider.DefaultNetwork; - var explorer = _ExplorerClients.GetExplorerClient(network); - IActionResult NotSupported(string err) - { - TempData[WellKnownTempData.ErrorMessage] = err; - return RedirectToAction(nameof(ListInvoices), new { storeId }); - } - if (explorer is null) - return NotSupported("This feature is only available to BTC wallets"); - if (!GetCurrentStore().HasPermission(GetUserId(), Policies.CanModifyStoreSettings)) - return Forbid(); + switch (command) + { + case "archive": + await _InvoiceRepository.MassArchive(selectedItems); + TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} archived."; + break; - var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_NetworkProvider, network.CryptoCode))?.AccountDerivation; - if (derivationScheme is null) - return NotSupported("This feature is only available to BTC wallets"); - var bumpableAddresses = (await GetAddresses(selectedItems)) - .Where(p => p.GetPaymentMethodId().IsBTCOnChain) - .Select(p => p.GetAddress()).ToHashSet(); - var utxos = await explorer.GetUTXOsAsync(derivationScheme); - var bumpableUTXOs = utxos.GetUnspentUTXOs().Where(u => u.Confirmations == 0 && bumpableAddresses.Contains(u.ScriptPubKey.Hash.ToString())).ToArray(); - var parameters = new MultiValueDictionary(); - foreach (var utxo in bumpableUTXOs) - { - parameters.Add($"outpoints[]", utxo.Outpoint.ToString()); - } - return View("PostRedirect", new PostRedirectViewModel - { - AspController = "UIWallets", - AspAction = nameof(UIWalletsController.WalletCPFP), - RouteParameters = { - { "walletId", new WalletId(storeId, network.CryptoCode).ToString() }, - { "returnUrl", Url.Action(nameof(ListInvoices), new { storeId }) } - }, - FormParameters = parameters, - }); - } + case "unarchive": + await _InvoiceRepository.MassArchive(selectedItems, false); + TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} unarchived."; + break; + case "cpfp": + var network = _NetworkProvider.DefaultNetwork; + var explorer = _ExplorerClients.GetExplorerClient(network); + if (explorer is null) + return NotSupported("This feature is only available to BTC wallets"); + if (!GetCurrentStore().HasPermission(GetUserId(), Policies.CanModifyStoreSettings)) + return Forbid(); + + var derivationScheme = (this.GetCurrentStore().GetDerivationSchemeSettings(_NetworkProvider, network.CryptoCode))?.AccountDerivation; + if (derivationScheme is null) + return NotSupported("This feature is only available to BTC wallets"); + var bumpableAddresses = (await GetAddresses(selectedItems)) + .Where(p => p.GetPaymentMethodId().IsBTCOnChain) + .Select(p => p.GetAddress()).ToHashSet(); + var utxos = await explorer.GetUTXOsAsync(derivationScheme); + var bumpableUTXOs = utxos.GetUnspentUTXOs().Where(u => u.Confirmations == 0 && bumpableAddresses.Contains(u.ScriptPubKey.Hash.ToString())).ToArray(); + var parameters = new MultiValueDictionary(); + foreach (var utxo in bumpableUTXOs) + { + parameters.Add($"outpoints[]", utxo.Outpoint.ToString()); + } + return View("PostRedirect", new PostRedirectViewModel + { + AspController = "UIWallets", + AspAction = nameof(UIWalletsController.WalletCPFP), + RouteParameters = { + { "walletId", new WalletId(storeId, network.CryptoCode).ToString() }, + { "returnUrl", Url.Action(nameof(ListInvoices), new { storeId }) } + }, + FormParameters = parameters, + }); } return RedirectToAction(nameof(ListInvoices), new { storeId }); } diff --git a/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml b/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml index 8b5367f10..ab5ee672a 100644 --- a/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml +++ b/BTCPayServer/Views/Shared/Crowdfund/UpdateCrowdfund.cshtml @@ -26,7 +26,6 @@ }
-