Refactor access to the ViewModel of the MainLayout (#6970)

This commit is contained in:
Nicolas Dorier
2025-11-01 00:02:11 +09:00
committed by GitHub
parent b8fcb83fd6
commit 54cec83507
149 changed files with 554 additions and 442 deletions

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using BTCPayServer.Abstractions.Models;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@@ -35,16 +36,28 @@ namespace BTCPayServer.Abstractions.Extensions
SetActivePage(viewData, activePage.ToString(), activePage.GetType().ToString(), title, activeId);
}
public static void SetActivePage(this ViewDataDictionary viewData, string activePage, string category, string title = null, string activeId = null)
public static void SetTitle(this ViewDataDictionary viewData, string title) => viewData["Title"] = title;
public static string GetTitle(this ViewDataDictionary viewData) => viewData["Title"]?.ToString();
public static void SetLayoutModel(this ViewDataDictionary viewData, LayoutModel model)
{
// Page Title
viewData["Title"] = title ?? activePage;
viewData["Title"] = model.Title ?? model.MenuItemId;
// Navigation
viewData[ACTIVE_PAGE_KEY] = activePage;
viewData[ACTIVE_ID_KEY] = activeId;
SetActiveCategory(viewData, category);
viewData[ACTIVE_PAGE_KEY] = model.MenuItemId;
viewData[ACTIVE_ID_KEY] = model.SubMenuItemId;
SetActiveCategory(viewData, model.ActiveCategory);
}
public static bool IsCategory(this ViewDataDictionary viewData, string category) =>
viewData.TryGetValue(ACTIVE_CATEGORY_KEY, out var k) && category == k as string;
public static bool IsCategory(this ViewDataDictionary viewData, WellKnownCategories category) =>
IsCategory(viewData, LayoutModel.Map(category));
public static void SetActivePage(this ViewDataDictionary viewData, string activePage, string category, string title = null, string activeId = null)
=> viewData.SetLayoutModel(new(activePage, title){ SubMenuItemId = activeId, ActiveCategory = category } );
public static void SetActiveCategory<T>(this ViewDataDictionary viewData, T activeCategory)
{
SetActiveCategory(viewData, activeCategory.ToString());
@@ -55,6 +68,7 @@ namespace BTCPayServer.Abstractions.Extensions
viewData[ACTIVE_CATEGORY_KEY] = activeCategory;
}
[Obsolete("Use IsCategory instead")]
public static bool IsCategoryActive(this ViewDataDictionary viewData, string category, object id = null)
{
if (!viewData.ContainsKey(ACTIVE_CATEGORY_KEY)) return false;
@@ -65,6 +79,7 @@ namespace BTCPayServer.Abstractions.Extensions
return categoryMatch && idMatch;
}
[Obsolete("Use IsCategory instead")]
public static bool IsCategoryActive<T>(this ViewDataDictionary viewData, T category, object id = null)
{
return IsCategoryActive(viewData, category.ToString(), id);
@@ -81,7 +96,7 @@ namespace BTCPayServer.Abstractions.Extensions
var idMatch = id == null || activeId == null || id.Equals(activeId);
return categoryAndPageMatch && idMatch;
}
public static bool IsPageActive<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null)
where T : IConvertible
{
@@ -98,17 +113,20 @@ namespace BTCPayServer.Abstractions.Extensions
return IsCategoryActive(viewData, category, id) ? ACTIVE_CLASS : null;
}
[Obsolete("Use the tagHelper layout-menu-item instead")]
public static string ActivePageClass<T>(this ViewDataDictionary viewData, T page, object id = null)
where T : IConvertible
{
return ActivePageClass(viewData, page.ToString(), page.GetType().ToString(), id);
}
[Obsolete("Use the tagHelper layout-menu-item instead")]
public static string ActivePageClass(this ViewDataDictionary viewData, string page, string category, object id = null)
{
return IsPageActive(viewData, page, category, id) ? ACTIVE_CLASS : null;
}
[Obsolete("Use the tagHelper layout-menu-item instead")]
public static string ActivePageClass<T>(this ViewDataDictionary viewData, IEnumerable<T> pages, object id = null) where T : IConvertible
{
return IsPageActive(viewData, pages, id) ? ACTIVE_CLASS : null;

View File

@@ -0,0 +1,36 @@
#nullable enable
using NBitcoin;
namespace BTCPayServer.Abstractions.Models;
public record WellKnownCategories(string CategoryId)
{
public static readonly WellKnownCategories Server = new("BTCPayServer.Views.Server.ServerNavPages");
public static readonly WellKnownCategories Store = new("BTCPayServer.Views.Stores.StoreNavPages");
public static readonly WellKnownCategories Wallet = new("BTCPayServer.Views.Wallets.WalletsNavPages");
public static WellKnownCategories ForWallet(string cryptoCode) => new(
$"BTCPayServer.Views.Wallets.WalletsNavPages.{cryptoCode.ToUpperInvariant()}");
public static WellKnownCategories ForLightning(string cryptoCode) => new(
$"LightningPages.{cryptoCode.ToUpperInvariant()}");
}
public class LayoutModel(string menuItemId, string? title = null)
{
public static string Map(WellKnownCategories c) => c.CategoryId;
public LayoutModel SetCategory(WellKnownCategories category)
{
ActiveCategory = Map(category);
return this;
}
public LayoutModel SetCategory(string category)
{
ActiveCategory = category;
return this;
}
public string? ActiveCategory { get; set; }
public string MenuItemId { get; set; } = menuItemId;
public string? Title { get; set; } = title;
public string? SubMenuItemId { get; set; }
}

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace BTCPayServer.Abstractions.TagHelpers;
[HtmlTargetElement(Attributes = "[layout-menu-item]")]
public class LayoutMenuItemTagHelper : TagHelper
{
private const string ActivePageKey = "ActivePage";
private const string ActiveClass = "active";
[ViewContext]
public ViewContext ViewContext { get; set; }
public string LayoutMenuItem { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.Attributes.Add("id", $"menu-item-{LayoutMenuItem}");
var viewData = ViewContext.ViewData;
var match = viewData.ContainsKey(ActivePageKey) && viewData[ActivePageKey]?.ToString() == LayoutMenuItem;
output.Attributes.Add("class", $"menu-item nav-link {(match ? ActiveClass : "")}");
}
}

View File

@@ -91,7 +91,7 @@ public class MultisigTests : UnitTestBase
s.TestLogs.LogInformation($"Multisig wallet setup: {multisigDerivationScheme}");
// fetch address from receive page
await s.Page.ClickAsync("#WalletNav-Receive");
await s.GoToWallet(navPages: WalletsNavPages.Receive);
var addressElement = s.Page.Locator("#Address");
await addressElement.ClickAsync();
@@ -101,7 +101,7 @@ public class MultisigTests : UnitTestBase
await s.Page.ClickAsync("#CancelWizard");
// we are creating a pending transaction
await s.Page.ClickAsync("#WalletNav-Send");
await s.GoToWallet(navPages: WalletsNavPages.Send);
await s.Page.FillAsync("#Outputs_0__DestinationAddress", address);
var amount = "0.1";
await s.Page.FillAsync("#Outputs_0__Amount", amount);
@@ -127,7 +127,7 @@ public class MultisigTests : UnitTestBase
Assert.False(await s.Page.Locator("//a[text()='Broadcast']").IsVisibleAsync());
// Abort pending transaction flow
await s.Page.ClickAsync("#WalletNav-Send");
await s.GoToWallet(navPages: WalletsNavPages.Send);
await s.Page.FillAsync("#Outputs_0__DestinationAddress", address);
await s.Page.FillAsync("#Outputs_0__Amount", "0.2");
await s.Page.ClickAsync("#CreatePendingTransaction");

View File

@@ -148,7 +148,7 @@ fruit tea:
}
[Fact]
[Trait("Integration", "Integration")]
[Trait("Playwright", "Playwright")]
public async Task CanExportInvoicesWithMetadata()
{
await using var s = CreatePlaywrightTester();

View File

@@ -47,7 +47,7 @@ namespace BTCPayServer.Tests
var psbt = await ExtractPSBT(s);
await s.GoToStore(hot.storeId);
await s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.PSBT);
await s.GoToWallet(s.WalletId, navPages: Views.Wallets.WalletsNavPages.PSBT);
await s.Page.Locator("[name='PSBT']").FillAsync(psbt);
await s.Page.ClickAsync("#Decode");
await s.Page.ClickAsync("#SignTransaction");
@@ -72,7 +72,7 @@ namespace BTCPayServer.Tests
var skeletonPSBT = psbtParsed;
await s.GoToStore(cold.storeId);
await s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.PSBT);
await s.GoToWallet(s.WalletId, navPages: Views.Wallets.WalletsNavPages.PSBT);
await s.Page.Locator("[name='PSBT']").FillAsync(skeletonPSBT.ToBase64());
await s.Page.ClickAsync("#Decode");
await s.Page.ClickAsync("#SignTransaction");
@@ -96,7 +96,7 @@ namespace BTCPayServer.Tests
// Let's if we can combine the updated psbt (which has hdkeys, but no sig)
// with the signed psbt (which has sig, but no hdkeys)
await s.GoToWallet(navPages: Views.Wallets.WalletsNavPages.PSBT);
await s.GoToWallet(s.WalletId, navPages: Views.Wallets.WalletsNavPages.PSBT);
await s.Page.Locator("[name='PSBT']").FillAsync(psbtParsed.ToBase64());
await s.Page.ClickAsync("#Decode");
await s.Page.ClickAsync("#PSBTOptionsAdvancedHeader");

View File

