mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Archive stores and apps (#5296)
* Add flags and migration * Archive store * Archive apps
This commit is contained in:
@@ -37,6 +37,7 @@ namespace BTCPayServer.Client.Models
|
||||
public string RedirectUrl { get; set; } = null;
|
||||
public bool? RedirectAutomatically { get; set; } = null;
|
||||
public bool? RequiresRefundEmail { get; set; } = null;
|
||||
public bool? Archived { get; set; } = null;
|
||||
public string FormId { get; set; } = null;
|
||||
public string EmbeddedCSS { get; set; } = null;
|
||||
public CheckoutType? CheckoutType { get; set; } = null;
|
||||
@@ -78,6 +79,7 @@ namespace BTCPayServer.Client.Models
|
||||
public bool? DisplayPerksValue { get; set; } = null;
|
||||
public bool? DisplayPerksRanking { get; set; } = null;
|
||||
public bool? SortPerksByPopularity { get; set; } = null;
|
||||
public bool? Archived { get; set; } = null;
|
||||
public string[] Sounds { get; set; } = null;
|
||||
public string[] AnimationColors { get; set; } = null;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace BTCPayServer.Client.Models
|
||||
public string AppType { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool? Archived { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ namespace BTCPayServer.Client.Models
|
||||
public bool LazyPaymentMethods { get; set; }
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool Archived { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool ShowRecommendedFee { get; set; } = true;
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace BTCPayServer.Data
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public bool TagAllInvoices { get; set; }
|
||||
public string Settings { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace BTCPayServer.Data
|
||||
public IEnumerable<StoreSettingData> Settings { get; set; }
|
||||
public IEnumerable<FormData> Forms { get; set; }
|
||||
public IEnumerable<StoreRole> StoreRoles { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
|
||||
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using BTCPayServer.Data;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace BTCPayServer.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20230906135844_AddArchivedFlagForStoresAndApps")]
|
||||
public partial class AddArchivedFlagForStoresAndApps : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Archived",
|
||||
table: "Stores",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Archived",
|
||||
table: "Apps",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Archived",
|
||||
table: "Stores");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Archived",
|
||||
table: "Apps");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("AppType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Archived")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -751,6 +754,9 @@ namespace BTCPayServer.Migrations
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Archived")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DefaultCrypto")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="114.0.5735.9000" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="116.0.5845.9600" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -54,13 +54,33 @@ namespace BTCPayServer.Tests
|
||||
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
|
||||
Assert.Single(appList.Apps);
|
||||
Assert.Empty(appList2.Apps);
|
||||
Assert.Equal("test", appList.Apps[0].AppName);
|
||||
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
|
||||
Assert.True(appList.Apps[0].Role.ToPermissionSet(appList.Apps[0].StoreId).Contains(Policies.CanModifyStoreSettings, appList.Apps[0].StoreId));
|
||||
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
|
||||
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id));
|
||||
Assert.IsType<ViewResult>(apps.DeleteApp(appList.Apps[0].Id));
|
||||
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(appList.Apps[0].Id).Result);
|
||||
Assert.Equal("test", app.AppName);
|
||||
Assert.Equal(apps.CreatedAppId, app.Id);
|
||||
Assert.True(app.Role.ToPermissionSet(app.StoreId).Contains(Policies.CanModifyStoreSettings, app.StoreId));
|
||||
Assert.Equal(user.StoreId, app.StoreId);
|
||||
// Archive
|
||||
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
|
||||
Assert.EndsWith("/settings/crowdfund", redirect.Url);
|
||||
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
Assert.Empty(appList.Apps);
|
||||
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId, archived: true).Result).Model);
|
||||
app = appList.Apps[0];
|
||||
Assert.True(app.Archived);
|
||||
Assert.IsType<NotFoundResult>(await crowdfund.ViewCrowdfund(app.Id));
|
||||
// Unarchive
|
||||
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
|
||||
Assert.EndsWith("/settings/crowdfund", redirect.Url);
|
||||
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
app = appList.Apps[0];
|
||||
Assert.False(app.Archived);
|
||||
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
|
||||
crowdfundViewModel.Enabled = true;
|
||||
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
|
||||
Assert.IsType<ViewResult>(await crowdfund.ViewCrowdfund(app.Id));
|
||||
// Delete
|
||||
Assert.IsType<NotFoundResult>(apps2.DeleteApp(app.Id));
|
||||
Assert.IsType<ViewResult>(apps.DeleteApp(app.Id));
|
||||
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(app.Id).Result);
|
||||
Assert.Equal(nameof(UIStoresController.Dashboard), redirectToAction.ActionName);
|
||||
appList = await apps.ListApps(user.StoreId).AssertViewModelAsync<ListAppsViewModel>();
|
||||
Assert.Empty(appList.Apps);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
@@ -16,8 +15,6 @@ using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.PayoutProcessors;
|
||||
using BTCPayServer.PayoutProcessors.OnChain;
|
||||
using BTCPayServer.Plugins;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Custodian.Client.MockCustodian;
|
||||
using BTCPayServer.Services.Notifications;
|
||||
@@ -25,7 +22,6 @@ using BTCPayServer.Services.Notifications.Blobs;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
@@ -301,6 +297,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(user.StoreId, app.StoreId);
|
||||
Assert.Equal("PointOfSale", app.AppType);
|
||||
Assert.Equal("test app title", app.Title);
|
||||
Assert.False(app.Archived);
|
||||
|
||||
// Make sure we return a 404 if we try to get an app that doesn't exist
|
||||
await AssertHttpError(404, async () =>
|
||||
@@ -324,17 +321,20 @@ namespace BTCPayServer.Tests
|
||||
new CreatePointOfSaleAppRequest()
|
||||
{
|
||||
AppName = "new app name",
|
||||
Title = "new app title"
|
||||
Title = "new app title",
|
||||
Archived = true
|
||||
}
|
||||
);
|
||||
// Test generic GET app endpoint first
|
||||
retrievedApp = await client.GetApp(app.Id);
|
||||
Assert.Equal("new app name", retrievedApp.Name);
|
||||
Assert.True(retrievedApp.Archived);
|
||||
|
||||
// Test the POS-specific endpoint also
|
||||
var retrievedPosApp = await client.GetPosApp(app.Id);
|
||||
Assert.Equal("new app name", retrievedPosApp.Name);
|
||||
Assert.Equal("new app title", retrievedPosApp.Title);
|
||||
Assert.True(retrievedPosApp.Archived);
|
||||
|
||||
// Make sure we return a 404 if we try to delete an app that doesn't exist
|
||||
await AssertHttpError(404, async () =>
|
||||
@@ -466,6 +466,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("test app from API", app.Name);
|
||||
Assert.Equal(user.StoreId, app.StoreId);
|
||||
Assert.Equal("Crowdfund", app.AppType);
|
||||
Assert.False(app.Archived);
|
||||
|
||||
// Make sure we return a 404 if we try to get an app that doesn't exist
|
||||
await AssertHttpError(404, async () =>
|
||||
@@ -482,11 +483,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(app.Name, retrievedApp.Name);
|
||||
Assert.Equal(app.StoreId, retrievedApp.StoreId);
|
||||
Assert.Equal(app.AppType, retrievedApp.AppType);
|
||||
Assert.False(retrievedApp.Archived);
|
||||
|
||||
// Test the crowdfund-specific endpoint also
|
||||
var retrievedPosApp = await client.GetCrowdfundApp(app.Id);
|
||||
Assert.Equal(app.Name, retrievedPosApp.Name);
|
||||
Assert.Equal(app.Title, retrievedPosApp.Title);
|
||||
var retrievedCfApp = await client.GetCrowdfundApp(app.Id);
|
||||
Assert.Equal(app.Name, retrievedCfApp.Name);
|
||||
Assert.Equal(app.Title, retrievedCfApp.Title);
|
||||
Assert.False(retrievedCfApp.Archived);
|
||||
|
||||
// Make sure we return a 404 if we try to delete an app that doesn't exist
|
||||
await AssertHttpError(404, async () =>
|
||||
@@ -536,10 +539,12 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(posApp.Name, apps[0].Name);
|
||||
Assert.Equal(posApp.StoreId, apps[0].StoreId);
|
||||
Assert.Equal(posApp.AppType, apps[0].AppType);
|
||||
Assert.False(apps[0].Archived);
|
||||
|
||||
Assert.Equal(crowdfundApp.Name, apps[1].Name);
|
||||
Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId);
|
||||
Assert.Equal(crowdfundApp.AppType, apps[1].AppType);
|
||||
Assert.False(apps[1].Archived);
|
||||
|
||||
// Get all apps for all store now
|
||||
apps = await client.GetAllApps();
|
||||
@@ -549,15 +554,17 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(posApp.Name, apps[0].Name);
|
||||
Assert.Equal(posApp.StoreId, apps[0].StoreId);
|
||||
Assert.Equal(posApp.AppType, apps[0].AppType);
|
||||
Assert.False(apps[0].Archived);
|
||||
|
||||
Assert.Equal(crowdfundApp.Name, apps[1].Name);
|
||||
Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId);
|
||||
Assert.Equal(crowdfundApp.AppType, apps[1].AppType);
|
||||
Assert.False(apps[1].Archived);
|
||||
|
||||
Assert.Equal(newApp.Name, apps[2].Name);
|
||||
Assert.Equal(newApp.StoreId, apps[2].StoreId);
|
||||
Assert.Equal(newApp.AppType, apps[2].AppType);
|
||||
|
||||
Assert.False(apps[2].Archived);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@@ -1272,7 +1279,7 @@ namespace BTCPayServer.Tests
|
||||
using var tester = CreateServerTester();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
await user.GrantAccessAsync();
|
||||
await user.MakeAdmin();
|
||||
var client = await user.CreateClient(Policies.Unrestricted);
|
||||
|
||||
@@ -1351,6 +1358,13 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
tester.DeleteStore = false;
|
||||
Assert.Empty(await client.GetStores());
|
||||
|
||||
// Archive
|
||||
var archivableStore = await client.CreateStore(new CreateStoreRequest { Name = "Archivable" });
|
||||
Assert.False(archivableStore.Archived);
|
||||
archivableStore = await client.UpdateStore(archivableStore.Id, new UpdateStoreRequest { Name = "Archived", Archived = true });
|
||||
Assert.Equal("Archived", archivableStore.Name);
|
||||
Assert.True(archivableStore.Archived);
|
||||
}
|
||||
|
||||
private async Task<GreenfieldValidationException> AssertValidationError(string[] fields, Func<Task> act)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hosting;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale;
|
||||
using BTCPayServer.Plugins.PointOfSale.Controllers;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
@@ -123,12 +125,14 @@ donation:
|
||||
price: 1.02
|
||||
custom: true
|
||||
";
|
||||
vmpos.Currency = "EUR";
|
||||
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
|
||||
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
|
||||
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
|
||||
var publicApps = user.GetController<UIPointOfSaleController>();
|
||||
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
|
||||
|
||||
Assert.Equal("EUR", vmview.CurrencyCode);
|
||||
// apple shouldn't be available since we it's set to "disabled: true" above
|
||||
Assert.Equal(2, vmview.Items.Length);
|
||||
Assert.Equal("orange", vmview.Items[0].Title);
|
||||
@@ -139,6 +143,41 @@ donation:
|
||||
// apple is not found
|
||||
Assert.IsType<NotFoundResult>(publicApps
|
||||
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
|
||||
|
||||
// List
|
||||
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
app = appList.Apps[0];
|
||||
apps = user.GetController<UIAppsController>();
|
||||
appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType, Settings = "{\"currency\":\"EUR\"}" };
|
||||
apps.HttpContext.SetAppData(appData);
|
||||
pos.HttpContext.SetAppData(appData);
|
||||
Assert.Single(appList.Apps);
|
||||
Assert.Equal("test", app.AppName);
|
||||
Assert.True(app.Role.ToPermissionSet(appList.Apps[0].StoreId).Contains(Policies.CanModifyStoreSettings, app.StoreId));
|
||||
Assert.Equal(user.StoreId, app.StoreId);
|
||||
Assert.False(app.Archived);
|
||||
// Archive
|
||||
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
|
||||
Assert.EndsWith("/settings/pos", redirect.Url);
|
||||
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
Assert.Empty(appList.Apps);
|
||||
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId, archived: true).Result).Model);
|
||||
app = appList.Apps[0];
|
||||
Assert.True(app.Archived);
|
||||
Assert.IsType<NotFoundResult>(await publicApps.ViewPointOfSale(app.Id, PosViewType.Static));
|
||||
// Unarchive
|
||||
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
|
||||
Assert.EndsWith("/settings/pos", redirect.Url);
|
||||
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
|
||||
app = appList.Apps[0];
|
||||
Assert.False(app.Archived);
|
||||
Assert.IsType<ViewResult>(await publicApps.ViewPointOfSale(app.Id, PosViewType.Static));
|
||||
// Delete
|
||||
Assert.IsType<ViewResult>(apps.DeleteApp(app.Id));
|
||||
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(app.Id).Result);
|
||||
Assert.Equal(nameof(UIStoresController.Dashboard), redirectToAction.ActionName);
|
||||
appList = await apps.ListApps(user.StoreId).AssertViewModelAsync<ListAppsViewModel>();
|
||||
Assert.Empty(appList.Apps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,6 +809,27 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.GoToUrl(storeUrl);
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
|
||||
// Archive store
|
||||
(storeName, storeId) = s.CreateNewStore();
|
||||
|
||||
s.Driver.FindElement(By.Id("StoreSelectorToggle")).Click();
|
||||
Assert.Contains(storeName, s.Driver.FindElement(By.Id("StoreSelectorMenu")).Text);
|
||||
s.Driver.FindElement(By.Id($"StoreSelectorMenuItem-{storeId}")).Click();
|
||||
s.GoToStore();
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
Assert.Contains("The store has been archived and will no longer appear in the stores list by default.", s.FindAlertMessage().Text);
|
||||
|
||||
s.Driver.FindElement(By.Id("StoreSelectorToggle")).Click();
|
||||
Assert.DoesNotContain(storeName, s.Driver.FindElement(By.Id("StoreSelectorMenu")).Text);
|
||||
Assert.Contains("1 Archived Store", s.Driver.FindElement(By.Id("StoreSelectorMenu")).Text);
|
||||
s.Driver.FindElement(By.Id("StoreSelectorArchived")).Click();
|
||||
|
||||
var storeLink = s.Driver.FindElement(By.Id($"Store-{storeId}"));
|
||||
Assert.Contains(storeName, storeLink.Text);
|
||||
storeLink.Click();
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
Assert.Contains("The store has been unarchived and will appear in the stores list by default again.", s.FindAlertMessage().Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@@ -978,6 +999,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
var appId = s.Driver.Url.Split('/')[4];
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
@@ -1046,6 +1068,24 @@ namespace BTCPayServer.Tests
|
||||
// We are only if explicitly going to /
|
||||
s.GoToUrl("/");
|
||||
Assert.Contains("Tea shop", s.Driver.PageSource);
|
||||
|
||||
// Archive
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("Nav-ArchivedApps")));
|
||||
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);
|
||||
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();
|
||||
|
||||
// Unarchive
|
||||
s.Driver.FindElement(By.Id($"App-{appId}")).Click();
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
Assert.Contains("The app has been unarchived and will appear in the apps list by default again.", s.FindAlertMessage().Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
@@ -1079,17 +1119,37 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.ExecuteJavaScript("document.getElementById('EndDate').value = ''");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
Assert.Contains("App updated", s.FindAlertMessage().Text);
|
||||
var appId = s.Driver.Url.Split('/')[4];
|
||||
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
var cfUrl = s.Driver.Url;
|
||||
|
||||
Assert.Equal("Currently active!",
|
||||
s.Driver.FindElement(By.CssSelector("[data-test='time-state']")).Text);
|
||||
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(windows[0]);
|
||||
|
||||
// Archive
|
||||
Assert.True(s.Driver.ElementDoesNotExist(By.Id("Nav-ArchivedApps")));
|
||||
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);
|
||||
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();
|
||||
|
||||
// Unarchive
|
||||
s.Driver.FindElement(By.Id($"App-{appId}")).Click();
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
Assert.Contains("The app has been unarchived and will appear in the apps list by default again.", s.FindAlertMessage().Text);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
@using BTCPayServer.Views.Wallets
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Client
|
||||
@using BTCPayServer.Components.ThemeSwitch
|
||||
@using BTCPayServer.Components.UIExtensionPoint
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Views.Apps
|
||||
@using BTCPayServer.Views.CustodianAccounts
|
||||
@inject Microsoft.AspNetCore.Http.IHttpContextAccessor HttpContext;
|
||||
@inject BTCPayServerEnvironment Env
|
||||
@@ -187,6 +190,14 @@
|
||||
<span>Manage Plugins</span>
|
||||
</a>
|
||||
</li>
|
||||
@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.IsActivePage(AppsNavPages.Index)" id="Nav-ArchivedApps">
|
||||
@Model.ArchivedAppsCount Archived App@(Model.ArchivedAppsCount == 1 ? "" : "s")
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,14 +68,18 @@ namespace BTCPayServer.Components.MainNav
|
||||
vm.LightningNodes = lightningNodes;
|
||||
|
||||
// Apps
|
||||
var apps = await _appService.GetAllApps(UserId, false, store.Id);
|
||||
vm.Apps = apps.Select(a => new StoreApp
|
||||
var apps = await _appService.GetAllApps(UserId, false, store.Id, true);
|
||||
vm.Apps = apps
|
||||
.Where(a => !a.Archived)
|
||||
.Select(a => new StoreApp
|
||||
{
|
||||
Id = a.Id,
|
||||
AppName = a.AppName,
|
||||
AppType = a.AppType
|
||||
}).ToList();
|
||||
|
||||
vm.ArchivedAppsCount = apps.Count(a => a.Archived);
|
||||
|
||||
if (PoliciesSettings.Experimental)
|
||||
{
|
||||
// Custodian Accounts
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Apps;
|
||||
|
||||
namespace BTCPayServer.Components.MainNav
|
||||
{
|
||||
@@ -13,6 +12,7 @@ namespace BTCPayServer.Components.MainNav
|
||||
public List<StoreApp> Apps { get; set; }
|
||||
public CustodianAccountData[] CustodianAccounts { get; set; }
|
||||
public bool AltcoinsBuild { get; set; }
|
||||
public int ArchivedAppsCount { get; set; }
|
||||
}
|
||||
|
||||
public class StoreApp
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@using BTCPayServer.Client
|
||||
@using BTCPayServer.Components.MainLogo
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Views.Stores
|
||||
@inject BTCPayServerEnvironment Env
|
||||
@inject IFileService FileService
|
||||
@model BTCPayServer.Components.StoreSelector.StoreSelectorViewModel
|
||||
@@ -34,7 +35,7 @@ else
|
||||
<a asp-controller="UIStores" asp-action="Dashboard" permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
|
||||
<a asp-controller="UIInvoice" asp-action="ListInvoices" not-permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
|
||||
}
|
||||
@if (Model.Options.Any())
|
||||
@if (Model.Options.Any() || Model.ArchivedCount > 0)
|
||||
{
|
||||
<div id="StoreSelector">
|
||||
<div id="StoreSelectorDropdown" class="dropdown only-for-js">
|
||||
@@ -64,8 +65,16 @@ else
|
||||
}
|
||||
</li>
|
||||
}
|
||||
@if (Model.Options.Any())
|
||||
{
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item" id="StoreSelectorCreate">Create Store</a></li>
|
||||
}
|
||||
<li><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item @ViewData.IsActivePage(StoreNavPages.Create)" id="StoreSelectorCreate">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.IsActivePage(StoreNavPages.Index)" id="StoreSelectorArchived">@Model.ArchivedCount Archived Store@(Model.ArchivedCount == 1 ? "" : "s")</a></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,9 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
var userId = _userManager.GetUserId(UserClaimsPrincipal);
|
||||
var stores = await _storeRepo.GetStoresByUserId(userId);
|
||||
var currentStore = ViewContext.HttpContext.GetStoreData();
|
||||
var archivedCount = stores.Count(s => s.Archived);
|
||||
var options = stores
|
||||
.Where(store => !store.Archived)
|
||||
.Select(store =>
|
||||
{
|
||||
var cryptoCode = store
|
||||
@@ -59,7 +61,8 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
Options = options,
|
||||
CurrentStoreId = currentStore?.Id,
|
||||
CurrentDisplayName = currentStore?.StoreName,
|
||||
CurrentStoreLogoFileId = blob?.LogoFileId
|
||||
CurrentStoreLogoFileId = blob?.LogoFileId,
|
||||
ArchivedCount = archivedCount
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace BTCPayServer.Components.StoreSelector
|
||||
public string CurrentStoreId { get; set; }
|
||||
public string CurrentStoreLogoFileId { get; set; }
|
||||
public string CurrentDisplayName { get; set; }
|
||||
public int ArchivedCount { get; set; }
|
||||
}
|
||||
|
||||
public class StoreSelectorOption
|
||||
|
||||
@@ -66,7 +66,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Name = request.AppName,
|
||||
AppType = CrowdfundAppType.AppType
|
||||
AppType = CrowdfundAppType.AppType,
|
||||
Archived = request.Archived ?? false
|
||||
};
|
||||
|
||||
appData.SetSettings(ToCrowdfundSettings(request));
|
||||
@@ -97,7 +98,8 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
StoreDataId = storeId,
|
||||
Name = request.AppName,
|
||||
AppType = PointOfSaleAppType.AppType
|
||||
AppType = PointOfSaleAppType.AppType,
|
||||
Archived = request.Archived ?? false
|
||||
};
|
||||
|
||||
appData.SetSettings(ToPointOfSaleSettings(request));
|
||||
@@ -111,7 +113,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> UpdatePointOfSaleApp(string appId, CreatePointOfSaleAppRequest request)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -129,6 +131,10 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
}
|
||||
|
||||
app.Name = request.AppName;
|
||||
if (request.Archived != null)
|
||||
{
|
||||
app.Archived = request.Archived.Value;
|
||||
}
|
||||
app.SetSettings(ToPointOfSaleSettings(request));
|
||||
|
||||
await _appService.UpdateOrCreateApp(app);
|
||||
@@ -153,7 +159,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetAllApps()
|
||||
{
|
||||
var apps = await _appService.GetAllApps(_userManager.GetUserId(User));
|
||||
var apps = await _appService.GetAllApps(_userManager.GetUserId(User), includeArchived: true);
|
||||
|
||||
return Ok(apps.Select(ToModel).ToArray());
|
||||
}
|
||||
@@ -162,7 +168,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetAllApps(string storeId)
|
||||
{
|
||||
var apps = await _appService.GetAllApps(_userManager.GetUserId(User), allowNoUser: false, storeId);
|
||||
var apps = await _appService.GetAllApps(_userManager.GetUserId(User), false, storeId, true);
|
||||
|
||||
return Ok(apps.Select(ToModel).ToArray());
|
||||
}
|
||||
@@ -171,7 +177,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, null);
|
||||
var app = await _appService.GetApp(appId, null, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -184,7 +190,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetPosApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType);
|
||||
var app = await _appService.GetApp(appId, PointOfSaleAppType.AppType, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -197,7 +203,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
public async Task<IActionResult> GetCrowdfundApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType);
|
||||
var app = await _appService.GetApp(appId, CrowdfundAppType.AppType, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -209,7 +215,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
[HttpDelete("~/api/v1/apps/{appId}")]
|
||||
public async Task<IActionResult> DeleteApp(string appId)
|
||||
{
|
||||
var app = await _appService.GetApp(appId, null);
|
||||
var app = await _appService.GetApp(appId, null, includeArchived: true);
|
||||
if (app == null)
|
||||
{
|
||||
return AppNotFound();
|
||||
@@ -293,6 +299,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return new AppDataBase
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.Name,
|
||||
StoreId = appData.StoreDataId,
|
||||
@@ -305,6 +312,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return new AppDataBase
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.AppName,
|
||||
StoreId = appData.StoreId,
|
||||
@@ -319,6 +327,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return new PointOfSaleAppData
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.Name,
|
||||
StoreId = appData.StoreDataId,
|
||||
@@ -387,6 +396,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return new CrowdfundAppData
|
||||
{
|
||||
Id = appData.Id,
|
||||
Archived = appData.Archived,
|
||||
AppType = appData.AppType,
|
||||
Name = appData.Name,
|
||||
StoreId = appData.StoreDataId,
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
_storeRepository = storeRepository;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores")]
|
||||
public Task<ActionResult<IEnumerable<Client.Models.StoreData>>> GetStores()
|
||||
@@ -112,7 +113,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
return Ok(FromModel(store));
|
||||
}
|
||||
|
||||
internal static Client.Models.StoreData FromModel(Data.StoreData data)
|
||||
internal static Client.Models.StoreData FromModel(StoreData data)
|
||||
{
|
||||
var storeBlob = data.GetStoreBlob();
|
||||
return new Client.Models.StoreData
|
||||
@@ -120,6 +121,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
Id = data.Id,
|
||||
Name = data.StoreName,
|
||||
Website = data.StoreWebsite,
|
||||
Archived = data.Archived,
|
||||
SupportUrl = storeBlob.StoreSupportUrl,
|
||||
SpeedPolicy = data.SpeedPolicy,
|
||||
DefaultPaymentMethod = data.GetDefaultPaymentId()?.ToStringNormalized(),
|
||||
@@ -166,6 +168,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
var blob = model.GetStoreBlob();
|
||||
model.StoreName = restModel.Name;
|
||||
model.StoreWebsite = restModel.Website;
|
||||
model.Archived = restModel.Archived;
|
||||
model.SpeedPolicy = restModel.SpeedPolicy;
|
||||
model.SetDefaultPaymentId(defaultPaymentMethod);
|
||||
//we do not include the default payment method in this model and instead opt to set it in the stores/storeid/payment-methods endpoints
|
||||
|
||||
@@ -76,28 +76,26 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> ListApps(
|
||||
string storeId,
|
||||
string sortOrder = null,
|
||||
string sortOrderColumn = null
|
||||
string sortOrderColumn = null,
|
||||
bool archived = false
|
||||
)
|
||||
{
|
||||
var store = GetCurrentStore();
|
||||
var apps = await _appService.GetAllApps(GetUserId(), false, store.Id);
|
||||
var apps = (await _appService.GetAllApps(GetUserId(), false, store.Id, archived))
|
||||
.Where(app => app.Archived == archived);
|
||||
|
||||
if (sortOrder != null && sortOrderColumn != null)
|
||||
{
|
||||
apps = apps.OrderByDescending(app =>
|
||||
{
|
||||
switch (sortOrderColumn)
|
||||
return sortOrderColumn switch
|
||||
{
|
||||
case nameof(app.AppName):
|
||||
return app.AppName;
|
||||
case nameof(app.StoreName):
|
||||
return app.StoreName;
|
||||
case nameof(app.AppType):
|
||||
return app.AppType;
|
||||
default:
|
||||
return app.Id;
|
||||
}
|
||||
}).ToArray();
|
||||
nameof(app.AppName) => app.AppName,
|
||||
nameof(app.StoreName) => app.StoreName,
|
||||
nameof(app.AppType) => app.AppType,
|
||||
_ => app.Id
|
||||
};
|
||||
});
|
||||
|
||||
switch (sortOrder)
|
||||
{
|
||||
@@ -105,7 +103,7 @@ namespace BTCPayServer.Controllers
|
||||
ViewData[$"{sortOrderColumn}SortOrder"] = "asc";
|
||||
break;
|
||||
case "asc":
|
||||
apps = apps.Reverse().ToArray();
|
||||
apps = apps.Reverse();
|
||||
ViewData[$"{sortOrderColumn}SortOrder"] = "desc";
|
||||
break;
|
||||
}
|
||||
@@ -113,7 +111,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
return View(new ListAppsViewModel
|
||||
{
|
||||
Apps = apps
|
||||
Apps = apps.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -161,7 +159,6 @@ namespace BTCPayServer.Controllers
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App successfully created";
|
||||
CreatedAppId = appData.Id;
|
||||
|
||||
|
||||
var url = await type.ConfigureLink(appData);
|
||||
return Redirect(url);
|
||||
}
|
||||
@@ -191,6 +188,36 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId = app.StoreDataId });
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpPost("{appId}/archive")]
|
||||
public async Task<IActionResult> ToggleArchive(string appId)
|
||||
{
|
||||
var app = GetCurrentApp();
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
|
||||
var type = _appService.GetAppType(app.AppType);
|
||||
if (type is null)
|
||||
{
|
||||
return UnprocessableEntity();
|
||||
}
|
||||
|
||||
var archived = !app.Archived;
|
||||
if (await _appService.SetArchived(app, archived))
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = archived
|
||||
? "The app has been archived and will no longer appear in the apps list by default."
|
||||
: "The app has been unarchived and will appear in the apps list by default again.";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"Failed to {(archived ? "archive" : "unarchive")} the app.";
|
||||
}
|
||||
|
||||
var url = await type.ConfigureLink(app);
|
||||
return Redirect(url);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[HttpPost("{appId}/upload-file")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
|
||||
@@ -680,6 +680,7 @@ namespace BTCPayServer.Controllers
|
||||
InvoiceExpiration = (int)storeBlob.InvoiceExpiration.TotalMinutes,
|
||||
DefaultCurrency = storeBlob.DefaultCurrency,
|
||||
BOLT11Expiration = (long)storeBlob.RefundBOLT11Expiration.TotalDays,
|
||||
Archived = store.Archived,
|
||||
CanDelete = _Repo.CanDeleteStores()
|
||||
};
|
||||
|
||||
@@ -827,6 +828,23 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/archive")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)]
|
||||
public async Task<IActionResult> ToggleArchive(string storeId)
|
||||
{
|
||||
CurrentStore.Archived = !CurrentStore.Archived;
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = CurrentStore.Archived
|
||||
? "The store has been archived and will no longer appear in the stores list by default."
|
||||
: "The store has been unarchived and will appear in the stores list by default again.";
|
||||
|
||||
return RedirectToAction(nameof(GeneralSettings), new
|
||||
{
|
||||
storeId = CurrentStore.Id
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/delete")]
|
||||
public IActionResult DeleteStore(string storeId)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@@ -37,6 +35,26 @@ namespace BTCPayServer.Controllers
|
||||
_rateFactory = rateFactory;
|
||||
}
|
||||
|
||||
[HttpGet()]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettingsUnscoped)]
|
||||
public async Task<IActionResult> ListStores(bool archived = false)
|
||||
{
|
||||
var stores = await _repo.GetStoresByUserId(GetUserId());
|
||||
var vm = new ListStoresViewModel
|
||||
{
|
||||
Stores = stores
|
||||
.Where(s => s.Archived == archived)
|
||||
.Select(s => new ListStoresViewModel.StoreViewModel
|
||||
{
|
||||
StoreId = s.Id,
|
||||
StoreName = s.StoreName,
|
||||
Archived = s.Archived
|
||||
}).ToList(),
|
||||
Archived = archived
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpGet("create")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettingsUnscoped)]
|
||||
public async Task<IActionResult> CreateStore(bool skipWizard)
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public AppData App { get; set; }
|
||||
public StoreRepository.StoreRole Role { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
}
|
||||
|
||||
public ListAppViewModel[] Apps { get; set; }
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
|
||||
public bool CanDelete { get; set; }
|
||||
|
||||
public bool Archived { get; set; }
|
||||
|
||||
[Display(Name = "Allow anyone to create invoice")]
|
||||
public bool AnyoneCanCreateInvoice { get; set; }
|
||||
|
||||
|
||||
16
BTCPayServer/Models/StoreViewModels/ListStoresViewModel.cs
Normal file
16
BTCPayServer/Models/StoreViewModels/ListStoresViewModel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BTCPayServer.Models.StoreViewModels;
|
||||
|
||||
public class ListStoresViewModel
|
||||
{
|
||||
public class StoreViewModel
|
||||
{
|
||||
public string StoreName { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public bool Archived { get; set; }
|
||||
}
|
||||
|
||||
public List<StoreViewModel> Stores { get; set; } = new ();
|
||||
public bool Archived { get; set; }
|
||||
}
|
||||
@@ -10,7 +10,6 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Plugins.Crowdfund.Models;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services.Apps;
|
||||
@@ -237,6 +236,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
||||
StoreName = app.StoreData?.StoreName,
|
||||
StoreDefaultCurrency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, settings.TargetCurrency),
|
||||
AppName = app.Name,
|
||||
Archived = app.Archived,
|
||||
Enabled = settings.Enabled,
|
||||
EnforceTargetAmount = settings.EnforceTargetAmount,
|
||||
StartDate = settings.StartDate,
|
||||
@@ -346,6 +346,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers
|
||||
}
|
||||
|
||||
app.Name = vm.AppName;
|
||||
app.Archived = vm.Archived;
|
||||
var newSettings = new CrowdfundSettings
|
||||
{
|
||||
Title = vm.Title,
|
||||
|
||||
@@ -116,5 +116,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
|
||||
|
||||
// NOTE: Improve validation if needed
|
||||
public bool ModelWithMinimumData => Description != null && Title != null && TargetCurrency != null;
|
||||
|
||||
public bool Archived { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Plugins.PointOfSale.Models;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
@@ -61,8 +58,6 @@ namespace BTCPayServer.Plugins.Crowdfund.Models
|
||||
public DateTime? NextResetDate { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool Started => !StartDate.HasValue || DateTime.UtcNow > StartDate;
|
||||
|
||||
public bool Ended => EndDate.HasValue && DateTime.UtcNow > EndDate;
|
||||
|
||||
@@ -28,7 +28,6 @@ using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
@@ -537,6 +536,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
StoreId = app.StoreDataId,
|
||||
StoreName = app.StoreData?.StoreName,
|
||||
StoreDefaultCurrency = await GetStoreDefaultCurrentIfEmpty(app.StoreDataId, settings.Currency),
|
||||
Archived = app.Archived,
|
||||
AppName = app.Name,
|
||||
Title = settings.Title,
|
||||
DefaultView = settings.DefaultView,
|
||||
@@ -647,6 +647,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers
|
||||
};
|
||||
|
||||
app.Name = vm.AppName;
|
||||
app.Archived = vm.Archived;
|
||||
app.SetSettings(settings);
|
||||
await _appService.UpdateOrCreateApp(app);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
|
||||
@@ -99,5 +99,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Models
|
||||
|
||||
[Display(Name = "Request customer data on checkout")]
|
||||
public string FormId { get; set; }
|
||||
|
||||
public bool Archived { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,15 @@ namespace BTCPayServer.Services.Apps
|
||||
return await ctx.SaveChangesAsync() == 1;
|
||||
}
|
||||
|
||||
public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string? userId, bool allowNoUser = false, string? storeId = null)
|
||||
public async Task<bool> SetArchived(AppData appData, bool archived)
|
||||
{
|
||||
await using var ctx = _ContextFactory.CreateContext();
|
||||
appData.Archived = archived;
|
||||
ctx.Entry(appData).State = EntityState.Modified;
|
||||
return await ctx.SaveChangesAsync() == 1;
|
||||
}
|
||||
|
||||
public async Task<ListAppsViewModel.ListAppViewModel[]> GetAllApps(string? userId, bool allowNoUser = false, string? storeId = null, bool includeArchived = false)
|
||||
{
|
||||
await using var ctx = _ContextFactory.CreateContext();
|
||||
var listApps = (await ctx.UserStore
|
||||
@@ -254,6 +262,7 @@ namespace BTCPayServer.Services.Apps
|
||||
.Include(store => store.StoreRole)
|
||||
.Include(store => store.StoreData)
|
||||
.Join(ctx.Apps, us => us.StoreDataId, app => app.StoreDataId, (us, app) => new { us, app })
|
||||
.Where(b => !b.app.Archived || b.app.Archived == includeArchived)
|
||||
.OrderBy(b => b.app.Created)
|
||||
.ToArrayAsync()).Select(arg => new ListAppsViewModel.ListAppViewModel
|
||||
{
|
||||
@@ -264,6 +273,7 @@ namespace BTCPayServer.Services.Apps
|
||||
AppType = arg.app.AppType,
|
||||
Id = arg.app.Id,
|
||||
Created = arg.app.Created,
|
||||
Archived = arg.app.Archived,
|
||||
App = arg.app
|
||||
}).ToArray();
|
||||
|
||||
@@ -300,11 +310,12 @@ namespace BTCPayServer.Services.Apps
|
||||
return style;
|
||||
}
|
||||
|
||||
public async Task<List<AppData>> GetApps(string[] appIds, bool includeStore = false)
|
||||
public async Task<List<AppData>> GetApps(string[] appIds, bool includeStore = false, bool includeArchived = false)
|
||||
{
|
||||
await using var ctx = _ContextFactory.CreateContext();
|
||||
var query = ctx.Apps
|
||||
.Where(app => appIds.Contains(app.Id));
|
||||
.Where(app => appIds.Contains(app.Id))
|
||||
.Where(app => !app.Archived || app.Archived == includeArchived);
|
||||
if (includeStore)
|
||||
{
|
||||
query = query.Include(data => data.StoreData);
|
||||
@@ -320,13 +331,12 @@ namespace BTCPayServer.Services.Apps
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<AppData?> GetApp(string appId, string? appType, bool includeStore = false)
|
||||
public async Task<AppData?> GetApp(string appId, string? appType, bool includeStore = false, bool includeArchived = false)
|
||||
{
|
||||
await using var ctx = _ContextFactory.CreateContext();
|
||||
var query = ctx.Apps
|
||||
.Where(us => us.Id == appId &&
|
||||
(appType == null || us.AppType == appType));
|
||||
|
||||
.Where(us => us.Id == appId && (appType == null || us.AppType == appType))
|
||||
.Where(app => !app.Archived || app.Archived == includeArchived);
|
||||
if (includeStore)
|
||||
{
|
||||
query = query.Include(data => data.StoreData);
|
||||
@@ -339,7 +349,6 @@ namespace BTCPayServer.Services.Apps
|
||||
return _storeRepository.FindStore(app.StoreDataId);
|
||||
}
|
||||
|
||||
|
||||
public static string SerializeTemplate(ViewPointOfSaleViewModel.Item[] items)
|
||||
{
|
||||
return JsonConvert.SerializeObject(items, Formatting.Indented, _defaultSerializer);
|
||||
|
||||
@@ -31,9 +31,13 @@
|
||||
<h2 class="mb-0">@ViewData["Title"]</h2>
|
||||
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
|
||||
@if (Model.ModelWithMinimumData)
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<a class="btn btn-secondary" asp-action="ViewCrowdfund" asp-route-appId="@Model.AppId" id="ViewApp" target="_blank">View</a>
|
||||
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False">Unarchive</button>
|
||||
}
|
||||
else if (Model.ModelWithMinimumData)
|
||||
{
|
||||
<a class="btn btn-secondary" asp-controller="UICrowdfund" asp-action="ViewCrowdfund" asp-route-appId="@Model.AppId" id="ViewApp" target="_blank">View</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,6 +45,7 @@
|
||||
<partial name="_StatusMessage" />
|
||||
|
||||
<input type="hidden" asp-for="StoreId" />
|
||||
<input type="hidden" asp-for="Archived" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="row">
|
||||
@@ -330,6 +335,18 @@
|
||||
|
||||
<div class="d-flex gap-3 mt-3">
|
||||
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
<form method="post" asp-controller="UIApps" asp-action="ToggleArchive" asp-route-appId="@Model.AppId">
|
||||
<button type="submit" class="btn btn-outline-secondary" id="btn-archive-toggle">
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<span class="text-nowrap">Unarchive this app</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-nowrap" data-bs-toggle="tooltip" title="Archive this app so that it does not appear in the apps list by default">Archive this app</span>
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
<a id="DeleteApp" class="btn btn-outline-danger" asp-controller="UIApps" asp-action="DeleteApp" asp-route-appId="@Model.AppId" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Html.Encode(Model.AppName)</strong> and its settings will be permanently deleted." data-confirm-input="DELETE">Delete this app</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,13 +18,21 @@
|
||||
<h2 class="mb-0">@ViewData["Title"]</h2>
|
||||
<div class="d-flex gap-3 mt-3 mt-sm-0">
|
||||
<button type="submit" class="btn btn-primary order-sm-1" id="SaveSettings">Save</button>
|
||||
<a class="btn btn-secondary" asp-action="ViewPointOfSale" asp-route-appId="@Model.Id" id="ViewApp" target="_blank">View</a>
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<button type="submit" class="btn btn-outline-secondary" name="Archived" value="False">Unarchive</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" asp-controller="UIPointOfSale" asp-action="ViewPointOfSale" asp-route-appId="@Model.Id" id="ViewApp" target="_blank">View</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_StatusMessage" />
|
||||
|
||||
<input type="hidden" asp-for="StoreId" />
|
||||
<input type="hidden" asp-for="Archived" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="row">
|
||||
@@ -266,6 +274,18 @@
|
||||
|
||||
<div class="d-flex gap-3 mt-3">
|
||||
<a class="btn btn-secondary" asp-action="ListInvoices" asp-controller="UIInvoice" asp-route-storeId="@Model.StoreId" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
<form method="post" asp-controller="UIApps" asp-action="ToggleArchive" asp-route-appId="@Model.Id">
|
||||
<button type="submit" class="btn btn-outline-secondary" id="btn-archive-toggle">
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<span class="text-nowrap">Unarchive this app</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-nowrap" data-bs-toggle="tooltip" title="Archive this app so that it does not appear in the apps list by default">Archive this app</span>
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
<a id="DeleteApp" class="btn btn-outline-danger" asp-controller="UIApps" asp-action="DeleteApp" asp-route-appId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Html.Encode(Model.AppName)</strong> and its settings will be permanently deleted." data-confirm-input="DELETE">Delete this app</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -33,19 +33,6 @@
|
||||
<table class="table table-hover table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a
|
||||
asp-action="ListApps"
|
||||
asp-route-storeId="@Context.GetStoreData().Id"
|
||||
asp-route-sortOrder="@(storeNameSortOrder ?? "asc")"
|
||||
asp-route-sortOrderColumn="StoreName"
|
||||
class="text-nowrap"
|
||||
title="@(storeNameSortOrder == "desc" ? sortByDesc : sortByAsc)"
|
||||
>
|
||||
Store
|
||||
<span class="fa @(storeNameSortOrder == "asc" ? "fa-sort-alpha-desc" : storeNameSortOrder == "desc" ? "fa-sort-alpha-asc" : "fa-sort")" />
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a
|
||||
asp-action="ListApps"
|
||||
@@ -72,21 +59,36 @@
|
||||
<span class="fa @(appTypeSortOrder == "asc" ? "fa-sort-alpha-desc" : appTypeSortOrder == "desc" ? "fa-sort-alpha-asc" : "fa-sort")" />
|
||||
</a>
|
||||
</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th>
|
||||
<a
|
||||
asp-action="ListApps"
|
||||
asp-route-storeId="@Context.GetStoreData().Id"
|
||||
asp-route-sortOrder="@(storeNameSortOrder ?? "asc")"
|
||||
asp-route-sortOrderColumn="StoreName"
|
||||
class="text-nowrap"
|
||||
title="@(storeNameSortOrder == "desc" ? sortByDesc : sortByAsc)"
|
||||
>
|
||||
Store
|
||||
<span class="fa @(storeNameSortOrder == "asc" ? "fa-sort-alpha-desc" : storeNameSortOrder == "desc" ? "fa-sort-alpha-asc" : "fa-sort")" />
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var app in Model.Apps)
|
||||
{
|
||||
var appType = AppService.GetAppType(app.AppType)!;
|
||||
var appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = app.AppType };
|
||||
var url = await appType.ConfigureLink(appData);
|
||||
<tr>
|
||||
<td>
|
||||
<span permission="@Policies.CanModifyStoreSettings">
|
||||
<a asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@app.StoreId">@app.StoreName</a>
|
||||
</span>
|
||||
|
||||
<span not-permission="@Policies.CanModifyStoreSettings">@app.StoreName</span>
|
||||
<a href="@url" permission="@Policies.CanModifyStoreSettings" id="App-@app.Id">@app.AppName</a>
|
||||
<span not-permission="@Policies.CanModifyStoreSettings">@app.AppName</span>
|
||||
@if (app.Archived)
|
||||
{
|
||||
<span class="badge bg-info ms-2">archived</span>
|
||||
}
|
||||
</td>
|
||||
<td>@app.AppName</td>
|
||||
<td>
|
||||
@AppService.GetAvailableAppTypes()[app.AppType]
|
||||
@{
|
||||
@@ -98,14 +100,11 @@
|
||||
<span>@viewStyle</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end" permission="@Policies.CanModifyStoreSettings">
|
||||
|
||||
<a asp-action="@app.UpdateAction" asp-controller="UIApps" asp-route-appId="@app.Id" asp-route-storeId="@app.StoreId">Settings</a>
|
||||
<span> - </span>
|
||||
|
||||
<a asp-action="DeleteApp" asp-route-appId="@app.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@Html.Encode(app.AppName)</strong> and its settings will be permanently deleted from your store <strong>@Html.Encode(app.StoreName)</strong>." data-confirm-input="DELETE">Delete</a>
|
||||
</td>
|
||||
<td class="text-end" no-permission="@Policies.CanModifyStoreSettings">
|
||||
<td>
|
||||
<span permission="@Policies.CanModifyStoreSettings">
|
||||
<a asp-controller="UIStores" asp-action="GeneralSettings" asp-route-storeId="@app.StoreId">@app.StoreName</a>
|
||||
</span>
|
||||
<span not-permission="@Policies.CanModifyStoreSettings">@app.StoreName</span>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@using BTCPayServer.TagHelpers
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@using BTCPayServer.Client
|
||||
@inject IFileService FileService;
|
||||
@model GeneralSettingsViewModel
|
||||
@{
|
||||
@@ -172,14 +173,26 @@
|
||||
|
||||
<button type="submit" class="btn btn-primary mt-2" id="Save">Save</button>
|
||||
</form>
|
||||
<h3 class="mt-5 mb-3">Additional Actions</h3>
|
||||
<div id="danger-zone" class="d-flex flex-wrap align-items-center gap-3 mb-5 mt-2">
|
||||
<form asp-action="ToggleArchive" asp-route-storeId="@Model.Id" method="post" permission="@Policies.CanModifyStoreSettings">
|
||||
<button type="submit" class="btn btn-outline-secondary" id="btn-archive-toggle">
|
||||
@if (Model.Archived)
|
||||
{
|
||||
<span class="text-nowrap" data-bs-toggle="tooltip" title="Unarchive this store">Unarchive this store</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-nowrap" data-bs-toggle="tooltip" title="Archive this store so that it does not appear in the stores list by default">Archive this store</span>
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
@if (Model.CanDelete)
|
||||
{
|
||||
<h3 class="mt-5 mb-3">Additional Actions</h3>
|
||||
<div id="danger-zone">
|
||||
<a id="DeleteStore" class="btn btn-outline-danger mb-5 mt-2" asp-action="DeleteStore" asp-route-storeId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@Html.Encode(Model.StoreName)</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete this store</a>
|
||||
</div>
|
||||
<a id="DeleteStore" class="btn btn-outline-danger" asp-action="DeleteStore" asp-route-storeId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@Html.Encode(Model.StoreName)</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete this store</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Delete store", "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store.", "Delete"))" />
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace BTCPayServer.Views.Stores
|
||||
{
|
||||
public enum StoreNavPages
|
||||
{
|
||||
Index,
|
||||
Create,
|
||||
Dashboard,
|
||||
General,
|
||||
|
||||
50
BTCPayServer/Views/UIUserStores/ListStores.cshtml
Normal file
50
BTCPayServer/Views/UIUserStores/ListStores.cshtml
Normal file
@@ -0,0 +1,50 @@
|
||||
@using BTCPayServer.Client
|
||||
@model BTCPayServer.Models.StoreViewModels.ListStoresViewModel
|
||||
@{
|
||||
ViewData.SetActivePage(StoreNavPages.Index, Model.Archived ? "Archived Stores" : "Stores");
|
||||
}
|
||||
|
||||
<partial name="_StatusMessage" />
|
||||
|
||||
<div class="d-sm-flex justify-content-between mb-2">
|
||||
<h2 class="mb-0">
|
||||
@ViewData["Title"]
|
||||
</h2>
|
||||
</div>
|
||||
@if (Model.Stores.Any())
|
||||
{
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Store Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var store in Model.Stores)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
permission="@Policies.CanModifyStoreSettings"
|
||||
asp-action="GeneralSettings" asp-controller="UIStores" asp-route-storeId="@store.StoreId"
|
||||
id="Store-@store.StoreId">
|
||||
@store.StoreName
|
||||
</a>
|
||||
<span not-permission="@Policies.CanModifyStoreSettings">@store.StoreName</span>
|
||||
@if (store.Archived)
|
||||
{
|
||||
<span class="badge bg-info ms-2">archived</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-secondary mt-3">
|
||||
There are no stores yet.
|
||||
<span permission="@Policies.CanModifyStoreSettingsUnscoped"><a asp-action="CreateStore">Create a store</a>.</span>
|
||||
</p>
|
||||
}
|
||||
@@ -770,6 +770,12 @@
|
||||
"type": "string",
|
||||
"example": "PointOfSale",
|
||||
"description": "Type of the app which was created"
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean",
|
||||
"description": "If true, the app does not appear in the apps list by default.",
|
||||
"default": false,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -454,6 +454,11 @@
|
||||
"default": 0.0,
|
||||
"description": "Consider an invoice fully paid, even if the payment is missing 'x' % of the full amount."
|
||||
},
|
||||
"archived": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, the store does not appear in the stores list by default."
|
||||
},
|
||||
"anyoneCanCreateInvoice": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
Reference in New Issue
Block a user