@@ -7,21 +7,17 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Blazor.VaultBridge.Elements;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Views.Manage;
using BTCPayServer.Views.Server;
using BTCPayServer.Views.Stores;
using BTCPayServer.Views.Wallets;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Playwright;
using NBitcoin;
using NBitcoin.RPC;
using OpenQA.Selenium;
using Xunit;
namespace BTCPayServer.Tests
@@ -52,7 +48,8 @@ namespace BTCPayServer.Tests
Browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = Server.PayTester.InContainer,
SlowMo = 0 // 50 if you want to slow down
SlowMo = 0, // 50 if you want to slow down
Args = ["--disable-frame-rate-limit"] // Fix slowness on linux (https://github.com/microsoft/playwright/issues/34625#issuecomment-2822015672)
});
var context = await Browser.NewContextAsync();
Page = await context.NewPageAsync();
@@ -67,7 +64,7 @@ namespace BTCPayServer.Tests
{
if (storeId is null)
{
await Page.Locator("#StoreNav-Invoices").ClickAsync();
await Page.Locator("#menu-item-Invoices").ClickAsync();
}
else
{
@@ -117,21 +114,51 @@ namespace BTCPayServer.Tests
await GoToUrl($"/i/{invoiceId}");
}
public async Task GoToWallet(WalletId walletId = null, WalletsNavPages navPages = WalletsNavPages.Send)
public async Task GoToWallet(WalletId walletId = null, WalletsNavPages? navPages = null)
{
walletId ??= WalletId;
await GoToUrl($"wallets/{walletId}");
if (navPages == WalletsNavPages.PSBT)
var walletPage = GetWalletIdFromUrl();
// If null, we try to go to the wallet of the current page
if (walletId is null)
{
await Page.Locator("#WalletNav-Send").ClickAsync();
if (walletPage is not null)
walletId = WalletId.Parse(walletPage);
walletId ??= WalletId;
if (walletId is not null && walletId.ToString() != walletPage)
await GoToUrl($"wallets/{walletId}");
}
// If not, we browse to the wallet before going to subpages
else
{
await GoToUrl($"wallets/{walletId}");
}
var cryptoCode = walletId?.CryptoCode ?? "BTC";
if (navPages is null)
{
await Page.GetByTestId("Wallet-" + cryptoCode).Locator("a").ClickAsync();
}
else if (navPages == WalletsNavPages.PSBT)
{
await Page.Locator($"#menu-item-Send-{cryptoCode}").ClickAsync();
await Page.Locator("#PSBT").ClickAsync();
}
else if (navPages != WalletsNavPages.Transactions)
else
{
await Page.Locator($"#WalletNav-{navPages}").ClickAsync();
await Page.Locator($"#menu-item-{navPages}-{cryptoCode}").ClickAsync();
}
}
private string GetWalletIdFromUrl()
{
var m = Regex.Match(Page.Url, "wallets/([^/]+)");
if (m.Success)
return m.Groups[1].Value;
m = Regex.Match(Page.Url, "([^/]+)/onchain/([^/]+)");
if (m.Success)
return new WalletId(m.Groups[1].Value, m.Groups[2].Value).ToString();
return null;
}
public async Task<ILocator> FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success, string partialText = null)
{
var locator = await FindAlertMessage(new[] { severity });
@@ -219,7 +246,7 @@ namespace BTCPayServer.Tests
Assert.Equal("Recommendation (Kraken)", selectedOption?.Trim());
await Page.Locator("#PreferredExchange").SelectOptionAsync(new SelectOptionValue { Label = preferredExchange });
await Page.ClickAsync("#Create");
await Page.ClickAsync("#StoreNav-General");
await GoToStore(StoreNavPages.General);
var storeId = await Page.InputValueAsync("#Id");
if (keepId)
StoreId = storeId;
@@ -232,7 +259,8 @@ namespace BTCPayServer.Tests
var isImport = !string.IsNullOrEmpty(seed);
await GoToWalletSettings(cryptoCode);
// Replace previous wallet case
if (await Page.Locator("#ActionsDropdownToggle").IsVisibleAsync())
var isSettings = Page.Url.EndsWith("/settings");
if (isSettings)
{
TestLogs.LogInformation($"Replacing the wallet");
await Page.ClickAsync("#ActionsDropdownToggle");
@@ -296,7 +324,7 @@ namespace BTCPayServer.Tests
}
public async Task Logout()
{
await Page.Locator("#Nav-Account").ClickAsync();
await Page.Locator("#menu-item-Account").ClickAsync();
await Page.Locator("#Nav-Logout").ClickAsync();
}
@@ -330,24 +358,24 @@ namespace BTCPayServer.Tests
public async Task GoToProfile(ManageNavPages navPages = ManageNavPages.Index)
{
await Page.ClickAsync("#Nav-Account");
await Page.ClickAsync("#menu-item-Account");
await Page.ClickAsync("#Nav-ManageAccount");
if (navPages != ManageNavPages.Index)
{
await Page.ClickAsync($"#SectionNav-{navPages.ToString()}");
await Page.ClickAsync($"#menu-item-{navPages.ToString()}");
}
}
public async Task GoToServer(ServerNavPages navPages = ServerNavPages.Policies)
{
await Page.ClickAsync("#Nav-ServerSettings");
await Page.ClickAsync("#menu-item-Policies");
if (navPages != ServerNavPages.Policies)
{
await Page.ClickAsync($"#SectionNav-{navPages}");
await Page.ClickAsync($"#menu-item-{navPages}");
}
}
public async Task ClickOnAllSectionLinks(string sectionSelector = "#SectionNav")
public async Task ClickOnAllSectionLinks(string sectionSelector = "#menu-item")
{
List<string> links = [];
foreach (var locator in await Page.Locator($"{sectionSelector} .nav-link").AllAsync())
@@ -441,11 +469,11 @@ namespace BTCPayServer.Tests
public async Task GoToLightningSettings(string cryptoCode = "BTC")
{
await Page.ClickAsync($"#StoreNav-Lightning{cryptoCode}");
await Page.GetByTestId("Lightning-" + cryptoCode).ClickAsync();
// if Lightning is already set up we need to navigate to the settings
if ((await Page.ContentAsync()).Contains("id=\"StoreNav-LightningSettings\""))
if ((await Page.ContentAsync()).Contains($"id=\"menu-item-LightningSettings-{cryptoCode}\""))
{
await Page.ClickAsync("#StoreNav-LightningSettings");
await Page.ClickAsync($"#menu-item-LightningSettings-{cryptoCode}");
}
}
@@ -456,8 +484,8 @@ namespace BTCPayServer.Tests
public async Task GoToWalletSettings(string cryptoCode = "BTC")
{
await Page.ClickAsync($"#StoreNav-Wallet{cryptoCode}");
var walletNavSettings = Page.Locator("#WalletNav-Settings");
await Page.GetByTestId("Wallet-" + cryptoCode).Locator("a").ClickAsync();
var walletNavSettings = Page.Locator($"#menu-item-Settings-{cryptoCode}");
if (await walletNavSettings.CountAsync() > 0)
await walletNavSettings.ClickAsync();
}
@@ -476,9 +504,9 @@ namespace BTCPayServer.Tests
if (WalletId != null)
WalletId = new WalletId(storeId, WalletId.CryptoCode);
if (storeNavPage != StoreNavPages.General)
await Page.Locator($"#StoreNav-{StoreNavPages.General}").ClickAsync();
await Page.Locator($"#menu-item-{StoreNavPages.General}").ClickAsync();
}
await Page.Locator($"#StoreNav-{storeNavPage}").ClickAsync();
await Page.Locator($"#menu-item-{storeNavPage}").ClickAsync();
}
public async Task ClickCancel()
{
@@ -561,7 +589,7 @@ namespace BTCPayServer.Tests
{
if (string.IsNullOrEmpty(name))
name = $"{type}-{Guid.NewGuid().ToString()[..14]}";
await Page.Locator($"#StoreNav-Create{type}").ClickAsync();
await Page.Locator($"#menu-item-CreateApp-{type}").ClickAsync();
await Page.Locator("[name='AppName']").FillAsync(name);
await ClickPagePrimary();
await FindAlertMessage(partialText: "App successfully created");

View File

@@ -71,10 +71,7 @@ namespace BTCPayServer.Tests
await s.InitializeBTCPayServer();
// Point Of Sale
var appName = $"PoS-{Guid.NewGuid().ToString()[..21]}";
await s.Page.ClickAsync("#StoreNav-CreatePointOfSale");
await s.Page.FillAsync("#AppName", appName);
await s.ClickPagePrimary();
await s.FindAlertMessage(partialText: "App successfully created");
await s.CreateApp("PointOfSale", appName);
await s.Page.SelectOptionAsync("#FormId", "Email");
await s.ClickPagePrimary();
await s.FindAlertMessage(partialText: "App updated");
@@ -94,7 +91,7 @@ namespace BTCPayServer.Tests
await s.GoToUrl($"/invoices/{invoiceId}/");
Assert.Contains("aa@aa.com", await s.Page.ContentAsync());
// Payment Request
await s.Page.ClickAsync("#StoreNav-PaymentRequests");
await s.Page.ClickAsync("#menu-item-PaymentRequests");
await s.ClickPagePrimary();
await s.Page.FillAsync("#Title", "Pay123");
await s.Page.FillAsync("#Amount", "700");
@@ -142,9 +139,11 @@ namespace BTCPayServer.Tests
await s.GoToHome();
await s.GoToStore();
await s.GoToStore(StoreNavPages.Forms);
await s.Page.WaitForLoadStateAsync();
Assert.Contains("Custom Form 1", await s.Page.ContentAsync());
await s.Page.GetByRole(AriaRole.Link, new() { Name = "Remove" }).ClickAsync();
await s.ConfirmDeleteModal();
await s.Page.WaitForLoadStateAsync();
Assert.DoesNotContain("Custom Form 1", await s.Page.ContentAsync());
await s.ClickPagePrimary();
await s.Page.FillAsync("[name='Name']", "Custom Form 2");
@@ -169,7 +168,7 @@ namespace BTCPayServer.Tests
await s.ClickPagePrimary();
await s.GoToStore(StoreNavPages.Forms);
Assert.Contains("Custom Form 3", await s.Page.ContentAsync());
await s.Page.ClickAsync("#StoreNav-PaymentRequests");
await s.Page.ClickAsync("#menu-item-PaymentRequests");
await s.ClickPagePrimary();
var selectOptions = await s.Page.Locator("#FormId >> option").CountAsync();
Assert.Equal(4, selectOptions);
@@ -584,7 +583,7 @@ namespace BTCPayServer.Tests
Assert.Equal("Can Use Store?" ,await s.Page.InputValueAsync("#Name"));
await s.Page.FillAsync("#Name", "Just changed it!");
await s.Page.ClickAsync("#Create");
await s.Page.ClickAsync("#StoreNav-General");
await s.GoToStore();
var newStoreId = await s.Page.InputValueAsync("#Id");
Assert.NotEqual(newStoreId, s.StoreId);
@@ -875,8 +874,7 @@ namespace BTCPayServer.Tests
await s.GenerateWallet(cryptoCode, "", true);
//let's test quickly the wallet send page
await s.Page.ClickAsync($"#StoreNav-Wallet{cryptoCode}");
await s.Page.ClickAsync("#WalletNav-Send");
await s.GoToWallet(navPages: WalletsNavPages.Send);
//you cannot use the Sign with NBX option without saving private keys when generating the wallet.
Assert.DoesNotContain("nbx-seed", await s.Page.ContentAsync());
Assert.Equal(0, await s.Page.Locator("#GoBack").CountAsync());
@@ -884,7 +882,7 @@ namespace BTCPayServer.Tests
await s.Page.WaitForSelectorAsync("text=Destination Address field is required");
Assert.Equal(0, await s.Page.Locator("#GoBack").CountAsync());
await s.Page.ClickAsync("#CancelWizard");
await s.Page.ClickAsync("#WalletNav-Receive");
await s.GoToWallet(navPages: WalletsNavPages.Receive);
//generate a receiving address
await s.Page.WaitForSelectorAsync("#address-tab .qr-container");
@@ -1038,8 +1036,7 @@ namespace BTCPayServer.Tests
// Assert that the added label is associated with the transaction
await wt.AssertHasLabels("tx-label");
await s.Page.ClickAsync($"#StoreNav-Wallet{cryptoCode}");
await s.Page.ClickAsync("#WalletNav-Send");
await s.GoToWallet(navPages: WalletsNavPages.Send);
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
await ws.FillAddress(jack);
@@ -1090,7 +1087,7 @@ namespace BTCPayServer.Tests
Assert.Equal(settingsUri.ToString(), s.Page.Url);
// Once more, test the cancel link of the wallet send page leads back to the previous page
await s.Page.ClickAsync("#WalletNav-Send");
await s.GoToWallet(navPages: WalletsNavPages.Send);
cancelUrl = await s.Page.Locator("#CancelWizard").GetAttributeAsync("href");
Assert.EndsWith(settingsUri.AbsolutePath, cancelUrl);
// no previous page in the wizard, hence no back button
@@ -1230,7 +1227,7 @@ namespace BTCPayServer.Tests
// Create a payment request
await s.GoToStore();
await s.Page.ClickAsync("#StoreNav-PaymentRequests");
await s.Page.ClickAsync("#menu-item-PaymentRequests");
await s.ClickPagePrimary();
await s.Page.FillAsync("#Title", "Test Payment Request");
await s.Page.FillAsync("#Amount", "0.1");
@@ -1272,7 +1269,7 @@ namespace BTCPayServer.Tests
await s.Page.WaitForLoadStateAsync();
await s.GoToStore();
await s.Page.ClickAsync("#StoreNav-PaymentRequests");
await s.Page.ClickAsync("#menu-item-PaymentRequests");
await s.Page.WaitForLoadStateAsync();
var opening2 = s.Page.Context.WaitForPageAsync();
@@ -1288,7 +1285,7 @@ namespace BTCPayServer.Tests
}
await s.GoToStore();
await s.Page.ClickAsync("#StoreNav-PaymentRequests");
await s.Page.ClickAsync("#menu-item-PaymentRequests");
await s.Page.WaitForLoadStateAsync();
var listContent = await s.Page.ContentAsync();
@@ -1349,14 +1346,14 @@ namespace BTCPayServer.Tests
Assert.Equal("1", await s.Page.Locator("#NotificationsBadge").TextContentAsync());
});
await s.Page.Locator("#NotificationsHandle").ClickAsync();
Assert.Matches($"New user {unapproved.RegisterDetails.Email} requires approval", await s.Page.Locator("#NotificationsList .notification").TextContentAsync());
await s.Page.Locator("#NotificationsMarkAllAsSeen").ClickAsync();
await s.Page.ClickAsync("#NotificationsHandle");
await s.Page.Locator($"#NotificationsList .notification:has-text('New user {unapproved.RegisterDetails.Email} requires approval')").WaitForAsync();
await s.Page.ClickAsync("#NotificationsMarkAllAsSeen");
await s.GoToServer(ServerNavPages.Policies);
Assert.True(await s.Page.Locator("#EnableRegistration").IsCheckedAsync());
Assert.True(await s.Page.Locator("#RequiresUserApproval").IsCheckedAsync());
await s.Page.Locator("#RequiresUserApproval").ClickAsync();
await s.Page.ClickAsync("#RequiresUserApproval");
await s.ClickPagePrimary();
await s.FindAlertMessage(partialText: "Policies updated successfully");
Assert.False(await s.Page.Locator("#RequiresUserApproval").IsCheckedAsync());
@@ -1702,7 +1699,7 @@ namespace BTCPayServer.Tests
await s.Page.GetAttributeAsync("#AccountKeys_0__AccountKeyPath", "value"));
// Transactions list is empty
await s.Page.ClickAsync($"#StoreNav-Wallet{cryptoCode}");
await s.GoToWallet();
await s.Page.WaitForSelectorAsync("#WalletTransactions[data-loaded='true']");
Assert.Contains("There are no transactions yet", await s.Page.Locator("#WalletTransactions").TextContentAsync());
}
@@ -1829,7 +1826,7 @@ namespace BTCPayServer.Tests
];
await s.Server.ExplorerNode.GenerateAsync(1);
await s.GoToWallet(walletId);
await s.GoToWallet(walletId, WalletsNavPages.Send);
await s.Page.ClickAsync("#toggleInputSelection");
var input = s.Page.Locator("input[placeholder^='Filter']");
@@ -1885,11 +1882,11 @@ namespace BTCPayServer.Tests
(string storeName, _) = await s.CreateNewStore();
// Check status in navigation
await s.Page.Locator("#StoreNav-LightningBTC .btcpay-status--pending").WaitForAsync();
await s.Page.Locator("#menu-item-LightningSettings-BTC .btcpay-status--pending").WaitForAsync();
// Set up LN node
await s.AddLightningNode();
await s.Page.Locator("#StoreNav-LightningBTC .btcpay-status--enabled").WaitForAsync();
await s.Page.Locator("#menu-item-Lightning-BTC .btcpay-status--enabled").WaitForAsync();
// Check public node info for availability
var opening = s.Page.Context.WaitForPageAsync();
@@ -1915,7 +1912,7 @@ namespace BTCPayServer.Tests
await s.FindAlertMessage(partialText: "BTC Lightning node updated.");
// Check offline state is communicated in nav item
await s.Page.Locator("#StoreNav-LightningBTC .btcpay-status--disabled").WaitForAsync();
await s.Page.Locator("#menu-item-Lightning-BTC .btcpay-status--disabled").WaitForAsync();
// Check public node info for availability
opening = s.Page.Context.WaitForPageAsync();
@@ -2098,8 +2095,9 @@ namespace BTCPayServer.Tests
await s.Page.SetCheckedAsync("#AutoApproveClaims", true);
await s.ClickPagePrimary();
var o = s.Page.Context.WaitForPageAsync();
await s.Page.ClickAsync("text=View");
var newPage = await s.Page.Context.WaitForPageAsync();
var newPage = await o;
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
await newPage.FillAsync("#Destination", address.ToString());
@@ -2198,7 +2196,7 @@ namespace BTCPayServer.Tests
// The delivery is done asynchronously, so small wait here
await Task.Delay(500);
await s.GoToStore();
await s.Page.ClickAsync("#StoreNav-Webhooks");
await s.GoToStore(StoreNavPages.Webhooks);
await s.Page.ClickAsync("text=Modify");
var redeliverElements = await s.Page.Locator("button.redeliver").AllAsync();
@@ -2235,8 +2233,9 @@ namespace BTCPayServer.Tests
private static async Task CanBrowseContentAsync(PlaywrightTester s)
{
var newPageDoing = s.Page.Context.WaitForPageAsync();
await s.Page.ClickAsync(".delivery-content");
var newPage = await s.Page.Context.WaitForPageAsync();
var newPage = await newPageDoing;
var bodyText = await newPage.Locator("body").TextContentAsync();
JObject.Parse(bodyText);
await newPage.CloseAsync();

View File

@@ -222,7 +222,7 @@ retry:
Assert.Equal("Recommendation (Kraken)", rateSource.SelectedOption.Text);
rateSource.SelectByText("CoinGecko");
Driver.WaitForElement(By.Id("Create")).Click();
Driver.FindElement(By.Id("StoreNav-General")).Click();
Driver.FindElement(By.Id("menu-item-General")).Click();
var storeId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
if (keepId)
StoreId = storeId;
@@ -415,7 +415,7 @@ retry:
{
if (!Driver.PageSource.Contains("id=\"Nav-Logout\""))
GoToUrl("/account");
Driver.FindElement(By.Id("Nav-Account")).Click();
Driver.FindElement(By.Id("menu-item-Account")).Click();
Driver.FindElement(By.Id("Nav-Logout")).Click();
}
@@ -447,27 +447,27 @@ retry:
if (storeNavPage != StoreNavPages.General)
{
Driver.FindElement(By.Id($"StoreNav-{StoreNavPages.General}")).Click();
Driver.FindElement(By.Id($"menu-item-{StoreNavPages.General}")).Click();
}
Driver.FindElement(By.Id($"StoreNav-{storeNavPage}")).Click();
Driver.FindElement(By.Id($"menu-item-{storeNavPage}")).Click();
}
public void GoToWalletSettings(string cryptoCode = "BTC")
{
Driver.FindElement(By.Id($"StoreNav-Wallet{cryptoCode}")).Click();
if (Driver.PageSource.Contains("id=\"WalletNav-Settings\""))
Driver.FindElement(By.CssSelector($"[data-testid=\"Wallet-{cryptoCode}\"] a")).Click();
if (Driver.PageSource.Contains($"id=\"menu-item-Settings-{cryptoCode}\""))
{
Driver.FindElement(By.Id("WalletNav-Settings")).Click();
Driver.FindElement(By.Id($"menu-item-Settings-{cryptoCode}")).Click();
}
}
public void GoToLightningSettings(string cryptoCode = "BTC")
{
Driver.FindElement(By.Id($"StoreNav-Lightning{cryptoCode}")).Click();
Driver.FindElement(By.CssSelector($"[data-testid=\"Lightning-{cryptoCode}\"]")).Click();
// if Lightning is already set up we need to navigate to the settings
if (Driver.PageSource.Contains("id=\"StoreNav-LightningSettings\""))
if (Driver.PageSource.Contains($"id=\"menu-item-LightningSettings-{cryptoCode}\""))
{
Driver.FindElement(By.Id("StoreNav-LightningSettings")).Click();
Driver.FindElement(By.Id($"menu-item-LightningSettings-{cryptoCode}")).Click();
}
}
@@ -480,7 +480,7 @@ retry:
public void GoToInvoiceCheckout(string invoiceId = null)
{
invoiceId ??= InvoiceId;
Driver.FindElement(By.Id("StoreNav-Invoices")).Click();
Driver.FindElement(By.Id("menu-item-Invoices")).Click();
Driver.FindElement(By.Id($"invoice-checkout-{invoiceId}")).Click();
CheckForJSErrors();
Driver.WaitUntilAvailable(By.Id("Checkout"));
@@ -495,7 +495,7 @@ retry:
{
if (storeId is null)
{
Driver.FindElement(By.Id("StoreNav-Invoices")).Click();
Driver.FindElement(By.Id("menu-item-Invoices")).Click();
}
else
{
@@ -506,11 +506,11 @@ retry:
public void GoToProfile(ManageNavPages navPages = ManageNavPages.Index)
{
Driver.WaitForAndClick(By.Id("Nav-Account"));
Driver.WaitForAndClick(By.Id("menu-item-Account"));
Driver.WaitForAndClick(By.Id("Nav-ManageAccount"));
if (navPages != ManageNavPages.Index)
{
Driver.WaitForAndClick(By.Id($"SectionNav-{navPages.ToString()}"));
Driver.WaitForAndClick(By.Id($"menu-item-{navPages.ToString()}"));
}
}
@@ -614,12 +614,12 @@ retry:
Driver.Navigate().GoToUrl(new Uri(ServerUri, $"wallets/{walletId}"));
if (navPages == WalletsNavPages.PSBT)
{
Driver.FindElement(By.Id("WalletNav-Send")).Click();
Driver.FindElement(By.Id($"menu-item-Send-{walletId.CryptoCode}")).Click();
Driver.FindElement(By.Id("PSBT")).Click();
}
else if (navPages != WalletsNavPages.Transactions)
{
Driver.FindElement(By.Id($"WalletNav-{navPages}")).Click();
Driver.FindElement(By.Id($"menu-item-{navPages}-{walletId.CryptoCode}")).Click();
}
}
@@ -630,10 +630,10 @@ retry:
public void GoToServer(ServerNavPages navPages = ServerNavPages.Policies)
{
Driver.FindElement(By.Id("Nav-ServerSettings")).Click();
Driver.FindElement(By.Id("menu-item-Policies")).Click();
if (navPages != ServerNavPages.Policies)
{
Driver.FindElement(By.Id($"SectionNav-{navPages}")).Click();
Driver.FindElement(By.Id($"menu-item-{navPages}")).Click();
}
}
@@ -671,7 +671,7 @@ retry:
{
if (string.IsNullOrEmpty(name))
name = $"{type}-{Guid.NewGuid().ToString()[..14]}";
Driver.FindElement(By.Id($"StoreNav-Create{type}")).Click();
Driver.FindElement(By.Id($"menu-item-CreateApp-{type}")).Click();
Driver.FindElement(By.Name("AppName")).SendKeys(name);
ClickPagePrimary();
Assert.Contains("App successfully created", FindAlertMessage().Text);

View File

@@ -165,7 +165,7 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.FindElement(By.Id("Dashboard")).Displayed);
// setup offchain wallet
s.Driver.FindElement(By.Id("StoreNav-LightningBTC")).Click();
s.Driver.FindElement(By.CssSelector("[data-testid='Lightning-BTC']")).Click();
s.AddLightningNode();
s.Driver.AssertNoError();
var successAlert = s.FindAlertMessage();
@@ -449,16 +449,16 @@ namespace BTCPayServer.Tests
// Archive
s.Driver.SwitchTo().Window(windows[0]);
Assert.True(s.Driver.ElementDoesNotExist(By.Id("Nav-ArchivedApps")));
Assert.True(s.Driver.ElementDoesNotExist(By.Id("menu-item-AppsNavPages")));
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
Assert.Contains("The app has been archived and will no longer appear in the apps list by default.", s.FindAlertMessage().Text);
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ViewApp")));
Assert.Contains("1 Archived App", s.Driver.FindElement(By.Id("Nav-ArchivedApps")).Text);
Assert.Contains("1 Archived App", s.Driver.FindElement(By.Id("menu-item-AppsNavPages")).Text);
s.Driver.Navigate().GoToUrl(posBaseUrl);
Assert.Contains("Page not found", s.Driver.Title, StringComparison.OrdinalIgnoreCase);
s.Driver.Navigate().Back();
s.Driver.FindElement(By.Id("Nav-ArchivedApps")).Click();
s.Driver.FindElement(By.Id("menu-item-AppsNavPages")).Click();
// Unarchive
s.Driver.FindElement(By.Id($"App-{appId}")).Click();
@@ -527,17 +527,17 @@ namespace BTCPayServer.Tests
s.Driver.SwitchTo().Window(windows[0]);
// Archive
Assert.True(s.Driver.ElementDoesNotExist(By.Id("Nav-ArchivedApps")));
Assert.True(s.Driver.ElementDoesNotExist(By.Id("menu-item-AppsNavPages")));
s.Driver.SwitchTo().Window(windows[0]);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
Assert.Contains("The app has been archived and will no longer appear in the apps list by default.", s.FindAlertMessage().Text);
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ViewApp")));
Assert.Contains("1 Archived App", s.Driver.FindElement(By.Id("Nav-ArchivedApps")).Text);
Assert.Contains("1 Archived App", s.Driver.FindElement(By.Id("menu-item-AppsNavPages")).Text);
s.Driver.Navigate().GoToUrl(cfUrl);
Assert.Contains("Page not found", s.Driver.Title, StringComparison.OrdinalIgnoreCase);
s.Driver.Navigate().Back();
s.Driver.FindElement(By.Id("Nav-ArchivedApps")).Click();
s.Driver.FindElement(By.Id("menu-item-AppsNavPages")).Click();
// Unarchive
s.Driver.FindElement(By.Id($"App-{appId}")).Click();
@@ -612,14 +612,14 @@ namespace BTCPayServer.Tests
await s.StartAsync();
s.RegisterNewUser();
s.CreateNewStore();
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
s.Driver.FindElement(By.Id("menu-item-PaymentRequests")).Click();
// Should give us an error message if we try to create a payment request before adding a wallet
s.ClickPagePrimary();
Assert.Contains("To create a payment request, you need to", s.Driver.PageSource);
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
s.Driver.FindElement(By.Id("menu-item-PaymentRequests")).Click();
s.ClickPagePrimary();
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
s.Driver.FindElement(By.Id("Amount")).Clear();
@@ -1483,11 +1483,11 @@ namespace BTCPayServer.Tests
//ln address tests
s.CreateNewStore();
//ensure ln address is not available as Lightning is not enable
s.Driver.AssertElementNotFound(By.Id("StoreNav-LightningAddress"));
s.Driver.AssertElementNotFound(By.Id("menu-item-LightningAddress"));
s.AddLightningNode(LightningConnectionType.LndREST, false);
s.Driver.FindElement(By.Id("StoreNav-LightningAddress")).Click();
s.Driver.FindElement(By.Id("menu-item-LightningAddress")).Click();
s.Driver.ToggleCollapse("AddAddress");
var lnaddress1 = Guid.NewGuid().ToString();

View File

@@ -226,7 +226,8 @@ public class SubscriptionTests(ITestOutputHelper testOutputHelper) : UnitTestBas
{
await portal.Downgrade("Basic Plan");
unused = GetUnusedPeriodValue(usedDays: 7, planPrice: 99.0m, daysInPeriod: DaysInThisMonth());
totalRefunded += await portal.AssertRefunded(unused);
unused = await portal.AssertRefunded(unused);
totalRefunded += unused;
});
Assert.Equal(unused, credited.Amount);
@@ -241,7 +242,8 @@ public class SubscriptionTests(ITestOutputHelper testOutputHelper) : UnitTestBas
await portal.GoTo7Days();
await portal.Upgrade("Pro Plan");
unused = GetUnusedPeriodValue(usedDays: 7, planPrice: 29.0m, daysInPeriod: DaysInThisMonth());
totalRefunded += await portal.AssertRefunded(unused);
unused = await portal.AssertRefunded(unused);
totalRefunded += unused;
expectedBalance = totalRefunded - 29.0m - 99.0m - 99.0m;
await portal.AssertCredit(creditBalance: $"${expectedBalance:F2}");
@@ -253,14 +255,24 @@ public class SubscriptionTests(ITestOutputHelper testOutputHelper) : UnitTestBas
await s.Server.WaitForEvent<SubscriptionEvent.PlanStarted>(async () =>
{
await portal.Upgrade("Enterprise Plan");
await invoice.AssertContent(new()
{
TotalFiat = USD(299m - expectedBalance - unused)
});
await s.PayInvoice(mine: true);
});
await invoice.ClickRedirect();
totalRefunded += await portal.AssertRefunded(unused);
unused = await portal.AssertRefunded(unused);
totalRefunded += unused;
await s.Page.EvaluateAsync("window.scrollTo(0, document.body.scrollHeight)");
await s.TakeScreenshot("upgrade2.png");
await portal.AssertCreditHistory(
[
"Upgrade to new plan 'Enterprise Plan'",
"Credit purchase"
],
[
"-$" + (299m - unused).ToString("F2", CultureInfo.InvariantCulture),
"$" + (299m - expectedBalance - unused).ToString("F2", CultureInfo.InvariantCulture)
]);
expectedBalance = 0m;
await portal.AssertCredit(creditBalance: $"${expectedBalance:F2}");
}
}
@@ -759,7 +771,10 @@ public class SubscriptionTests(ITestOutputHelper testOutputHelper) : UnitTestBas
var match = Regex.Match(text!, @"\((.*?) USD has been refunded\)");
var v = decimal.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
var diff = Math.Abs(refunded - v);
Assert.True(diff < 2.0m);
if (diff >= 3.0m)
{
Assert.Fail($"Expected {refunded} USD, but got {v} USD");
}
return v;
}
@@ -769,13 +784,16 @@ public class SubscriptionTests(ITestOutputHelper testOutputHelper) : UnitTestBas
Assert.Equal(plan, name);
}
public async Task AssertCreditHistory(List<string> creditLines)
public async Task AssertCreditHistory(List<string> creditLines, List<string>? creditAmounts = null)
{
var rows = await s.Page.QuerySelectorAllAsync(".credit-history tr td:nth-child(2)");
var descriptions = await s.Page.QuerySelectorAllAsync(".credit-history tr td:nth-child(2)");
var credits = await s.Page.QuerySelectorAllAsync(".credit-history tr td:nth-child(3)");
for (int i = 0; i < creditLines.Count; i++)
{
var txt = await rows[i].InnerTextAsync();
var txt = await descriptions[i].InnerTextAsync();
Assert.StartsWith(creditLines[i], txt);
if (creditAmounts is not null)
Assert.Equal(creditAmounts[i], await credits[i].InnerTextAsync());
}
}
}

View File

@@ -25,7 +25,7 @@ public class WalletTests(ITestOutputHelper helper) : UnitTestBase(helper)
var txs = (await client.ShowOnChainWalletTransactions(s.StoreId, "BTC")).Select(t => t.TransactionHash).ToArray();
Assert.Equal(3, txs.Length);
var w = await s.GoToWalletTransactions();
var w = await s.GoToWalletTransactions(s.WalletId);
await w.BumpFee(txs[0]);
// Because a single transaction is selected, we should be able to select CPFP only (Because no change are available, we can't do RBF)
@@ -145,7 +145,7 @@ public class WalletTests(ITestOutputHelper helper) : UnitTestBase(helper)
for (int i = 0; i < 5; i++)
{
var txs = await s.GoToWalletTransactions();
var txs = await s.GoToWalletTransactions(s.WalletId);
await txs.SelectAll();
await txs.BumpFeeSelected();
await s.ClickPagePrimary();

View File

@@ -1,12 +1,8 @@
@using BTCPayServer.Views.Server
@using BTCPayServer.Views.Stores
@using BTCPayServer.Views.Invoice
@using BTCPayServer.Views.Manage
@using BTCPayServer.Views.PaymentRequest
@using BTCPayServer.Views.Wallets
@using BTCPayServer.Client
@using BTCPayServer.Components.ThemeSwitch
@using BTCPayServer.Components.UIExtensionPoint
@using BTCPayServer.Plugins
@using BTCPayServer.Services
@using BTCPayServer.Views.Apps
@@ -33,45 +29,45 @@
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Dashboard))" asp-area="" asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Dashboard)">
<a layout-menu-item="@nameof(StoreNavPages.Dashboard)" asp-area="" asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@Model.Store.Id">
<vc:icon symbol="nav-dashboard"/>
<span text-translate="true">Dashboard</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.General))" asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.General)">
<a layout-menu-item="General" asp-area="" asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@Model.Store.Id">
<vc:icon symbol="nav-store-settings"/>
<span text-translate="true">Settings</span>
</a>
</li>
@if (ViewData.IsPageActive([StoreNavPages.General, StoreNavPages.Rates, StoreNavPages.CheckoutAppearance, StoreNavPages.Tokens, StoreNavPages.Users, StoreNavPages.Roles, StoreNavPages.Webhooks, StoreNavPages.PayoutProcessors, StoreNavPages.Emails, StoreNavPages.Forms]))
@if (ViewData.IsCategory(WellKnownCategories.Store))
{
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Rates)" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@Model.Store.Id" text-translate="true">Rates</a>
<a layout-menu-item="@(nameof(StoreNavPages.Rates))" asp-controller="UIStores" asp-action="Rates" asp-route-storeId="@Model.Store.Id" text-translate="true">Rates</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.CheckoutAppearance)" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@Model.Store.Id" text-translate="true">Checkout Appearance</a>
<a layout-menu-item="@(nameof(StoreNavPages.CheckoutAppearance))" asp-controller="UIStores" asp-action="CheckoutAppearance" asp-route-storeId="@Model.Store.Id" text-translate="true">Checkout Appearance</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@Model.Store.Id" text-translate="true">Access Tokens</a>
<a layout-menu-item="@nameof(StoreNavPages.Tokens)" asp-controller="UIStores" asp-action="ListTokens" asp-route-storeId="@Model.Store.Id" text-translate="true">Access Tokens</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Users)" asp-controller="UIStores" asp-action="StoreUsers" asp-route-storeId="@Model.Store.Id" text-translate="true">Users</a>
<a layout-menu-item="@(nameof(StoreNavPages.Users))" asp-controller="UIStores" asp-action="StoreUsers" asp-route-storeId="@Model.Store.Id" text-translate="true">Users</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Roles))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Roles)" asp-controller="UIStores" asp-action="ListRoles" asp-route-storeId="@Model.Store.Id" text-translate="true">Roles</a>
<a layout-menu-item="@(nameof(StoreNavPages.Roles))" asp-controller="UIStores" asp-action="ListRoles" asp-route-storeId="@Model.Store.Id" text-translate="true">Roles</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Webhooks)" asp-area="Webhooks" asp-controller="UIStoreWebhooks" asp-action="Webhooks" asp-route-storeId="@Model.Store.Id" text-translate="true">Webhooks</a>
<a layout-menu-item="@(nameof(StoreNavPages.Webhooks))" asp-area="Webhooks" asp-controller="UIStoreWebhooks" asp-action="Webhooks" asp-route-storeId="@Model.Store.Id" text-translate="true">Webhooks</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.PayoutProcessors))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.PayoutProcessors)" asp-controller="UIPayoutProcessors" asp-action="ConfigureStorePayoutProcessors" asp-route-storeId="@Model.Store.Id" text-translate="true">Payout Processors</a>
<a layout-menu-item="@(nameof(StoreNavPages.PayoutProcessors))" asp-controller="UIPayoutProcessors" asp-action="ConfigureStorePayoutProcessors" asp-route-storeId="@Model.Store.Id" text-translate="true">Payout Processors</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Emails))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Emails)" asp-area="@EmailsPlugin.Area" asp-controller="UIStoresEmail" asp-action="StoreEmailSettings" asp-route-storeId="@Model.Store.Id" text-translate="true">Emails</a>
<a layout-menu-item="@(nameof(StoreNavPages.Emails))" asp-area="@EmailsPlugin.Area" asp-controller="UIStoresEmail" asp-action="StoreEmailSettings" asp-route-storeId="@Model.Store.Id" text-translate="true">Emails</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a id="StoreNav-@(nameof(StoreNavPages.Forms))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Forms)" asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@Model.Store.Id" text-translate="true">Forms</a>
<a layout-menu-item="@(nameof(StoreNavPages.Forms))" asp-controller="UIForms" asp-action="FormsList" asp-route-storeId="@Model.Store.Id" text-translate="true">Forms</a>
</li>
}
<vc:ui-extension-point location="store-nav" model="@Model"/>
@@ -88,39 +84,43 @@
@foreach (var scheme in Model.DerivationSchemes.OrderBy(scheme => scheme.Collapsed))
{
var isSetUp = !string.IsNullOrWhiteSpace(scheme.Value);
var categoryId = $"{Model.Store.Id}-{scheme.WalletId.CryptoCode}";
<li class="nav-item">
<li class="nav-item" data-testid="Wallet-@scheme.Crypto">
@if (isSetUp && scheme.WalletSupported)
{
<a asp-area="" asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@scheme.WalletId" class="nav-link @ViewData.ActivePageClass([WalletsNavPages.Transactions], scheme.WalletId.ToString())" id="@($"StoreNav-Wallet{scheme.Crypto}")">
<a layout-menu-item="@nameof(WalletsNavPages.Transactions)-@scheme.Crypto" asp-area="" asp-controller="UIWallets" asp-action="WalletTransactions" asp-route-walletId="@scheme.WalletId">
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
</a>
}
else
{
<a asp-area="" asp-controller="UIStores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.OnchainSettings)" id="@($"StoreNav-Wallet{scheme.Crypto}")">
<a asp-area="" asp-controller="UIStores" asp-action="SetupWallet" asp-route-cryptoCode="@scheme.Crypto" asp-route-storeId="@Model.Store.Id">
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
</a>
}
</li>
@if (ViewData.IsCategoryActive(typeof(WalletsNavPages), scheme.WalletId.ToString()) || ViewData.IsPageActive([WalletsNavPages.Settings], scheme.WalletId.ToString()) || ViewData.IsPageActive([StoreNavPages.OnchainSettings], categoryId))
@if (ViewData.IsCategory(WellKnownCategories.ForWallet(scheme.Crypto)))
{
@if (!scheme.ReadonlyWallet)
{
<li class="nav-item nav-item-sub">
<a id="WalletNav-Send" class="nav-link @ViewData.ActivePageClass([WalletsNavPages.Send, WalletsNavPages.PSBT], scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@scheme.WalletId" text-translate="true">Send</a>
<a layout-menu-item="@nameof(WalletsNavPages.Send)-@scheme.Crypto"
asp-area="" asp-controller="UIWallets" asp-action="WalletSend" asp-route-walletId="@scheme.WalletId" text-translate="true">Send</a>
</li>
}
<li class="nav-item nav-item-sub">
<a id="WalletNav-Receive" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Receive, scheme.WalletId.ToString())" asp-area="" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@scheme.WalletId" text-translate="true">Receive</a>
<a
layout-menu-item="@nameof(WalletsNavPages.Receive)-@scheme.Crypto"
asp-area="" asp-controller="UIWallets" asp-action="WalletReceive" asp-route-walletId="@scheme.WalletId" text-translate="true">Receive</a>
</li>
<li class="nav-item nav-item-sub">
<a id="WalletNav-Settings" class="nav-link @ViewData.ActivePageClass(WalletsNavPages.Settings, scheme.WalletId.ToString()) @ViewData.ActivePageClass(StoreNavPages.OnchainSettings, categoryId)" asp-area="" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@scheme.WalletId.CryptoCode" asp-route-storeId="@scheme.WalletId.StoreId" text-translate="true">Settings</a>
<a
layout-menu-item="@nameof(WalletsNavPages.Settings)-@scheme.Crypto"
asp-area="" asp-controller="UIStores" asp-action="WalletSettings" asp-route-cryptoCode="@scheme.WalletId.CryptoCode" asp-route-storeId="@scheme.WalletId.StoreId" text-translate="true">Settings</a>
</li>
<vc:ui-extension-point location="wallet-nav" model="@Model" />
}
@@ -134,23 +134,29 @@
var status = scheme.Enabled
? scheme.Available ? "enabled" : "disabled"
: "pending";
<a asp-area="" asp-controller="UIStores" asp-action="Lightning" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Lightning, $"{Model.Store.Id}-{scheme.CryptoCode}")" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
<a
data-testid="Lightning-@scheme.CryptoCode"
layout-menu-item="@nameof(StoreNavPages.Lightning)-@scheme.CryptoCode"
asp-area="" asp-controller="UIStores" asp-action="Lightning" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id">
<span class="me-2 btcpay-status btcpay-status--@status"></span>
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
</a>
}
else
{
<a asp-area="" asp-controller="UIStores" asp-action="SetupLightningNode" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.LightningSettings, $"{Model.Store.Id}-{scheme.CryptoCode}")" id="@($"StoreNav-Lightning{scheme.CryptoCode}")">
<a
data-testid="Lightning-@scheme.CryptoCode"
layout-menu-item="@nameof(StoreNavPages.LightningSettings)-@scheme.CryptoCode"
asp-area="" asp-controller="UIStores" asp-action="SetupLightningNode" asp-route-cryptoCode="@scheme.CryptoCode" asp-route-storeId="@Model.Store.Id">
<span class="me-2 btcpay-status btcpay-status--@(scheme.Enabled ? "enabled" : "pending")"></span>
<span>@PrettyName.PrettyName(scheme.PaymentMethodId)</span>
</a>
}
</li>
@if (ViewData.IsPageActive([StoreNavPages.Lightning, StoreNavPages.LightningSettings], $"{Model.Store.Id}-{scheme.CryptoCode}"))
@if (ViewData.IsCategory(WellKnownCategories.ForLightning(scheme.CryptoCode)))
{
<li class="nav-item nav-item-sub">
<a id="StoreNav-@(nameof(StoreNavPages.LightningSettings))" class="nav-link @ViewData.ActivePageClass(StoreNavPages.LightningSettings)" asp-controller="UIStores" asp-action="LightningSettings" asp-route-storeId="@Model.Store.Id" asp-route-cryptoCode="@scheme.CryptoCode" text-translate="true">Settings</a>
<a layout-menu-item="@(nameof(StoreNavPages.LightningSettings))-@scheme.CryptoCode" asp-controller="UIStores" asp-action="LightningSettings" asp-route-storeId="@Model.Store.Id" asp-route-cryptoCode="@scheme.CryptoCode" text-translate="true">Settings</a>
</li>
<vc:ui-extension-point location="lightning-nav" model="@Model"/>
}
@@ -170,34 +176,36 @@
<div class="accordion-body">
<ul class="navbar-nav">
<li class="nav-item" permission="@Policies.CanViewInvoices">
<a asp-area="" asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActiveCategoryClass(typeof(InvoiceNavPages))" id="StoreNav-Invoices">
<a layout-menu-item="Invoices" asp-area="" asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@Model.Store.Id">
<vc:icon symbol="nav-invoices"/>
<span text-translate="true">Invoices</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewReports">
<a asp-area="" asp-controller="UIReports" asp-action="StoreReports" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Reporting)" id="SectionNav-Reporting">
<a layout-menu-item="@nameof(StoreNavPages.Reporting)" asp-area="" asp-controller="UIReports" asp-action="StoreReports" asp-route-storeId="@Model.Store.Id">
<vc:icon symbol="nav-reporting" />
<span text-translate="true">Reporting</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewPaymentRequests">
<a asp-area="" asp-controller="UIPaymentRequest" asp-action="GetPaymentRequests" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActiveCategoryClass(typeof(PaymentRequestsNavPages))" id="StoreNav-PaymentRequests">
<a layout-menu-item="PaymentRequests" asp-area="" asp-controller="UIPaymentRequest" asp-action="GetPaymentRequests" asp-route-storeId="@Model.Store.Id">
<vc:icon symbol="nav-payment-requests"/>
<span text-translate="true">Requests</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewPullPayments">
<a asp-area="" asp-controller="UIStorePullPayments" asp-action="PullPayments" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.PullPayments)" id="StoreNav-PullPayments">
<a layout-menu-item="@nameof(StoreNavPages.PullPayments)" asp-area="" asp-controller="UIStorePullPayments" asp-action="PullPayments" asp-route-storeId="@Model.Store.Id">
<vc:icon symbol="nav-pull-payments"/>
<span text-translate="true">Pull Payments</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewPayouts">
<a asp-area=""
<a
layout-menu-item="@nameof(StoreNavPages.Payouts)"
asp-area=""
asp-controller="UIStorePullPayments" asp-action="Payouts"
asp-route-pullPaymentId=""
asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.Payouts)" id="StoreNav-Payouts">
asp-route-storeId="@Model.Store.Id">
<vc:icon symbol="nav-payouts"/>
<span text-translate="true">Payouts</span>
</a>
@@ -226,7 +234,7 @@
</ul>
<ul class="navbar-nav">
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
<a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Plugins)" id="Nav-ManagePlugins">
<a layout-menu-item="@nameof(ServerNavPages.Plugins)" asp-area="" asp-controller="UIServer" asp-action="ListPlugins">
@if (PluginService.GetDisabledPlugins().Any())
{
<span class="me-2 btcpay-status btcpay-status--disabled"></span>
@@ -241,7 +249,7 @@
@if (Model.Store != null && Model.ArchivedAppsCount > 0)
{
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIApps" asp-action="ListApps" asp-route-storeId="@Model.Store.Id" asp-route-archived="true" class="nav-link @ViewData.ActivePageClass(AppsNavPages.Index)" id="Nav-ArchivedApps">
<a layout-menu-item="@nameof(AppsNavPages)" asp-area="" asp-controller="UIApps" asp-action="ListApps" asp-route-storeId="@Model.Store.Id" asp-route-archived="true">
@Model.ArchivedAppsCount Archived App@(Model.ArchivedAppsCount == 1 ? "" : "s")
</a>
</li>
@@ -292,48 +300,48 @@
{
<ul id="mainNavSettings" class="navbar-nav border-top p-3 px-lg-4">
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
<a asp-area="" asp-controller="UIServer" asp-action="Policies" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Policies)" id="Nav-ServerSettings">
<a layout-menu-item="@nameof(ServerNavPages.Policies)" asp-area="" asp-controller="UIServer" asp-action="Policies">
<vc:icon symbol="nav-server-settings"/>
<span text-translate="true">Server Settings</span>
</a>
</li>
@if (ViewData.IsCategoryActive(typeof(ServerNavPages)) && !ViewData.IsPageActive([ServerNavPages.Plugins]))
@if (ViewData.IsCategory(WellKnownCategories.Server))
{
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Users" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Users)" asp-action="ListUsers" text-translate="true">Users</a>
<a layout-menu-item="@nameof(ServerNavPages.Users)" asp-controller="UIServer" asp-action="ListUsers" text-translate="true">Users</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Roles" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Roles)" asp-action="ListRoles" text-translate="true">Roles</a>
<a layout-menu-item="@nameof(ServerNavPages.Roles)" asp-controller="UIServer" asp-action="ListRoles" text-translate="true">Roles</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Emails" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Emails)" asp-action="Emails" text-translate="true">Email</a>
<a layout-menu-item="Server-@nameof(ServerNavPages.Emails)" asp-controller="UIServer" asp-action="Emails" text-translate="true">Email</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Services" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Services)" asp-action="Services" text-translate="true">Services</a>
<a layout-menu-item="@nameof(ServerNavPages.Services)" asp-controller="UIServer" asp-action="Services" text-translate="true">Services</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Branding" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Branding)" asp-action="Branding" text-translate="true">Branding</a>
<a layout-menu-item="@nameof(ServerNavPages.Branding)" asp-controller="UIServer" asp-action="Branding" text-translate="true">Branding</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Translations" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Translations)" asp-action="ListDictionaries" text-translate="true">Translations</a>
<a layout-menu-item="@nameof(ServerNavPages.Translations)" asp-controller="UIServer" asp-action="ListDictionaries" text-translate="true">Translations</a>
</li>
@if (BtcPayServerOptions.DockerDeployment)
{
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Maintenance" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Maintenance)" asp-action="Maintenance" text-translate="true">Maintenance</a>
<a layout-menu-item="@nameof(ServerNavPages.Maintenance)" asp-controller="UIServer" asp-action="Maintenance" text-translate="true">Maintenance</a>
</li>
}
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Logs" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Logs)" asp-action="LogsView" text-translate="true">Logs</a>
<a layout-menu-item="@nameof(ServerNavPages.Logs)" asp-controller="UIServer" asp-action="LogsView" text-translate="true">Logs</a>
</li>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a asp-controller="UIServer" id="SectionNav-@ServerNavPages.Files" class="nav-link @ViewData.ActivePageClass(ServerNavPages.Files)" asp-action="Files" text-translate="true">Files</a>
<a layout-menu-item="@nameof(ServerNavPages.Files)" asp-controller="UIServer" asp-action="Files" text-translate="true">Files</a>
</li>
<vc:ui-extension-point location="server-nav" model="@Model"/>
}
<li class="nav-item dropup">
<a class="nav-link @ViewData.ActivePageClass(ManageNavPages.Index)" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" id="Nav-Account">
<a layout-menu-item="Account" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false">
<vc:icon symbol="nav-account"/>
<span text-translate="true">Account</span>
</a>
@@ -347,11 +355,11 @@
<strong class="d-block text-truncate" style="max-width:@(string.IsNullOrEmpty(Model.UserImageUrl) ? "195px" : "160px")">
@if (string.IsNullOrEmpty(Model.UserName))
{
@(User.Identity.Name)
@(User.Identity?.Name)
}
else
{
@($"{Model.UserName} ({User.Identity.Name})")
@($"{Model.UserName} ({User.Identity?.Name})")
}
</strong>
@if (User.IsInRole(Roles.ServerAdmin))
@@ -387,22 +395,22 @@
</li>
</ul>
</li>
@if (ViewData.IsCategoryActive(typeof(ManageNavPages)) || ViewData.IsPageActive([ManageNavPages.ChangePassword]))
@if (ViewData.IsCategory(nameof(ManageNavPages)))
{
<li class="nav-item nav-item-sub">
<a id="SectionNav-@ManageNavPages.ChangePassword.ToString()" class="nav-link @ViewData.ActivePageClass(ManageNavPages.ChangePassword)" asp-controller="UIManage" asp-action="ChangePassword" text-translate="true">Password</a>
<a layout-menu-item="@nameof(ManageNavPages.ChangePassword)" asp-controller="UIManage" asp-action="ChangePassword" text-translate="true">Password</a>
</li>
<li class="nav-item nav-item-sub">
<a id="SectionNav-@ManageNavPages.TwoFactorAuthentication.ToString()" class="nav-link @ViewData.ActivePageClass(ManageNavPages.TwoFactorAuthentication)" asp-controller="UIManage" asp-action="TwoFactorAuthentication" text-translate="true">Two-Factor Authentication</a>
<a layout-menu-item="@nameof(ManageNavPages.TwoFactorAuthentication)" asp-controller="UIManage" asp-action="TwoFactorAuthentication" text-translate="true">Two-Factor Authentication</a>
</li>
<li class="nav-item nav-item-sub">
<a id="SectionNav-@ManageNavPages.APIKeys.ToString()" class="nav-link @ViewData.ActivePageClass(ManageNavPages.APIKeys)" asp-controller="UIManage" asp-action="APIKeys" text-translate="true">API Keys</a>
<a layout-menu-item="@nameof(ManageNavPages.APIKeys)" asp-controller="UIManage" asp-action="APIKeys" text-translate="true">API Keys</a>
</li>
<li class="nav-item nav-item-sub">
<a id="SectionNav-@ManageNavPages.Notifications.ToString()" class="nav-link @ViewData.ActivePageClass(ManageNavPages.Notifications)" asp-controller="UIManage" asp-action="NotificationSettings" text-translate="true">Notifications</a>
<a layout-menu-item="@nameof(ManageNavPages.Notifications)" asp-controller="UIManage" asp-action="NotificationSettings" text-translate="true">Notifications</a>
</li>
<li class="nav-item nav-item-sub">
<a id="SectionNav-@ManageNavPages.LoginCodes.ToString()" class="nav-link @ViewData.ActivePageClass(ManageNavPages.LoginCodes)" asp-controller="UIManage" asp-action="LoginCodes" text-translate="true">Login Codes</a>
<a layout-menu-item="@nameof(ManageNavPages.LoginCodes)" asp-controller="UIManage" asp-action="LoginCodes" text-translate="true">Login Codes</a>
</li>
<vc:ui-extension-point location="user-nav" model="@Model" />
}

View File

@@ -51,23 +51,19 @@ else
@foreach (var option in Model.Options)
{
<li>
<a asp-controller="UIStores" asp-action="Index" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected && ViewData.ActivePageClass(ServerNavPages.Stores) != "active" ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
<a asp-controller="UIStores" asp-action="Index" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected && !ViewData.ContainsKey("StoreList"))" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
</li>
}
@if (Model.Options.Any())
{
<li><hr class="dropdown-divider"></li>
}
<li><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item @ViewData.ActivePageClass(StoreNavPages.Create)" id="StoreSelectorCreate" text-translate="true">Create Store</a></li>
<li><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item @(ViewData.ContainsKey("CreateStore") ? "active" : "")" id="StoreSelectorCreate" text-translate="true">Create Store</a></li>
@if (Model.ArchivedCount > 0)
{
<li><hr class="dropdown-divider"></li>
<li><a asp-controller="UIUserStores" asp-action="ListStores" asp-route-archived="true" class="dropdown-item @ViewData.ActivePageClass(StoreNavPages.Index)" id="StoreSelectorArchived">@(Model.ArchivedCount == 1 ? StringLocalizer["{0} Archived Store", Model.ArchivedCount] : StringLocalizer["{0} Archived Stores", Model.ArchivedCount])</a></li>
<li><a asp-controller="UIUserStores" asp-action="ListStores" asp-route-archived="true" class="dropdown-item @(ViewData.ContainsKey("ArchivedStores") ? "active" : "")" id="StoreSelectorArchived">@(Model.ArchivedCount == 1 ? StringLocalizer["{0} Archived Store", Model.ArchivedCount] : StringLocalizer["{0} Archived Stores", Model.ArchivedCount])</a></li>
}
@*
<li permission="@Policies.CanModifyServerSettings"><hr class="dropdown-divider"></li>
<li permission="@Policies.CanModifyServerSettings"><a asp-controller="UIServer" asp-action="ListStores" class="dropdown-item @ViewData.ActivePageClass(ServerNavPages.Stores)" id="StoreSelectorAdminStores" text-translate="true">Admin Store Overview</a></li>
*@
</ul>
</div>
</div>

View File

@@ -2,7 +2,8 @@
@{
var storeId = Context.GetStoreData().Id;
ViewData.SetActivePage(StoreNavPages.Emails, StringLocalizer["Email Rules"], storeId);
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Emails), StringLocalizer["Email Rules"])
.SetCategory(WellKnownCategories.Store));
}
<div class="sticky-header">
<nav aria-label="breadcrumb">

View File

@@ -3,7 +3,8 @@
@{
var storeId = Context.GetStoreData().Id;
bool isEdit = Model.Trigger != null;
ViewData.SetActivePage(StoreNavPages.Emails, StringLocalizer[isEdit ? "Edit Email Rule" : "Create Email Rule"], storeId);
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Emails), StringLocalizer[isEdit ? "Edit Email Rule" : "Create Email Rule"])
.SetCategory(WellKnownCategories.Store));
}
@section PageHeadContent {

View File

@@ -4,7 +4,8 @@
@model BTCPayServer.Models.EmailsViewModel
@{
var storeId = Context.GetStoreData().Id;
ViewData.SetActivePage(StoreNavPages.Emails, StringLocalizer["Email Rules"], storeId);
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Emails), StringLocalizer["Email Rules"])
.SetCategory(WellKnownCategories.Store));
}
<form method="post" autocomplete="off" permissioned="@Policies.CanModifyStoreSettings">

View File

@@ -79,7 +79,6 @@ namespace BTCPayServer.Plugins.PointOfSale.Models
[Display(Name = "Redirect invoice to redirect url automatically after paid")]
public string RedirectAutomatically { get; set; } = string.Empty;
public string AppId { get; set; }
public string SearchTerm { get; set; }
public SelectList RedirectAutomaticallySelectList =>

View File

@@ -45,7 +45,6 @@ public partial class UIOfferingController(
BTCPayServerEnvironment env,
DisplayFormatter displayFormatter,
EmailSenderFactory emailSenderFactory,
IHtmlHelper htmlHelper,
IEnumerable<EmailTriggerViewModel> emailTriggers
) : UISubscriptionControllerBase(dbContextFactory, linkGenerator, stringLocalizer, subsService)
{

View File

@@ -16,14 +16,14 @@
var appType = SubscriptionsAppType.AppType;
var apps = Model.Apps.Where(app => app.AppType == appType).ToList();
<li class="nav-item" permission="@Policies.CanModifyMembership">
<a asp-area="Subscriptions" asp- asp-controller="UIOffering" asp-action="CreateOffering" asp-route-storeId="@store.Id" class="nav-link @ViewData.ActivePageClass(AppsNavPages.Create, appType)" id="@($"StoreNav-Create{appType}")">
<a layout-menu-item="@nameof(SubscriptionsPlugin)" asp-area="Subscriptions" asp- asp-controller="UIOffering" asp-action="CreateOffering" asp-route-storeId="@store.Id">
<vc:icon symbol="nav-reporting" />
<span text-translate="true">Subscriptions</span>
</a>
</li>
@if (apps.Any())
{
<li class="nav-item" not-permission="@Policies.CanModifyMembership" permission="@Policies.CanViewStoreSettings">
<li layout-menu-item="@nameof(SubscriptionsPlugin)" not-permission="@Policies.CanModifyMembership" permission="@Policies.CanViewStoreSettings">
<span class="nav-link">
<vc:icon symbol="nav-reporting" />
<span text-translate="true">Subscriptions</span>
@@ -35,12 +35,12 @@
var offeringId = app.Data.GetSettings<SubscriptionsAppType.AppConfig>().OfferingId ?? "";
<li class="nav-item nav-item-sub" permission="@Policies.CanViewMembership">
<a asp-area="Subscriptions" asp-controller="UIOffering" asp-action="Offering" asp-route-storeId="@Model.Store.Id" asp-route-offeringId="@offeringId" asp-route-section="Plans" class="nav-link @ViewData.ActivePageClass(AppsNavPages.Update, @offeringId)" id="@($"StoreNav-Offering-{offeringId}")">
<a layout-menu-item="@nameof(SubscriptionsPlugin)-@offeringId" asp-area="Subscriptions" asp-controller="UIOffering" asp-action="Offering" asp-route-storeId="@Model.Store.Id" asp-route-offeringId="@offeringId" asp-route-section="Plans">
<span>@app.AppName</span>
</a>
</li>
<li class="nav-item nav-item-sub" not-permission="@Policies.CanViewMembership">
<a asp-area="Subscriptions" asp-controller="UIOffering" asp-action="Offering" asp-route-storeId="@Model.Store.Id" asp-route-offeringId="@offeringId" asp-route-section="Plans" class="nav-link">
<a layout-menu-item="@nameof(SubscriptionsPlugin)-@offeringId" asp-area="Subscriptions" asp-controller="UIOffering" asp-action="Offering" asp-route-storeId="@Model.Store.Id" asp-route-offeringId="@offeringId" asp-route-section="Plans" class="nav-link">
<span>@app.AppName</span>
</a>
</li>

View File

@@ -1,19 +1,12 @@
@model AddEditPlanViewModel
@using BTCPayServer.Plugins.Subscriptions
@model AddEditPlanViewModel
@{
string storeId = (string)this.Context.GetRouteValue("storeId");
string offeringId = (string)this.Context.GetRouteValue("offeringId");
string submitLabel = "";
if (Model.PlanId is null)
{
ViewData.SetActivePage(AppsNavPages.Update, StringLocalizer["Add plan"], offeringId);
submitLabel = StringLocalizer["Create"];
}
else
{
ViewData.SetActivePage(AppsNavPages.Update, StringLocalizer["Edit plan"], offeringId);
submitLabel = StringLocalizer["Save"];
}
var title = Model.PlanId is null ? StringLocalizer["Add plan"] : StringLocalizer["Edit plan"];
var submitLabel = Model.PlanId is null ? StringLocalizer["Create"] : StringLocalizer["Save"];
ViewData.SetLayoutModel(new LayoutModel($"{nameof(SubscriptionsPlugin)}-{offeringId}", title));
}
<form method="post">

View File

@@ -1,12 +1,12 @@

@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@using BTCPayServer.Controllers
@using BTCPayServer.Plugins.Subscriptions
@model ConfigureOfferingViewModel
@{
string offeringId = (string)this.Context.GetRouteValue("offeringId");
ViewData.SetActivePage(AppsNavPages.Update, StringLocalizer["Configure offering"], offeringId);
ViewData.SetLayoutModel(new LayoutModel($"{nameof(SubscriptionsPlugin)}-{offeringId}", StringLocalizer["Configure offering"]));
string storeId = (string)this.Context.GetRouteValue("storeId");
var deleteModal = new ConfirmModel(StringLocalizer["Delete offering"], StringLocalizer["This offering will be removed from this store."], StringLocalizer["Delete"])
{

View File

@@ -1,8 +1,9 @@
@using BTCPayServer.Services
@using BTCPayServer.Plugins.Subscriptions
@using BTCPayServer.Services
@model CreateOfferingViewModel
@{
ViewData.SetActivePage(StoreNavPages.Subscriptions, StringLocalizer["New offering"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(SubscriptionsPlugin), StringLocalizer["New offering"]));
}
<form method="post">

View File

@@ -1,7 +1,6 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@using BTCPayServer.Controllers
@using BTCPayServer.Client
@using BTCPayServer.Plugins.Emails
@using BTCPayServer.Plugins.Subscriptions
@using BTCPayServer.Plugins.Subscriptions.Controllers
@using BTCPayServer.Services
@model SubscriptionsViewModel
@@ -11,7 +10,7 @@
@{
string storeId = (string)this.Context.GetRouteValue("storeId");
string offeringId = (string)this.Context.GetRouteValue("offeringId");
ViewData.SetActivePage(AppsNavPages.Update, StringLocalizer["Subscriptions"], offeringId);
ViewData.SetLayoutModel(new LayoutModel($"{nameof(SubscriptionsPlugin)}-{offeringId}", StringLocalizer["Subscriptions"]));
Csp.UnsafeEval();
}

View File

@@ -2,7 +2,7 @@
@inject IEnumerable<AvailableWebhookViewModel> Webhooks
@{
var storeId = Context.GetStoreData().Id;
ViewData.SetActivePage(StoreNavPages.Webhooks, StringLocalizer["Webhook"], storeId);
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Webhooks), StringLocalizer["Webhooks"]).SetCategory(WellKnownCategories.Store));
}
@section PageHeadContent {

View File

@@ -1,8 +1,7 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@model WebhooksViewModel
@{
ViewData.SetActivePage(StoreNavPages.Webhooks, StringLocalizer["Webhooks"], Context.GetStoreData().Id);
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Webhooks), StringLocalizer["Webhooks"]).SetCategory(WellKnownCategories.Store));
}
<div class="sticky-header">

View File

@@ -13,10 +13,8 @@
var storeId = Context.GetRouteValue("storeId") as string;
var title = role is null ? StringLocalizer["Create role"] : StringLocalizer["Update Role"];
if (storeId is null)
ViewData.SetActivePage(ServerNavPages.Roles, title);
else
ViewData.SetActivePage(StoreNavPages.Roles, title, storeId);
var category = storeId is null ? WellKnownCategories.Server : WellKnownCategories.Store;
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Roles), title).SetCategory(category));
var storePolicies = Policies.AllPolicies.Where(Policies.IsStorePolicy).ToArray();
}
@@ -103,7 +101,7 @@
const { checked, value: policy } = element;
const policySelect = document.getElementById('Policies');
const subPolicies = element.parentElement.querySelectorAll(`.list-group .policy-cb:not([value="${policy}"])`);
policySelect.querySelector(`option[value="${policy}"]`).selected = checked;
subPolicies.forEach(subPolicy => {
subPolicy.checked = checked? false : subPolicy.checked;
@@ -115,12 +113,12 @@
policySelect.querySelector(`option[value="${subPolicy.value}"]`).selected = subPolicy.checked;
});
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll(".policy-cb:checked").forEach(handleCheckboxChange);
delegate('change', '.policy-cb', event => {
handleCheckboxChange(event.target);
});
});
});
</script>

View File

@@ -14,14 +14,14 @@
var appType = AppService.GetAppType(CrowdfundAppType.AppType)!;
var apps = Model.Apps.Where(app => app.AppType == appType.Type).ToList();
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type" class="nav-link @ViewData.ActivePageClass(AppsNavPages.Create, appType.Type)" id="@($"StoreNav-Create{appType.Type}")">
<a layout-menu-item="CreateApp-@appType.Type" asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type">
<vc:icon symbol="nav-crowdfund" />
<span text-translate="true">Crowdfund</span>
</a>
</li>
@if (apps.Any())
{
<li class="nav-item" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings">
<li layout-menu-item="CreateApp-@appType.Type" class="nav-item" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings">
<span class="nav-link">
<vc:icon symbol="nav-crowdfund" />
<span text-translate="true">Crowdfund</span>
@@ -31,12 +31,12 @@
@foreach (var app in apps)
{
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UICrowdfund" asp-action="UpdateCrowdfund" asp-route-appId="@app.Id" class="nav-link @ViewData.ActivePageClass(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
<a layout-menu-item="@nameof(CrowdfundPlugin)-@app.Id" asp-area="" asp-controller="UICrowdfund" asp-action="UpdateCrowdfund" asp-route-appId="@app.Id">
<span>@app.AppName</span>
</a>
</li>
<li class="nav-item nav-item-sub" not-permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UICrowdfund" asp-action="ViewCrowdfund" asp-route-appId="@app.Id" class="nav-link">
<a layout-menu-item="@nameof(CrowdfundPlugin)-@app.Id" asp-area="" asp-controller="UICrowdfund" asp-action="ViewCrowdfund" asp-route-appId="@app.Id">
<span>@app.AppName</span>
</a>
</li>

View File

@@ -1,17 +1,18 @@
@using System.Globalization
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@using BTCPayServer.TagHelpers
@using BTCPayServer.Views.Apps
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Forms
@using BTCPayServer.Plugins.Crowdfund
@inject FormDataService FormDataService
@inject IFileService FileService
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.Crowdfund.Models.UpdateCrowdfundViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, StringLocalizer["Update Crowdfund"], Model.AppId);
// layout-menu-item="@nameof(CrowdfundPlugin)-@app.Id"
ViewData.SetLayoutModel(new LayoutModel($"{nameof(CrowdfundPlugin)}-{Model.AppId}", StringLocalizer["Update Crowdfund"]));
Csp.UnsafeEval();
var canUpload = await FileService.IsAvailable();
var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId);

View File

@@ -8,7 +8,7 @@
@if (store.IsLightningEnabled(cryptoCode) && store.IsLNUrlEnabled(cryptoCode))
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UILNURL" asp-action="EditLightningAddress" asp-route-storeId="@store.Id" class="nav-link @ViewData.ActivePageClass("LightningAddress", nameof(StoreNavPages))" id="StoreNav-LightningAddress">
<a layout-menu-item="LightningAddress" asp-area="" asp-controller="UILNURL" asp-action="EditLightningAddress" asp-route-storeId="@store.Id">
<vc:icon symbol="nav-lightning-address "/>
<span text-translate="true">Lightning Address</span>
</a>

View File

@@ -6,10 +6,9 @@
@{
var storeId = Context.GetRouteValue("storeId") as string;
var controller = ViewContext.RouteData.Values["controller"].ToString().TrimEnd("Controller", StringComparison.InvariantCultureIgnoreCase);
if (string.IsNullOrEmpty(storeId))
ViewData.SetActivePage(ServerNavPages.Roles, StringLocalizer["Roles"]);
else
ViewData.SetActivePage(StoreNavPages.Roles, StringLocalizer["Roles"], storeId);
var category = string.IsNullOrEmpty(storeId) ? WellKnownCategories.Server : WellKnownCategories.Store;
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Roles), StringLocalizer["Roles"]).SetCategory(category));
var permission = string.IsNullOrEmpty(storeId) ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings;
var nextRoleSortOrder = (string) ViewData["NextRoleSortOrder"];
var roleSortOrder = nextRoleSortOrder switch

View File

@@ -2,7 +2,7 @@
@using BTCPayServer.Views.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@{
ViewData.SetActivePage(StoreNavPages.PayButton, StringLocalizer["Pay Button"], Context.GetStoreData().Id);
ViewData.SetLayoutModel(new(nameof(StoreNavPages.PayButton), StringLocalizer["Pay Button"]));
}
<div class="sticky-header">

View File

@@ -6,7 +6,7 @@
@if (store != null)
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIPayButton" asp-action="PayButton" asp-route-storeId="@store.Id" class="nav-link @ViewData.ActivePageClass(StoreNavPages.PayButton)" id="StoreNav-PayButton">
<a layout-menu-item="PayButton" asp-area="" asp-controller="UIPayButton" asp-action="PayButton" asp-route-storeId="@store.Id">
<vc:icon symbol="nav-pay-button"/>
<span text-translate="true">Pay Button</span>
</a>

View File

@@ -3,7 +3,7 @@
@inject BTCPayNetworkProvider NetworkProvider
@model BTCPayServer.Plugins.PayButton.Models.PayButtonViewModel
@{
ViewData.SetActivePage(StoreNavPages.PayButton, StringLocalizer["Pay Button"], Context.GetStoreData().Id);
ViewData.SetLayoutModel(new(nameof(StoreNavPages.PayButton), StringLocalizer["Pay Button"]));
Csp.UnsafeEval();
}
@@ -74,7 +74,7 @@
const max = parseInt(event.target.getAttribute('max'));
if (event.target.value < min) {
event.target.value = min;
} else if (event.target.value > max) {
} else if (event.target.value > max) {
event.target.value = max;
}
}
@@ -93,11 +93,11 @@
const price = parseInt(el.value);
const min = parseInt(event.target.getAttribute('min')) || 1;
const max = parseInt(event.target.getAttribute('max'));
if (price < min) {
if (price < min) {
el.value = min;
} else if (price > max) {
el.value = max;
}
}
root.querySelector('.btcpay-input-range').value = el.value;
}
function handleSliderInput(event) {
@@ -116,11 +116,11 @@
}
});
</template>
<script>
window.lnurlEndpoint = @Safe.Json(Url.Action("GetLNUrlForStore", "UILNURL", new
{
storeId = Model.StoreId,
storeId = Model.StoreId,
cryptoCode = NetworkProvider.DefaultNetwork.CryptoCode
}, "lnurlp", Context.Request.Host.ToString()));
const srvModel = @Safe.Json(Model);
@@ -145,7 +145,7 @@
light: '#f5f5f7'
}
}
},
computed: {
imageUrlRequired() {
@@ -245,7 +245,7 @@
<label for="buttonInlineTextMode" class="form-check-label" text-translate="true">Customize Pay Button Text</label>
</div>
</div>
<div class="form-group" v-show="buttonInlineTextMode">
<label class="form-label" for="pb-text" text-translate="true">Pay Button Text</label>
<input name="payButtonText" type="text" class="form-control" id="pb-text"
@@ -262,7 +262,7 @@
<div class="form-group mb-4">
<label class="form-label" text-translate="true">Image Size</label>
<div class="btn-group d-flex" role="group">
<button type="button" class="btn btn-outline-secondary"
<button type="button" class="btn btn-outline-secondary"
v-on:click="inputChanges($event, 0)">146 x 40 px</button>
<button type="button" class="btn btn-outline-secondary"
v-on:click="inputChanges($event, 1)">168 x 46 px</button>
@@ -419,7 +419,7 @@
Please fix errors shown in order for code generation to successfully execute.
</div>
</div>
<div v-if="!srvModel.appIdEndpoint && (previewLink || lnurlLink)">
<h4 class="mt-5 mb-3" text-translate="true">Alternatives</h4>
<p text-translate="true">You can also share the link/LNURL or encode it in a QR code.</p>
@@ -540,7 +540,7 @@
line-height: 35px;
background: #fff;
}
.btcpay-input-price::-webkit-outer-spin-button,
.btcpay-input-price::-webkit-inner-spin-button {
-webkit-appearance: none;

View File

@@ -14,14 +14,14 @@
var appType = AppService.GetAppType(PointOfSaleAppType.AppType)!;
var apps = Model.Apps.Where(app => app.AppType == appType.Type).ToList();
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type" class="nav-link @ViewData.ActivePageClass(AppsNavPages.Create, appType.Type)" id="@($"StoreNav-Create{appType.Type}")">
<a layout-menu-item="CreateApp-@appType.Type" asp-area="" asp-controller="UIApps" asp-action="CreateApp" asp-route-storeId="@store.Id" asp-route-appType="@appType.Type">
<vc:icon symbol="nav-pointofsale" />
<span text-translate="true">Point of Sale</span>
</a>
</li>
@if (apps.Any())
{
<li class="nav-item" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings">
<li layout-menu-item="CreateApp-@appType.Type" not-permission="@Policies.CanModifyStoreSettings" permission="@Policies.CanViewStoreSettings">
<span class="nav-link">
<vc:icon symbol="nav-pointofsale" />
<span text-translate="true">Point of Sale</span>
@@ -31,12 +31,12 @@
@foreach (var app in apps)
{
<li class="nav-item nav-item-sub" permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UIPointOfSale" asp-action="UpdatePointOfSale" asp-route-appId="@app.Id" class="nav-link @ViewData.ActivePageClass(AppsNavPages.Update, app.Id)" id="@($"StoreNav-App-{app.Id}")">
<a layout-menu-item="@nameof(PointOfSalePlugin)-@app.Id" asp-area="" asp-controller="UIPointOfSale" asp-action="UpdatePointOfSale" asp-route-appId="@app.Id">
<span>@app.AppName</span>
</a>
</li>
<li class="nav-item nav-item-sub" not-permission="@Policies.CanViewStoreSettings">
<a asp-area="" asp-controller="UIPointOfSale" asp-action="ViewPointOfSale" asp-route-appId="@app.Id" class="nav-link">
<a layout-menu-item="@nameof(PointOfSalePlugin)-@app.Id" asp-area="" asp-controller="UIPointOfSale" asp-action="ViewPointOfSale" asp-route-appId="@app.Id">
<span>@app.AppName</span>
</a>
</li>

View File

@@ -1,4 +1,3 @@
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Views.Apps
@using BTCPayServer.Client
@using BTCPayServer.Plugins.PointOfSale
@@ -9,7 +8,7 @@
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model BTCPayServer.Plugins.PointOfSale.Models.UpdatePointOfSaleViewModel
@{
ViewData.SetActivePage(AppsNavPages.Update, StringLocalizer["Update Point of Sale"], Model.Id);
ViewData.SetLayoutModel(new($"{nameof(PointOfSalePlugin)}-{Model.Id}",StringLocalizer["Update Point of Sale"]));
Csp.UnsafeEval();
var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId);
var posPath = Url.Action("ViewPointOfSale", "UIPointOfSale", new { appId = Model.Id });

View File

@@ -8,7 +8,7 @@
@if (store != null)
{
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<a asp-area="" asp-controller="UIShopify" asp-action="EditShopify" asp-route-storeId="@store.Id" class="nav-link @ViewData.ActivePageClass("shopify", nameof(StoreNavPages))" id="StoreNav-Shopify">
<a layout-menu-item="Shopify" asp-area="" asp-controller="UIShopify" asp-action="EditShopify" asp-route-storeId="@store.Id">
<vc:icon symbol="logo-shopify" />
<span text-translate="true">Shopify</span>
</a>

View File

@@ -1,6 +1,6 @@
@model CreateAppViewModel
@{
ViewData.SetActivePage(AppsNavPages.Create, StringLocalizer["Create a new {0}", Model.AppType]);
ViewData.SetLayoutModel(new($"CreateApp-{Model.AppType}", StringLocalizer["Create a new {0}", Model.AppType]));
}
@section PageFootContent {

View File

@@ -1,10 +1,9 @@
@using BTCPayServer.Services.Apps
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@model ListAppsViewModel
@inject AppService AppService
@{
ViewData.SetActivePage(AppsNavPages.Index, "Apps");
ViewData.SetLayoutModel(new(nameof(AppsNavPages), "Apps"));
var nextAppNameSortOrder = (string)ViewData["AppNameNextSortOrder"];
var nextAppTypeSortOrder = (string)ViewData["AppTypeNextSortOrder"];
var nextStoreNameSortOrder = (string)ViewData["StoreNameNextSortOrder"];

View File

@@ -1,7 +1,7 @@
@using Newtonsoft.Json.Linq
@model Fido2NetLib.CredentialCreateOptions
@{
ViewData.SetActivePage(ManageNavPages.TwoFactorAuthentication, StringLocalizer["Register your security device"]);
ViewData.SetLayoutModel(new(nameof(ManageNavPages.TwoFactorAuthentication), StringLocalizer["Register your security device"]));
}
<div class="sticky-header">

View File

@@ -1,9 +1,8 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@model List<BTCPayServer.Data.FormData>
@{
ViewData.SetActivePage(StoreNavPages.Forms, StringLocalizer["Forms"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.Forms), StringLocalizer["Forms"]).SetCategory(WellKnownCategories.Store));
var storeId = Context.GetCurrentStoreId();
}

View File

@@ -3,7 +3,7 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.TagHelpers
@{
ViewData.SetActivePage(InvoiceNavPages.Create, StringLocalizer["Create Invoice"]);
ViewData.SetLayoutModel(new("Invoices", StringLocalizer["Create Invoices"]));
}
@section PageFootContent {

View File

@@ -1,7 +1,7 @@
@using BTCPayServer.Client
@model InvoiceDetailsModel
@{
ViewData["Title"] = StringLocalizer["Invoice {0}", Model.Id];
ViewData.SetLayoutModel(new("Invoices", StringLocalizer["Invoice {0}", Model.Id]));
}
@section PageHeadContent {

View File

@@ -1,7 +0,0 @@
namespace BTCPayServer.Views.Invoice
{
public enum InvoiceNavPages
{
Index, Create
}
}

View File

@@ -5,7 +5,7 @@
@inject DisplayFormatter DisplayFormatter
@model InvoicesModel
@{
ViewData.SetActivePage(InvoiceNavPages.Index, StringLocalizer["Invoices"]);
ViewData.SetLayoutModel(new("Invoices", StringLocalizer["Invoices"]));
var statusFilterCount = CountArrayFilter("status") + CountArrayFilter("exceptionstatus") + (HasBooleanFilter("includearchived") ? 1 : 0) + (HasBooleanFilter("unusual") ? 1 : 0);
var hasDateFilter = HasArrayFilter("startdate") || HasArrayFilter("enddate");
var appFilterCount = Model.Apps.Count(app => HasArrayFilter("appid", app.Id));

View File

@@ -1,3 +1,2 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Services.Invoices
@using BTCPayServer.Views.Invoice

View File

@@ -1,6 +0,0 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Views
@using BTCPayServer.Views.Invoice
@{
ViewData.SetActiveCategory(typeof(InvoiceNavPages));
}

View File

@@ -1,8 +1,7 @@
@using BTCPayServer.Views.Stores
@using BTCPayServer.Abstractions.Models
@model UILNURLController.EditLightningAddressVM
@{
ViewData.SetActivePage("LightningAddress", nameof(StoreNavPages), StringLocalizer["Lightning Address"], Context.GetStoreData().Id);
ViewData.SetLayoutModel(new LayoutModel("LightningAddress", StringLocalizer["Lightning Address"]).SetCategory(WellKnownCategories.Store));
}
@section PageHeadContent {

View File

@@ -1,7 +1,7 @@
@using LNURL
@model Uri
@{
ViewData.SetActivePage(ManageNavPages.TwoFactorAuthentication, StringLocalizer["Register your Lightning node for LNURL Auth"]);
ViewData.SetLayoutModel(new(nameof(ManageNavPages.TwoFactorAuthentication), StringLocalizer["Register your Lightning node for LNURL Auth"]));
var formats = new Dictionary<string, string>
{
{ "Bech32", LNURL.EncodeUri(Model, "login", true).ToString().ToUpperInvariant() },

View File

@@ -4,7 +4,7 @@
@model BTCPayServer.PayoutProcessors.Lightning.UILightningAutomatedPayoutProcessorsController.LightningTransferViewModel
@{
var storeId = Context.GetStoreData().Id;
ViewData.SetActivePage(StoreNavPages.PayoutProcessors, StringLocalizer["Lightning Payout Processor"], storeId);
ViewData.SetLayoutModel(new(nameof(StoreNavPages.PayoutProcessors), StringLocalizer["Lightning Payout Processor"]));
}
<form method="post" permissioned="@Policies.CanModifyStoreSettings">

View File

@@ -1,5 +1,4 @@
@namespace BTCPayServer.Client
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.TagHelpers
@using Microsoft.AspNetCore.Html
@@ -7,7 +6,8 @@
@inject Security.ContentSecurityPolicies Csp
@model BTCPayServer.Controllers.UIManageController.ApiKeysViewModel
@{
ViewData.SetActivePage(ManageNavPages.APIKeys, StringLocalizer["API Keys"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.APIKeys), StringLocalizer["API Keys"])
.SetCategory(nameof(ManageNavPages)));
Csp.UnsafeEval();
}

View File

@@ -3,7 +3,8 @@
@model UIManageController.AddApiKeyViewModel
@{
ViewData.SetActivePage(ManageNavPages.APIKeys, StringLocalizer["Generate API Key"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.APIKeys), StringLocalizer["Generate API Key"])
.SetCategory(nameof(ManageNavPages)));
}
@section PageHeadContent {
@@ -50,7 +51,7 @@
<input asp-for="Label" class="form-control"/>
<span asp-validation-for="Label" class="text-danger"></span>
</div>
<h5 class="mt-4 mb-3">Permissions</h5>
<div class="list-group mb-4">
@for (int i = 0; i < Model.PermissionValues.Count; i++)
@@ -92,7 +93,7 @@
</h5>
<div class="text-muted">@Model.PermissionValues[i].Description</div>
<button type="submit" class="btn btn-link p-0" name="command" value="@($"{Model.PermissionValues[i].Permission}:change-store-mode")">Give permission to all stores instead</button>
@if (!Model.Stores.Any())
{
<p class="info-note text-warning mt-2 mb-0">
@@ -138,7 +139,7 @@
<span asp-validation-for="PermissionValues[i].Value" class="text-danger"></span>
<span class="text-muted">@Model.PermissionValues[i].Description</span>
</div>
</div>
</div>
}
</div>
}

View File

@@ -6,7 +6,7 @@
var store = string.IsNullOrEmpty(Model.StoreId) ? null : Model.Stores.FirstOrDefault(s => s.Id == Model.StoreId);
var permissions = Model.Permissions?.Split(';') ?? Array.Empty<string>();
var groupedPermissions = Permission.ToPermissions(permissions).GroupBy(permission => permission.Policy);
ViewData["Title"] = $"Authorize {displayName ?? "Application"}";
ViewData.SetTitle($"Authorize {displayName ?? "Application"}");
Layout = "_LayoutWizard";
}
@@ -73,20 +73,20 @@
else
{
<input type="hidden" asp-for="StoreId" class="form-select"/>
@if (Model.RedirectUrl != null)
{
<p class="alert alert-info mb-4">
<span text-translate="true">If authorized, the generated API key will be provided to</span> <strong>@Model.RedirectUrl.AbsoluteUri</strong>
</p>
}
<div class="form-group">
<label asp-for="Label" class="form-label"></label>
<input asp-for="Label" class="form-control"/>
<span asp-validation-for="Label" class="text-danger"></span>
</div>
<h2 class="h5 fw-semibold mt-4" text-translate="true">Permissions</h2>
@if (!groupedPermissions.Any())
{

View File

@@ -1,6 +1,7 @@
@model ChangePasswordViewModel
@{
ViewData.SetActivePage(ManageNavPages.ChangePassword, StringLocalizer["Change your password"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.ChangePassword), StringLocalizer["Change your password"])
.SetCategory(nameof(ManageNavPages)));
}
<form method="post">

View File

@@ -2,7 +2,7 @@
@{
var displayName = Model.ApplicationName ?? Model.ApplicationIdentifier;
ViewData["Title"] = $"Authorize {displayName ?? "Application"}";
ViewData.SetTitle($"Authorize {displayName ?? "Application"}");
Layout = "_LayoutWizard";
}

View File

@@ -5,7 +5,8 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model EnableAuthenticatorViewModel
@{
ViewData.SetActivePage(ManageNavPages.TwoFactorAuthentication, StringLocalizer["Enable Authenticator App"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.TwoFactorAuthentication), StringLocalizer["Enable Authenticator App"])
.SetCategory(nameof(ManageNavPages)));
}
<div class="sticky-header">
<nav aria-label="breadcrumb">
@@ -59,7 +60,7 @@
<span text-translate="true">Your two-factor authenticator app will provide you with a unique code.</span>
<br/>
<span text-translate="true">Enter the code in the confirmation box below.</span>
</p>
</p>
<form method="post">
<input asp-for="AuthenticatorUri" type="hidden" />
<input asp-for="SharedKey" type="hidden" />

View File

@@ -1,6 +1,7 @@
@model GenerateRecoveryCodesViewModel
@{
ViewData.SetActivePage(ManageNavPages.TwoFactorAuthentication, StringLocalizer["Recovery codes"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.TwoFactorAuthentication), StringLocalizer["Recovery codes"])
.SetCategory(nameof(ManageNavPages)));
}
<h2 class="mb-2 mb-lg-3">@ViewData["Title"]</h2>

View File

@@ -1,10 +1,10 @@
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Models
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject IFileService FileService
@model IndexViewModel
@{
ViewData.SetActivePage(ManageNavPages.Index, StringLocalizer["Update your account"]);
ViewData.SetLayoutModel(new LayoutModel("Account", StringLocalizer["Update your account"])
.SetCategory(nameof(ManageNavPages)));
var canUpload = await FileService.IsAvailable();
}

View File

@@ -1,6 +1,7 @@
@inject UserManager<ApplicationUser> UserManager;
@{
ViewData.SetActivePage(ManageNavPages.LoginCodes, StringLocalizer["Login Codes"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.LoginCodes), StringLocalizer["Login Codes"])
.SetCategory(nameof(ManageNavPages)));
}
<div class="sticky-header">

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Controllers.UIManageController.NotificationSettingsViewModel
@{
ViewData.SetActivePage(ManageNavPages.Notifications, StringLocalizer["Notification Settings"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.Notifications), StringLocalizer["Notification Settings"])
.SetCategory(nameof(ManageNavPages)));
}
<form method="post" asp-action="NotificationSettings">

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Models.ManageViewModels.SetPasswordViewModel
@{
ViewData.SetActivePage(ManageNavPages.ChangePassword, StringLocalizer["Set your password"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.ChangePassword), StringLocalizer["Set your password"])
.SetCategory(nameof(ManageNavPages)));
}
<form method="post">

View File

@@ -1,7 +1,7 @@
@using BTCPayServer.Abstractions.Models
@model TwoFactorAuthenticationViewModel
@{
ViewData.SetActivePage(ManageNavPages.TwoFactorAuthentication, StringLocalizer["Two-Factor Authentication"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ManageNavPages.TwoFactorAuthentication), StringLocalizer["Two-Factor Authentication"])
.SetCategory(nameof(ManageNavPages)));
}
<div class="sticky-header">
<h2 class="my-1">@ViewData["Title"]</h2>

View File

@@ -5,7 +5,7 @@
@{
var storeId = Context.GetStoreData().Id;
var cryptoCode = Context.GetRouteValue("cryptocode")?.ToString();
ViewData.SetActivePage(StoreNavPages.PayoutProcessors, StringLocalizer["On-Chain Payout Processor"], storeId);
ViewData.SetLayoutModel(new(nameof(StoreNavPages.PayoutProcessors), StringLocalizer["On-Chain Payout Processor"]));
}
<form method="post" permissioned="@Policies.CanModifyStoreSettings">

View File

@@ -11,8 +11,7 @@
@model BTCPayServer.Models.PaymentRequestViewModels.UpdatePaymentRequestViewModel
@{
var checkoutFormOptions = await FormDataService.GetSelect(Model.StoreId, Model.FormId);
ViewData.SetActivePage(PaymentRequestsNavPages.Create, string.IsNullOrEmpty(Model.Id) ? StringLocalizer["Create Payment Request"] : StringLocalizer["Edit Payment Request"], Model.Id);
ViewData.SetLayoutModel(new LayoutModel("PaymentRequests", string.IsNullOrEmpty(Model.Id) ? StringLocalizer["Create Payment Request"] : StringLocalizer["Edit Payment Request"]));
}
@section PageHeadContent {

View File

@@ -9,7 +9,7 @@
@model BTCPayServer.Models.PaymentRequestViewModels.ListPaymentRequestsViewModel
@{
Layout = "_Layout";
ViewData["Title"] = ViewLocalizer["Payment Requests"];
ViewData.SetLayoutModel(new("PaymentRequests", StringLocalizer["Payment Requests"]));
var storeId = Context.GetStoreData().Id;
var statusFilterCount = CountArrayFilter("status") + (HasBooleanFilter("includearchived") ? 1 : 0);
}

View File

@@ -1,7 +0,0 @@
namespace BTCPayServer.Views.PaymentRequest
{
public enum PaymentRequestsNavPages
{
Index, Create
}
}

View File

@@ -1,2 +1 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Views.PaymentRequest

View File

@@ -1,6 +0,0 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Views
@using BTCPayServer.Views.PaymentRequest
@{
ViewData.SetActiveCategory(typeof(PaymentRequestsNavPages));
}

View File

@@ -1,11 +1,10 @@
@using BTCPayServer.Views.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Abstractions.Models
@using BTCPayServer.Client
@model List<BTCPayServer.PayoutProcessors.UIPayoutProcessorsController.StorePayoutProcessorsView>
@{
var storeId = Context.GetStoreData().Id;
ViewData.SetActivePage(StoreNavPages.PayoutProcessors, StringLocalizer["Payout Processors"], storeId);
ViewData.SetLayoutModel(new LayoutModel(nameof(StoreNavPages.PayoutProcessors), StringLocalizer["Payout Processors"]).SetCategory(WellKnownCategories.Store));
}
<div class="sticky-header">
<h2 class="my-1">@ViewData["Title"]</h2>

View File

@@ -2,7 +2,7 @@
@model BTCPayServer.Models.WalletViewModels.UpdatePullPaymentModel
@{
var storeId = Context.GetStoreData().Id;
ViewData.SetActivePage(StoreNavPages.Create, StringLocalizer["Edit Pull Payment"], Model.Id);
ViewData.SetLayoutModel(new(nameof(StoreNavPages.Create), StringLocalizer["Edit Pull Payment"]));
}
@section PageHeadContent {

View File

@@ -4,7 +4,7 @@
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model StoreReportsViewModel
@{
ViewData.SetActivePage(StoreNavPages.Reporting, StringLocalizer["Reporting"]);
ViewData.SetLayoutModel(new(nameof(StoreNavPages.Reporting), StringLocalizer["Reporting"]));
Csp.UnsafeEval();
}

View File

@@ -3,7 +3,8 @@
@model BrandingViewModel;
@inject IFileService FileService
@{
ViewData.SetActivePage(ServerNavPages.Branding, StringLocalizer["Branding"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Branding), StringLocalizer["Branding"])
.SetCategory(WellKnownCategories.Server));
var canUpload = await FileService.IsAvailable();
var themeExtension = ((ThemeExtension[])Enum.GetValues(typeof(ThemeExtension))).Select(t =>
new SelectListItem(typeof(ThemeExtension).DisplayName(t.ToString()), t == ThemeExtension.Custom ? null : t.ToString()));
@@ -65,7 +66,7 @@
<h3 class="mt-5 mb-3" text-translate="true">Theme</h3>
<p text-translate="true">Use the default Light or Dark Themes, or provide a custom CSS theme file below.</p>
<div class="d-flex align-items-center mb-3">
<input asp-for="CustomTheme" type="checkbox" class="btcpay-toggle me-3" data-bs-toggle="collapse" data-bs-target="#CustomThemeSettings" aria-expanded="@(Model.CustomTheme)" aria-controls="CustomThemeSettings" />
<div>

View File

@@ -1,6 +1,7 @@
@model LndServicesViewModel
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["Core Lightning {0}", Model.ConnectionType]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["Core Lightning {0}", Model.ConnectionType])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,6 @@
@model LightningWalletServices
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["BTCPay Server Configurator"]);
ViewData.SetLayoutModel(new(nameof(ServerNavPages.Services), StringLocalizer["BTCPay Server Configurator"]));
}
@@ -28,7 +28,7 @@
<div class="form-group">
<p text-translate="true">This page exposes information to use the configured BTCPay Server Configurator to modify this setup.</p>
</div>
<a href="@Model.ServiceLink" target="_blank" class="form-group" rel="noreferrer noopener">
<label asp-for="ServiceLink" class="form-label" text-translate="true">Service</label>
<input asp-for="ServiceLink" class="form-control" readonly />

View File

@@ -1,6 +1,7 @@
@model CreateDictionaryViewModel
@{
ViewData.SetActivePage(ServerNavPages.Translations, "Create a new dictionary");
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Translations), "Create a new dictionary")
.SetCategory(WellKnownCategories.Server));
}
<form method="post">
<div class="sticky-header">

View File

@@ -1,7 +1,8 @@
@using BTCPayServer.Controllers
@model BTCPayServer.Controllers.UIServerController.CreateTemporaryFileUrlViewModel
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["Create temporary file link"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["Create temporary file link"])
.SetCategory(WellKnownCategories.Server));
}
<form method="post">

View File

@@ -2,7 +2,7 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Controllers.RegisterFromAdminViewModel
@{
ViewData.SetActivePage(ServerNavPages.Users, StringLocalizer["Create account"]);
ViewData.SetLayoutModel(new(nameof(ServerNavPages.Users), StringLocalizer["Create account"]));
var canSendEmail = ViewData["CanSendEmail"] is true;
}

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Models.ServerViewModels.DynamicDnsViewModel
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["Dynamic DNS Service"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["Dynamic DNS Service"])
.SetCategory(WellKnownCategories.Server));
}
@section PageFootContent {

View File

@@ -1,7 +1,7 @@
@using BTCPayServer.Abstractions.Models
@model BTCPayServer.Models.ServerViewModels.DynamicDnsViewModel[]
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["Dynamic DNS Settings"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["Dynamic DNS Settings"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">
<nav aria-label="breadcrumb">

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Storage.Services.Providers.AmazonS3Storage.Configuration.AmazonS3StorageConfiguration
@{
ViewData.SetActivePage(ServerNavPages.Files, StringLocalizer["Amazon S3 Storage"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Files), StringLocalizer["Amazon S3 Storage"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration.AzureBlobStorageConfiguration
@{
ViewData.SetActivePage(ServerNavPages.Files, StringLocalizer["Azure Blob Storage"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Files), StringLocalizer["Azure Blob Storage"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,7 +1,7 @@
@model EditDictionaryViewModel
@{
ViewData.SetActivePage(ServerNavPages.Translations);
ViewData["Title"] = Context.GetRouteValue("dictionary");
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Translations), Context.GetRouteValue("dictionary") as string)
.SetCategory(WellKnownCategories.Server));
}
<form method="post" class="d-flex flex-column">

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration.FileSystemStorageConfiguration
@{
ViewData.SetActivePage(ServerNavPages.Files, StringLocalizer["Local Filesystem Storage"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Files), StringLocalizer["Local Filesystem Storage"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration.GoogleCloudStorageConfiguration
@{
ViewData.SetActivePage(ServerNavPages.Files, StringLocalizer["Google Cloud Storage"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Files), StringLocalizer["Google Cloud Storage"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,6 @@
@model ServerEmailsViewModel
@{
ViewData.SetActivePage(ServerNavPages.Emails, StringLocalizer["Emails"]);
ViewData.SetLayoutModel(new LayoutModel("Server-" + nameof(ServerNavPages.Emails), StringLocalizer["Emails"]).SetCategory(WellKnownCategories.Server));
}
<form method="post" autocomplete="off">

View File

@@ -1,6 +1,7 @@
@model ViewFilesViewModel
@{
ViewData.SetActivePage(ServerNavPages.Files, StringLocalizer["File Storage"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Files), StringLocalizer["File Storage"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,7 @@
@model ChargeServiceViewModel
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["Lightning Charge Service"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["Lightning Charge Service"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,7 @@
@model LightningWalletServices
@{
ViewData.SetActivePage(ServerNavPages.Services, Model.WalletName);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), Model.WalletName)
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,7 +1,7 @@
@using BTCPayServer.Abstractions.Models
@model ListDictionariesViewModel
@{
ViewData.SetActivePage(ServerNavPages.Translations, StringLocalizer["Dictionaries"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Translations), StringLocalizer["Dictionaries"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -5,7 +5,7 @@
@inject PluginService PluginService
@{
Layout = "_Layout";
ViewData.SetActivePage(ServerNavPages.Plugins);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Plugins), StringLocalizer["Plugins"]));
var installed = Model.Installed;
var availableAndNotInstalled = new List<PluginService.AvailablePlugin>();
var availableAndNotInstalledx = Model.Available

View File

@@ -1,7 +1,8 @@
@model BTCPayServer.Models.StoreViewModels.ListStoresViewModel
@{
Layout = "_Layout";
ViewData.SetActivePage(ServerNavPages.Stores, StringLocalizer["Store Overview"]);
ViewData.SetTitle(StringLocalizer["Store Overview"]);
ViewData["StoreList"] = true;
}
<div class="sticky-header">
<h2 class="my-1">@ViewData["Title"]</h2>
@@ -60,7 +61,7 @@
}
else
{
<span class="text-secondary" text-translate="true">No users</span>
<span class="text-secondary" text-translate="true">No users</span>
}
</td>
</tr>

View File

@@ -1,8 +1,8 @@
@using BTCPayServer.Abstractions.Models
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
@model UsersViewModel
@{
ViewData.SetActivePage(ServerNavPages.Users, StringLocalizer["Users"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Users), StringLocalizer["Users"])
.SetCategory(WellKnownCategories.Server));
var nextUserEmailSortOrder = (string)ViewData["NextUserEmailSortOrder"];
var userEmailSortOrder = nextUserEmailSortOrder switch
{

View File

@@ -1,7 +1,7 @@
@using BTCPayServer.Abstractions.Models
@model LndSeedBackupViewModel
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["LND Seed Backup"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["LND Seed Backup"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">
@@ -26,7 +26,7 @@
<p>The recovering process is documented by LND on <a href="https://github.com/lightningnetwork/lnd/blob/master/docs/recovery.md" rel="noreferrer noopener">this page</a>.</p>
</div>
<button class="btn btn-primary @(Model.Removed ? "collapse" : "")" id="details" type="button">See confidential seed information</button>
@if (Model.Removed)
{
<div class="alert alert-light d-flex align-items-center" role="alert">

View File

@@ -1,6 +1,7 @@
@model LndServicesViewModel
@{
ViewData.SetActivePage(ServerNavPages.Services, StringLocalizer["LND {0}", Model.ConnectionType]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), StringLocalizer["LND {0}", Model.ConnectionType])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,7 @@
@model BTCPayServer.Models.ServerViewModels.LogsViewModel
@{
ViewData.SetActivePage(ServerNavPages.Logs, StringLocalizer["Logs"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Logs), StringLocalizer["Logs"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -3,7 +3,8 @@
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Models.ServerViewModels.MaintenanceViewModel
@{
ViewData.SetActivePage(ServerNavPages.Maintenance, StringLocalizer["Maintenance"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Maintenance), StringLocalizer["Maintenance"])
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,7 @@
@model LightningWalletServices
@{
ViewData.SetActivePage(ServerNavPages.Services, Model.WalletName);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), Model.WalletName)
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -4,7 +4,8 @@
@inject EmailSenderFactory EmailSenderFactory
@inject TransactionLinkProviders TransactionLinkProviders
@{
ViewData.SetActivePage(ServerNavPages.Policies, StringLocalizer["Policies"]);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Policies), StringLocalizer["Policies"])
.SetCategory(WellKnownCategories.Server));
var linkProviders = TransactionLinkProviders.ToArray();
}

View File

@@ -1,6 +1,7 @@
@model LightningWalletServices
@{
ViewData.SetActivePage(ServerNavPages.Services, Model.WalletName);
ViewData.SetLayoutModel(new LayoutModel(nameof(ServerNavPages.Services), Model.WalletName)
.SetCategory(WellKnownCategories.Server));
}
<div class="sticky-header">

View File

@@ -1,6 +1,6 @@
@model BTCPayServer.Controllers.ResetUserPasswordFromAdmin
@{
ViewData.SetActivePage(ServerNavPages.Users, StringLocalizer["Reset Password"]);
ViewData.SetLayoutModel(new(nameof(ServerNavPages.Users), StringLocalizer["Reset Password"]));
}
<form method="post" asp-action="ResetUserPassword">

Some files were not shown because too many files have changed in this diff Show More