mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Delete confirmation modals (#2614)
* Refactor confirm view: separate modal * Add delete confirmation modals for apps and FIDO2 * Add delete confirmation modals for 2FA actions * Add delete confirmation modals for api keys and webhooks * Add delete confirmation modals for stores and store users * Add delete confirmation modals for LND seed and SSH * Add delete confirmation modals for rate rule scripting * Test fixes and improvements * Add delete confirmation modals for dynamic DNS * Add delete confirmation modals for store access tokens * Add confirmation modals for pull payment archiving * Refactor confirm modal code * Add confirmation input, update wording * Update modal styles * Upgrade ChromeDriver * Simplify and unify confirmation input * Test fixes * Fix wording * Add modals for wallet replace and removal
This commit is contained in:
parent
6d317937c7
commit
06db29dd43
36 changed files with 527 additions and 464 deletions
|
@ -23,7 +23,7 @@
|
|||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="90.0.4430.2400" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="92.0.4515.10700" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
@ -135,7 +135,8 @@ namespace BTCPayServer.Tests
|
|||
if (Driver.PageSource.Contains("id=\"ChangeWalletLink\""))
|
||||
{
|
||||
Driver.FindElement(By.Id("ChangeWalletLink")).Click();
|
||||
Driver.FindElement(By.Id("continue")).Click();
|
||||
Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("REPLACE");
|
||||
Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
}
|
||||
|
||||
if (isImport)
|
||||
|
|
|
@ -85,7 +85,8 @@ namespace BTCPayServer.Tests
|
|||
Assert.True(passEl.Displayed);
|
||||
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
|
||||
s.Driver.FindElement(By.Id("delete")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.FindAlertMessage();
|
||||
seedEl = s.Driver.FindElement(By.Id("Seed"));
|
||||
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
|
||||
|
@ -249,12 +250,14 @@ namespace BTCPayServer.Tests
|
|||
|
||||
// Let's try to disable it now
|
||||
s.Driver.FindElement(By.Id("disable")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DISABLE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
policies = await settings.GetSettingAsync<PoliciesSettings>();
|
||||
Assert.True(policies.DisableSSHService);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
|
||||
Assert.True(s.Driver.PageSource.Contains("404 - Page not found", StringComparison.OrdinalIgnoreCase));
|
||||
policies.DisableSSHService = false;
|
||||
await settings.UpdateSetting(policies);
|
||||
}
|
||||
|
@ -296,7 +299,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
// Cleanup old test run
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
}
|
||||
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
|
@ -323,7 +326,7 @@ namespace BTCPayServer.Tests
|
|||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns"));
|
||||
Assert.Contains("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
Assert.DoesNotContain("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
|
||||
|
@ -427,8 +430,9 @@ namespace BTCPayServer.Tests
|
|||
s.Logout();
|
||||
s.LogIn(alice);
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Remove")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Delete")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
s.Driver.Navigate().GoToUrl(storeUrl);
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
|
@ -674,7 +678,8 @@ namespace BTCPayServer.Tests
|
|||
var deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Equal(2, deletes.Count);
|
||||
deletes[0].Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmInput")).SendKeys("DELETE");
|
||||
s.Driver.FindElement(By.Id("ConfirmContinue")).Click();
|
||||
deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Single(deletes);
|
||||
s.FindAlertMessage();
|
||||
|
@ -761,7 +766,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
s.Driver.ToggleCollapse("danger-zone");
|
||||
s.Driver.FindElement(By.Id("delete-store")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.WaitForElement(By.Id("ConfirmContinue")).Click();
|
||||
s.FindAlertMessage();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace BTCPayServer.Controllers
|
|||
if (appData == null)
|
||||
return NotFound();
|
||||
if (await _AppService.DeleteApp(appData))
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App removed successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App deleted successfully.";
|
||||
return RedirectToAction(nameof(ListApps));
|
||||
}
|
||||
|
||||
|
@ -177,19 +177,13 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{appId}/delete")]
|
||||
[HttpGet("{appId}/delete")]
|
||||
public async Task<IActionResult> DeleteApp(string appId)
|
||||
{
|
||||
var appData = await GetOwnedApp(appId);
|
||||
if (appData == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = $"Delete app {appData.Name} ({appData.AppType})",
|
||||
Description = "This app will be removed from this store",
|
||||
Action = "Delete"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Delete app", $"The app <strong>{appData.Name}</strong> and its settings will be permanently deleted. Are you sure?", "Delete"));
|
||||
}
|
||||
|
||||
private Task<AppData> GetOwnedApp(string appId, AppType? type = null)
|
||||
|
@ -197,7 +191,6 @@ namespace BTCPayServer.Controllers
|
|||
return _AppService.GetAppDataIfOwner(GetUserId(), appId, type);
|
||||
}
|
||||
|
||||
|
||||
private string GetUserId()
|
||||
{
|
||||
return _UserManager.GetUserId(User);
|
||||
|
|
|
@ -34,34 +34,6 @@ namespace BTCPayServer.Controllers
|
|||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Disable2faWarning()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException(
|
||||
$"Unexpected error occurred disabling 2FA for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
return View("Confirm",
|
||||
new ConfirmModel()
|
||||
{
|
||||
Title = $"Disable two-factor authentication (2FA)",
|
||||
DescriptionHtml = true,
|
||||
Description =
|
||||
$"Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key used in an authenticator app you should <a href=\"{Url.Action(nameof(ResetAuthenticatorWarning))}\"> reset your authenticator keys</a>.",
|
||||
Action = "Disable 2FA",
|
||||
ActionUrl = Url.ActionLink(nameof(Disable2fa))
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Disable2fa()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
|
@ -134,21 +106,6 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToAction(nameof(GenerateRecoveryCodes), new {confirm = false});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult ResetAuthenticatorWarning()
|
||||
{
|
||||
return View("Confirm",
|
||||
new ConfirmModel()
|
||||
{
|
||||
Title = $"Reset authenticator key",
|
||||
Description =
|
||||
$"This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes.{Environment.NewLine}If you do not complete your authenticator app configuration you may lose access to your account.",
|
||||
Action = "Reset",
|
||||
ActionUrl = Url.ActionLink(nameof(ResetAuthenticator))
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ResetAuthenticator()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
|
@ -164,25 +121,6 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToAction(nameof(EnableAuthenticator));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateRecoveryCodes(bool confirm = true)
|
||||
{
|
||||
if (!confirm)
|
||||
{
|
||||
return await GenerateRecoveryCodes();
|
||||
}
|
||||
|
||||
return View("Confirm",
|
||||
new ConfirmModel()
|
||||
{
|
||||
Title = $"Are you sure you want to generate new recovery codes?",
|
||||
Description = "Your existing recovery codes will no longer be valid!",
|
||||
Action = "Generate",
|
||||
ActionUrl = Url.ActionLink(nameof(GenerateRecoveryCodes))
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> GenerateRecoveryCodes()
|
||||
{
|
||||
var recoveryCodes = (string[])TempData[RecoveryCodesKey];
|
||||
|
|
|
@ -30,26 +30,26 @@ namespace BTCPayServer.Controllers
|
|||
});
|
||||
}
|
||||
|
||||
[HttpGet("api-keys/{id}/delete")]
|
||||
public async Task<IActionResult> RemoveAPIKey(string id)
|
||||
[HttpGet("~/api-keys/{id}/delete")]
|
||||
public async Task<IActionResult> DeleteAPIKey(string id)
|
||||
{
|
||||
var key = await _apiKeyRepository.GetKey(id);
|
||||
if (key == null || key.UserId != _userManager.GetUserId(User))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return View("Confirm", new ConfirmModel()
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Title = $"Delete API Key {(string.IsNullOrEmpty(key.Label) ? string.Empty : key.Label)}",
|
||||
Title = "Delete API key",
|
||||
DescriptionHtml = true,
|
||||
Description = $"Any application using this API key will immediately lose access: <code>{key.Id}</code>",
|
||||
Description = $"Any application using the API key <strong>{key.Label ?? key.Id}<strong> will immediately lose access.",
|
||||
Action = "Delete",
|
||||
ActionUrl = Url.ActionLink(nameof(RemoveAPIKeyPost), values: new { id })
|
||||
ActionUrl = Url.ActionLink(nameof(DeleteAPIKeyPost), values: new { id })
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("api-keys/{id}/delete")]
|
||||
public async Task<IActionResult> RemoveAPIKeyPost(string id)
|
||||
[HttpPost("~/api-keys/{id}/delete")]
|
||||
public async Task<IActionResult> DeleteAPIKeyPost(string id)
|
||||
{
|
||||
var key = await _apiKeyRepository.GetKey(id);
|
||||
if (key == null || key.UserId != _userManager.GetUserId(User))
|
||||
|
|
|
@ -194,7 +194,7 @@ namespace BTCPayServer.Controllers
|
|||
return View(model);
|
||||
}
|
||||
|
||||
[Route("server/users/{userId}/delete")]
|
||||
[HttpGet("server/users/{userId}/delete")]
|
||||
public async Task<IActionResult> DeleteUser(string userId)
|
||||
{
|
||||
var user = userId == null ? null : await _UserManager.FindByIdAsync(userId);
|
||||
|
@ -208,24 +208,19 @@ namespace BTCPayServer.Controllers
|
|||
if (admins.Count == 1)
|
||||
{
|
||||
// return
|
||||
return View("Confirm", new ConfirmModel("Unable to Delete Last Admin",
|
||||
"This is the last Admin, so it can't be removed"));
|
||||
return View("Confirm", new ConfirmModel("Delete admin",
|
||||
"Unable to proceed: As the user <strong>{user.Email}</strong> is the last admin, it cannot be removed."));
|
||||
}
|
||||
|
||||
return View("Confirm", new ConfirmModel("Delete Admin " + user.Email,
|
||||
"Are you sure you want to delete this Admin and delete all accounts, users and data associated with the server account?",
|
||||
return View("Confirm", new ConfirmModel("Delete admin",
|
||||
$"The admin <strong>{user.Email}</strong> will be permanently deleted. This action will also delete all accounts, users and data associated with the server account. Are you sure?",
|
||||
"Delete"));
|
||||
}
|
||||
else
|
||||
{
|
||||
return View("Confirm", new ConfirmModel("Delete user " + user.Email,
|
||||
"This user will be permanently deleted",
|
||||
"Delete"));
|
||||
}
|
||||
|
||||
return View("Confirm", new ConfirmModel("Delete user", $"The user <strong>{user.Email}</strong> will be permanently deleted. Are you sure?", "Delete"));
|
||||
}
|
||||
|
||||
[Route("server/users/{userId}/delete")]
|
||||
[HttpPost]
|
||||
[HttpPost("server/users/{userId}/delete")]
|
||||
public async Task<IActionResult> DeleteUserPost(string userId)
|
||||
{
|
||||
var user = userId == null ? null : await _UserManager.FindByIdAsync(userId);
|
||||
|
|
|
@ -546,20 +546,13 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("server/services/{serviceName}/{cryptoCode}/removelndseed")]
|
||||
[HttpGet("server/services/{serviceName}/{cryptoCode}/removelndseed")]
|
||||
public IActionResult RemoveLndSeed(string serviceName, string cryptoCode)
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete LND Seed",
|
||||
Description = "Please make sure you made a backup of the seed and password before deleting the LND backup seed from the server, are you sure to continue?",
|
||||
Action = "Delete"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Delete LND seed", "This action will permanently delete your LND seed and password. You will not be able to recover them if you don't have a backup. Are you sure?", "Delete"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("server/services/{serviceName}/{cryptoCode}/removelndseed")]
|
||||
[HttpPost("server/services/{serviceName}/{cryptoCode}/removelndseed")]
|
||||
public async Task<IActionResult> RemoveLndSeedPost(string serviceName, string cryptoCode)
|
||||
{
|
||||
var service = GetService(serviceName, cryptoCode);
|
||||
|
@ -819,23 +812,20 @@ namespace BTCPayServer.Controllers
|
|||
this.RouteData.Values.Remove(nameof(hostname));
|
||||
return RedirectToAction(nameof(DynamicDnsServices));
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("server/services/dynamic-dns/{hostname}/delete")]
|
||||
|
||||
[HttpGet("server/services/dynamic-dns/{hostname}/delete")]
|
||||
public async Task<IActionResult> DeleteDynamicDnsService(string hostname)
|
||||
{
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
var settings = await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>() ?? new DynamicDnsSettings();
|
||||
var i = settings.Services.FindIndex(d => d.Hostname.Equals(hostname, StringComparison.OrdinalIgnoreCase));
|
||||
if (i == -1)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete the dynamic dns service for " + hostname,
|
||||
Description = "BTCPayServer will stop updating this DNS record periodically",
|
||||
Action = "Delete"
|
||||
});
|
||||
return View("Confirm",
|
||||
new ConfirmModel("Delete dynamic DNS service",
|
||||
$"Deleting the dynamic DNS service for <strong>{hostname}</strong> means your BTCPay Server will stop updating the associated DNS record periodically.", "Delete"));
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("server/services/dynamic-dns/{hostname}/delete")]
|
||||
|
||||
[HttpPost("server/services/dynamic-dns/{hostname}/delete")]
|
||||
public async Task<IActionResult> DeleteDynamicDnsServicePost(string hostname)
|
||||
{
|
||||
var settings = (await _SettingsRepository.GetSettingAsync<DynamicDnsSettings>()) ?? new DynamicDnsSettings();
|
||||
|
@ -845,11 +835,11 @@ namespace BTCPayServer.Controllers
|
|||
settings.Services.RemoveAt(i);
|
||||
await _SettingsRepository.UpdateSetting(settings);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Dynamic DNS service successfully removed";
|
||||
this.RouteData.Values.Remove(nameof(hostname));
|
||||
RouteData.Values.Remove(nameof(hostname));
|
||||
return RedirectToAction(nameof(DynamicDnsServices));
|
||||
}
|
||||
|
||||
[Route("server/services/ssh")]
|
||||
[HttpGet("server/services/ssh")]
|
||||
public async Task<IActionResult> SSHService()
|
||||
{
|
||||
if (!await CanShowSSHService())
|
||||
|
@ -902,8 +892,7 @@ namespace BTCPayServer.Controllers
|
|||
return _Options.SSHSettings?.AuthorizedKeysFile != null && System.IO.File.Exists(_Options.SSHSettings.AuthorizedKeysFile);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("server/services/ssh")]
|
||||
[HttpPost("server/services/ssh")]
|
||||
public async Task<IActionResult> SSHService(SSHServiceViewModel viewModel, string command = null)
|
||||
{
|
||||
if (!await CanShowSSHService())
|
||||
|
@ -959,26 +948,22 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
return RedirectToAction(nameof(SSHService));
|
||||
}
|
||||
else if (command is "disable")
|
||||
|
||||
if (command is "disable")
|
||||
{
|
||||
return RedirectToAction(nameof(SSHServiceDisable));
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[Route("server/services/ssh/disable")]
|
||||
[HttpGet("server/services/ssh/disable")]
|
||||
public IActionResult SSHServiceDisable()
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Disable",
|
||||
Title = "Disable modification of SSH settings",
|
||||
Description = "This action is permanent and will remove the ability to change the SSH settings via the BTCPay Server user interface.",
|
||||
ButtonClass = "btn-danger"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Disable modification of SSH settings", "This action is permanent and will remove the ability to change the SSH settings via the BTCPay Server user interface.", "Disable"));
|
||||
}
|
||||
[Route("server/services/ssh/disable")]
|
||||
[HttpPost]
|
||||
|
||||
[HttpPost("server/services/ssh/disable")]
|
||||
public async Task<IActionResult> SSHServiceDisablePost()
|
||||
{
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
|
|
|
@ -66,12 +66,7 @@ namespace BTCPayServer.Controllers
|
|||
if (webhook is null)
|
||||
return NotFound();
|
||||
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Title = $"Delete a webhook",
|
||||
Description = "This webhook will be removed from this store, do you wish to continue?",
|
||||
Action = "Delete"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Delete webhook", "This webhook will be removed from this store. Are you sure?", "Delete"));
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/webhooks/{webhookId}/remove")]
|
||||
|
|
|
@ -398,6 +398,9 @@ namespace BTCPayServer.Controllers
|
|||
vm.Config = ProtectString(derivation.ToJson());
|
||||
vm.IsHotWallet = derivation.IsHotWallet;
|
||||
|
||||
ViewData["ReplaceDescription"] = WalletReplaceWarning(derivation.IsHotWallet);
|
||||
ViewData["RemoveDescription"] = WalletRemoveWarning(derivation.IsHotWallet, network.CryptoCode);
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
@ -411,20 +414,11 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
|
||||
var walletType = derivation.IsHotWallet ? "hot" : "watch-only";
|
||||
var additionalText = derivation.IsHotWallet
|
||||
? ""
|
||||
: " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet";
|
||||
var description =
|
||||
$"<p class=\"text-danger fw-bold\">Please note that this is a {walletType} wallet!</p>" +
|
||||
$"<p class=\"text-danger fw-bold\">Do not replace the wallet if you have not backed it up{additionalText}.</p>" +
|
||||
"<p class=\"text-start mb-0\">Replacing the wallet will erase the current wallet data from the server. " +
|
||||
"The current wallet will be replaced once you finish the setup of the new wallet. If you cancel the setup, the current wallet will stay active .</p>";
|
||||
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Title = $"Replace {network.CryptoCode} wallet",
|
||||
Description = description,
|
||||
Description = WalletReplaceWarning(derivation.IsHotWallet),
|
||||
DescriptionHtml = true,
|
||||
Action = "Setup new wallet"
|
||||
});
|
||||
|
@ -458,20 +452,11 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
|
||||
var walletType = derivation.IsHotWallet ? "hot" : "watch-only";
|
||||
var additionalText = derivation.IsHotWallet
|
||||
? ""
|
||||
: " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet";
|
||||
var description =
|
||||
$"<p class=\"text-danger fw-bold\">Please note that this is a {walletType} wallet!</p>" +
|
||||
$"<p class=\"text-danger fw-bold\">Do not remove the wallet if you have not backed it up{additionalText}.</p>" +
|
||||
"<p class=\"text-start mb-0\">Removing the wallet will erase the wallet data from the server. " +
|
||||
$"The store won't be able to receive {network.CryptoCode} onchain payments until a new wallet is set up.</p>";
|
||||
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Title = $"Remove {network.CryptoCode} wallet",
|
||||
Description = description,
|
||||
Description = WalletRemoveWarning(derivation.IsHotWallet, network.CryptoCode),
|
||||
DescriptionHtml = true,
|
||||
Action = "Remove"
|
||||
});
|
||||
|
@ -595,5 +580,30 @@ namespace BTCPayServer.Controllers
|
|||
return await stream.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private string WalletWarning(bool isHotWallet, string info)
|
||||
{
|
||||
var walletType = isHotWallet ? "hot" : "watch-only";
|
||||
var additionalText = isHotWallet
|
||||
? ""
|
||||
: " or imported it into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet";
|
||||
return
|
||||
$"<p class=\"text-danger fw-bold\">Please note that this is a {walletType} wallet!</p>" +
|
||||
$"<p class=\"text-danger fw-bold\">Do not proceed if you have not backed up the wallet{additionalText}.</p>" +
|
||||
$"<p class=\"text-start mb-0\">This action will erase the current wallet data from the server. {info}</p>";
|
||||
}
|
||||
|
||||
private string WalletReplaceWarning(bool isHotWallet)
|
||||
{
|
||||
return WalletWarning(isHotWallet,
|
||||
"The current wallet will be replaced once you finish the setup of the new wallet. " +
|
||||
"If you cancel the setup, the current wallet will stay active.");
|
||||
}
|
||||
|
||||
private string WalletRemoveWarning(bool isHotWallet, string cryptoCode)
|
||||
{
|
||||
return WalletWarning(isHotWallet,
|
||||
$"The store won't be able to receive {cryptoCode} onchain payments until a new wallet is set up.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,37 +184,28 @@ namespace BTCPayServer.Controllers
|
|||
ModelState.AddModelError(nameof(vm.Email), "The user already has access to this store");
|
||||
return View(vm);
|
||||
}
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User added successfully";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User added successfully.";
|
||||
return RedirectToAction(nameof(StoreUsers));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/users/{userId}/delete")]
|
||||
[HttpGet("{storeId}/users/{userId}/delete")]
|
||||
public async Task<IActionResult> DeleteStoreUser(string userId)
|
||||
{
|
||||
StoreUsersViewModel vm = new StoreUsersViewModel();
|
||||
var user = await _UserManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = $"Remove store user",
|
||||
Description = $"Are you sure you want to remove store access for {user.Email}?",
|
||||
Action = "Delete"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Remove store user", $"This action will prevent <strong>{user.Email}</strong> from accessing this store and its settings. Are you sure?", "Remove"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/users/{userId}/delete")]
|
||||
[HttpPost("{storeId}/users/{userId}/delete")]
|
||||
public async Task<IActionResult> DeleteStoreUserPost(string storeId, string userId)
|
||||
{
|
||||
await _Repo.RemoveStoreUser(storeId, userId);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User removed successfully";
|
||||
return RedirectToAction(nameof(StoreUsers), new { storeId = storeId, userId = userId });
|
||||
TempData[WellKnownTempData.SuccessMessage] = "User removed successfully.";
|
||||
return RedirectToAction(nameof(StoreUsers), new { storeId, userId });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/rates")]
|
||||
[HttpGet("{storeId}/rates")]
|
||||
public IActionResult Rates()
|
||||
{
|
||||
var exchanges = GetSupportedExchanges();
|
||||
|
@ -231,8 +222,7 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/rates")]
|
||||
[HttpPost("{storeId}/rates")]
|
||||
public async Task<IActionResult> Rates(RatesViewModel model, string command = null, string storeId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (command == "scripting-on")
|
||||
|
@ -350,11 +340,10 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/rates/confirm")]
|
||||
[HttpGet("{storeId}/rates/confirm")]
|
||||
public IActionResult ShowRateRules(bool scripting)
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Action = "Continue",
|
||||
Title = "Rate rule scripting",
|
||||
|
@ -365,8 +354,7 @@ namespace BTCPayServer.Controllers
|
|||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/rates/confirm")]
|
||||
[HttpPost("{storeId}/rates/confirm")]
|
||||
public async Task<IActionResult> ShowRateRulesPost(bool scripting)
|
||||
{
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
|
@ -374,7 +362,7 @@ namespace BTCPayServer.Controllers
|
|||
blob.RateScript = blob.GetDefaultRateRules(_NetworkProvider).ToString();
|
||||
CurrentStore.SetStoreBlob(blob);
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Rate rules scripting activated";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Rate rules scripting " + (scripting ? "activated" : "deactivated");
|
||||
return RedirectToAction(nameof(Rates), new { storeId = CurrentStore.Id });
|
||||
}
|
||||
|
||||
|
@ -666,25 +654,17 @@ namespace BTCPayServer.Controllers
|
|||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/delete")]
|
||||
[HttpGet("{storeId}/delete")]
|
||||
public IActionResult DeleteStore(string storeId)
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Delete",
|
||||
Title = "Delete this store",
|
||||
Description = "This action is irreversible and will remove all information related to this store. (Invoices, Apps etc...)",
|
||||
ButtonClass = "btn-danger"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Delete store", "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store. Are you sure?", "Delete"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/delete")]
|
||||
[HttpPost("{storeId}/delete")]
|
||||
public async Task<IActionResult> DeleteStorePost(string storeId)
|
||||
{
|
||||
await _Repo.DeleteStore(CurrentStore.Id);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully deleted";
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Store successfully deleted.";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
}
|
||||
|
||||
|
@ -745,31 +725,23 @@ namespace BTCPayServer.Controllers
|
|||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/tokens/{tokenId}/revoke")]
|
||||
[HttpGet("{storeId}/tokens/{tokenId}/revoke")]
|
||||
public async Task<IActionResult> RevokeToken(string tokenId)
|
||||
{
|
||||
var token = await _TokenRepository.GetToken(tokenId);
|
||||
if (token == null || token.StoreId != CurrentStore.Id)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Action = "Revoke",
|
||||
Title = "Revoke the token",
|
||||
Description = $"The access token with the label \"{token.Label}\" will be revoked, do you wish to continue?",
|
||||
ButtonClass = "btn-danger"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Revoke the token", $"The access token with the label <strong>{token.Label}</strong> will be revoked. Do you wish to continue?", "Revoke"));
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{storeId}/tokens/{tokenId}/revoke")]
|
||||
|
||||
[HttpPost("{storeId}/tokens/{tokenId}/revoke")]
|
||||
public async Task<IActionResult> RevokeTokenConfirm(string tokenId)
|
||||
{
|
||||
var token = await _TokenRepository.GetToken(tokenId);
|
||||
if (token == null ||
|
||||
token.StoreId != CurrentStore.Id ||
|
||||
!await _TokenRepository.DeleteToken(tokenId))
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Failure to revoke this token";
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Failure to revoke this token.";
|
||||
else
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Token revoked";
|
||||
return RedirectToAction(nameof(ListTokens), new { storeId = token.StoreId });
|
||||
|
|
|
@ -61,23 +61,16 @@ namespace BTCPayServer.Controllers
|
|||
get; set;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/me/delete")]
|
||||
[HttpGet("{storeId}/me/delete")]
|
||||
public IActionResult DeleteStore(string storeId)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Delete store " + store.StoreName,
|
||||
Description = "This store will still be accessible to users sharing it",
|
||||
Action = "Delete"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel($"Delete store {store.StoreName}", "This store will still be accessible to users sharing it", "Delete"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/me/delete")]
|
||||
[HttpPost("{storeId}/me/delete")]
|
||||
public async Task<IActionResult> DeleteStorePost(string storeId)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
|
|
|
@ -153,13 +153,7 @@ namespace BTCPayServer.Controllers
|
|||
WalletId walletId,
|
||||
string pullPaymentId)
|
||||
{
|
||||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = "Archive the pull payment",
|
||||
Description = "Do you really want to archive this pull payment?",
|
||||
ButtonClass = "btn-danger",
|
||||
Action = "Archive"
|
||||
});
|
||||
return View("Confirm", new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"));
|
||||
}
|
||||
|
||||
[HttpPost("{walletId}/pull-payments/{pullPaymentId}/archive")]
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
using System;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public class ConfirmModel
|
||||
{
|
||||
public ConfirmModel() { }
|
||||
private const string ButtonClassDefault = "btn-danger";
|
||||
|
||||
public ConfirmModel(string title, string desc, string action = null)
|
||||
public ConfirmModel() {}
|
||||
|
||||
public ConfirmModel(string title, string desc, string action = null, string buttonClass = ButtonClassDefault)
|
||||
{
|
||||
Title = title;
|
||||
Description = desc;
|
||||
Action = action;
|
||||
ButtonClass = buttonClass;
|
||||
|
||||
if (Description.Contains("<strong>", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
DescriptionHtml = true;
|
||||
}
|
||||
}
|
||||
|
||||
public string Title
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Description
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public bool DescriptionHtml { get; set; } = false;
|
||||
|
||||
public string Action
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string ButtonClass { get; set; } = "btn-danger";
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool DescriptionHtml { get; set; }
|
||||
public string Action { get; set; }
|
||||
public string ButtonClass { get; set; } = ButtonClassDefault;
|
||||
public string ActionUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
|||
{
|
||||
public class CreateTokenViewModel
|
||||
{
|
||||
[Display(Name = "Public Key")]
|
||||
[PubKeyValidatorAttribute]
|
||||
public string PublicKey
|
||||
{
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
<a asp-action="@app.ViewAction" asp-controller="AppsPublic" asp-route-appId="@app.Id" target="_blank"
|
||||
title="View in New Window"><span class="fa fa-external-link"></span></a>
|
||||
<span> - </span>
|
||||
<a asp-action="DeleteApp" asp-route-appId="@app.Id">Remove</a>
|
||||
<a asp-action="DeleteApp" asp-route-appId="@app.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The app <strong>@app.AppName</strong> and its settings will be permanently deleted from your store <strong>@app.StoreName</strong>." data-confirm-input="DELETE">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -114,3 +114,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Delete app", "This app will be removed from this store.", "Delete"))" />
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
<tbody>
|
||||
@foreach (var device in Model.Credentials)
|
||||
{
|
||||
var name = string.IsNullOrEmpty(device.Name) ? "Unnamed FIDO2 credential" : device.Name;
|
||||
<tr>
|
||||
<td>@(string.IsNullOrEmpty(device.Name)? "Unnamed FIDO2 credential": device.Name)</td>
|
||||
<td>@name</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="Remove" asp-route-id="@device.Id">Remove</a>
|
||||
<a asp-action="Remove" asp-route-id="@device.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="Your account will no longer have the credential <strong>@name</strong> as an option for multi-factor authentication." data-confirm-input="REMOVE">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -44,3 +45,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Remove FIDO2 credential", "Your account will no longer have the credential as an option for multi-factor authentication.", "Remove"))" />
|
||||
|
|
|
@ -50,14 +50,14 @@
|
|||
@foreach (var permission in Permission.ToPermissions(permissions).Select(c => c.ToString()).Distinct().ToArray())
|
||||
{
|
||||
<li>
|
||||
<code>@permission</code>
|
||||
<code class="text-break">@permission</code>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="RemoveAPIKey" asp-route-id="@keyData.Id" asp-controller="Manage">Remove</a>
|
||||
<a asp-action="DeleteAPIKey" asp-route-id="@keyData.Id" asp-controller="Manage" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="Any application using the API key <strong>@(keyData.Label ?? keyData.Id)<strong> will immediately lose access." data-confirm-input="DELETE">Delete</a>
|
||||
<span>-</span>
|
||||
<button type="button" class="btn btn-link only-for-js" data-qr="@index">Show QR</button>
|
||||
</td>
|
||||
|
@ -79,46 +79,47 @@
|
|||
Generate new key
|
||||
</a>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Delete API key", "Any application using the API key will immediately lose access.", "Delete"))" />
|
||||
|
||||
<partial name="ShowQR"/>
|
||||
|
||||
@section PageHeadContent {
|
||||
<link href="~/vendor/vue-qrcode-reader/vue-qrcode-reader.css" rel="stylesheet" asp-append-version="true"/>
|
||||
}
|
||||
|
||||
@section PageFootContent {
|
||||
<bundle name="wwwroot/bundles/camera-bundle.min.js"></bundle>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
$("[data-reveal-btn]").on("click", function (){
|
||||
var $revealButton = $(this);
|
||||
$revealButton.attr("hidden", "true");
|
||||
|
||||
<bundle name="wwwroot/bundles/camera-bundle.min.js"></bundle>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
$("[data-reveal-btn]").on("click", function (){
|
||||
var $revealButton = $(this);
|
||||
$revealButton.attr("hidden", "true");
|
||||
var $apiKeyContainer = $revealButton.next("[hidden]");
|
||||
$apiKeyContainer.removeAttr("hidden");
|
||||
|
||||
var $apiKeyContainer = $revealButton.next("[hidden]");
|
||||
$apiKeyContainer.removeAttr("hidden");
|
||||
(function setupCopyToClipboardButton() {
|
||||
var $clipboardBtn = $apiKeyContainer.children("[data-clipboard-confirm]");
|
||||
var apiKey = $apiKeyContainer.children("[data-api-key]").text().trim();
|
||||
$clipboardBtn.attr("data-clipboard", apiKey);
|
||||
$clipboardBtn.click(window.copyToClipboard);
|
||||
})();
|
||||
});
|
||||
|
||||
(function setupCopyToClipboardButton() {
|
||||
var $clipboardBtn = $apiKeyContainer.children("[data-clipboard-confirm]");
|
||||
var apiKey = $apiKeyContainer.children("[data-api-key]").text().trim();
|
||||
$clipboardBtn.attr("data-clipboard", apiKey);
|
||||
$clipboardBtn.click(window.copyToClipboard);
|
||||
})();
|
||||
var apiKeys = @Safe.Json(Model.ApiKeyDatas.Select(data => new
|
||||
{
|
||||
ApiKey = data.Id,
|
||||
Host = Context.Request.GetAbsoluteRoot()
|
||||
}));
|
||||
var qrApp = initQRShow("API Key QR", "", "scan-qr-modal");
|
||||
$("button[data-qr]").on("click", function (){
|
||||
var data = apiKeys[parseInt($(this).data("qr"))];
|
||||
qrApp.data = JSON.stringify(data);
|
||||
qrApp.currentMode = "static";
|
||||
qrApp.allowedModes = ["static"];
|
||||
$("#scan-qr-modal").modal("show");
|
||||
});
|
||||
});
|
||||
|
||||
var apiKeys = @Safe.Json(Model.ApiKeyDatas.Select(data => new
|
||||
{
|
||||
ApiKey = data.Id,
|
||||
Host = Context.Request.GetAbsoluteRoot()
|
||||
}));
|
||||
var qrApp = initQRShow("API Key QR", "", "scan-qr-modal");
|
||||
$("button[data-qr]").on("click", function (){
|
||||
var data = apiKeys[parseInt($(this).data("qr"))];
|
||||
qrApp.data = JSON.stringify(data);
|
||||
qrApp.currentMode = "static";
|
||||
qrApp.allowedModes = ["static"];
|
||||
$("#scan-qr-modal").modal("show");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
}
|
||||
|
||||
<partial name="ShowQR"/>
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
ViewData.SetActivePageAndTitle(ManageNavPages.TwoFactorAuthentication, "Two-factor authentication");
|
||||
}
|
||||
|
||||
@if(Model.Is2faEnabled)
|
||||
@if (Model.Is2faEnabled)
|
||||
{
|
||||
if(Model.RecoveryCodesLeft == 0)
|
||||
if (Model.RecoveryCodesLeft == 0)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<h4 class="alert-heading mb-3">
|
||||
|
@ -15,7 +15,7 @@
|
|||
<p class="mb-0">You must <a asp-action="GenerateRecoveryCodes" class="alert-link">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
|
||||
</div>
|
||||
}
|
||||
else if(Model.RecoveryCodesLeft == 1)
|
||||
else if (Model.RecoveryCodesLeft == 1)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<h4 class="alert-heading mb-3">
|
||||
|
@ -25,7 +25,7 @@
|
|||
<p class="mb-0">You can <a asp-action="GenerateRecoveryCodes" class="alert-link">generate a new set of recovery codes</a>.</p>
|
||||
</div>
|
||||
}
|
||||
else if(Model.RecoveryCodesLeft <= 3)
|
||||
else if (Model.RecoveryCodesLeft <= 3)
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
<h4 class="alert-heading mb-3">
|
||||
|
@ -40,48 +40,46 @@
|
|||
<div class="list-group">
|
||||
@if (Model.Is2faEnabled)
|
||||
{
|
||||
<a asp-action="Disable2faWarning" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3">
|
||||
<a asp-action="Disable2fa" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-title="Disable two-factor authentication (2FA)" data-description="Disabling 2FA does not change the keys used in the authenticator apps. If you wish to change the key used in an authenticator app you should reset your authenticator keys." data-confirm="Disable 2FA">
|
||||
<div>
|
||||
<h5 >Disable 2FA</h5>
|
||||
<h5>Disable 2FA</h5>
|
||||
<p class="mb-0 me-3">Disable two-factor authentication. Re-enabling will not require you to reconfigure your Authenticator app. </p>
|
||||
</div>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
<vc:icon symbol="caret-right" />
|
||||
</a>
|
||||
<a asp-action="GenerateRecoveryCodes" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3">
|
||||
<a asp-action="GenerateRecoveryCodes" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-title="Reset recovery codes" data-description="Your existing recovery codes will no longer be valid!" data-confirm="Reset">
|
||||
<div>
|
||||
<h5 >Reset recovery codes</h5>
|
||||
<h5>Reset recovery codes</h5>
|
||||
<p class="mb-0 me-3">Regenerate your two-factor recovery codes.</p>
|
||||
</div>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
<vc:icon symbol="caret-right" />
|
||||
</a>
|
||||
|
||||
<a asp-action="EnableAuthenticator" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3">
|
||||
<a asp-action="ResetAuthenticator" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-title="Reset authenticator app" data-description="This process disables 2FA until you verify your authenticator app and will also reset your 2FA recovery codes. If you do not complete your authenticator app configuration you may lose access to your account." data-confirm="Reset">
|
||||
<div>
|
||||
<h5 >Configure Authenticator app</h5>
|
||||
<p class="mb-0 me-3">Display the key or QR code to configure an authenticator app with your current setup.</p>
|
||||
</div>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
</a>
|
||||
<a asp-action="ResetAuthenticatorWarning" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3">
|
||||
<div>
|
||||
<h5 >Reset Authenticator app</h5>
|
||||
<h5>Reset authenticator app</h5>
|
||||
<p class="mb-0 me-3">Invalidates the current authenticator configuration. Useful if you believe your authenticator settings were compromised.</p>
|
||||
</div>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
<vc:icon symbol="caret-right" />
|
||||
</a>
|
||||
<a asp-action="EnableAuthenticator" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3">
|
||||
<div>
|
||||
<h5>Configure Authenticator app</h5>
|
||||
<p class="mb-0 me-3">Display the key or QR code to configure an authenticator app with your current setup.</p>
|
||||
</div>
|
||||
<vc:icon symbol="caret-right" />
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-action="EnableAuthenticator" class="list-group-item d-flex justify-content-between align-items-center list-group-item-action py-3">
|
||||
<div>
|
||||
<h5 >Enable 2FA</h5>
|
||||
<h5>Enable 2FA</h5>
|
||||
<p class="mb-0 me-3">Enable two-factor authentication using TOTP with apps such as Google Authenticator.</p>
|
||||
</div>
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
<vc:icon symbol="caret-right" />
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Remove 2FA credential", "Your account will no longer have the credential as an option for multi-factor authentication.", "Remove"))" />
|
||||
|
||||
|
|
|
@ -3,51 +3,78 @@
|
|||
ViewData.SetActivePageAndTitle(ServerNavPages.Services, "Dynamic DNS Settings");
|
||||
}
|
||||
|
||||
<h2 class="mb-4">@ViewData["PageTitle"]</h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-3">
|
||||
<h2 class="mb-0">
|
||||
@ViewData["PageTitle"]
|
||||
<small>
|
||||
<a href="https://docs.btcpayserver.org/Apps/" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
</small>
|
||||
</h2>
|
||||
<form method="post" asp-action="DynamicDnsService">
|
||||
<button id="AddDynamicDNS" class="btn btn-primary" type="submit"><span class="fa fa-plus"></span> Add service</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>
|
||||
Dynamic DNS service allows you to have a stable DNS name pointing to your server, even if your IP address change regulary. <br />
|
||||
This is recommended if you are hosting BTCPayServer at home and wish to have a clearnet HTTPS address to access your server.
|
||||
</span>
|
||||
Dynamic DNS allows you to have a stable DNS name pointing to your server, even if your IP address changes regulary.
|
||||
This is recommended if you are hosting BTCPay Server at home and wish to have a clearnet domain to access your server.
|
||||
</p>
|
||||
<p>
|
||||
Note that you need to properly configure your NAT and BTCPay Server installation to get the HTTPS certificate.
|
||||
See the documentation for <a href="https://docs.btcpayserver.org/DynamicDNS/" target="_blank" rel="noreferrer noopener">more information</a>.
|
||||
</p>
|
||||
<p>Note that you need to properly configure your NAT and BTCPayServer install to get HTTPS certificate. Check the documentation for <a href="https://docs.btcpayserver.org/DynamicDNS/" target="_blank" rel="noreferrer noopener">more information</a>.</p>
|
||||
</div>
|
||||
<form method="post" asp-action="DynamicDnsService">
|
||||
<button id="AddDynamicDNS" class="btn btn-primary" type="submit"><span class="fa fa-plus"></span> Add Dynamic DNS</button>
|
||||
</form>
|
||||
<table class="table table-hover table-responsive-md">
|
||||
<thead>
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hostname</th>
|
||||
<th>Last updated</th>
|
||||
<th style="text-align:center;">Enabled</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th class="text-center">Enabled</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var service in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>@service.Settings.Hostname</td>
|
||||
<td>@service.LastUpdated</td>
|
||||
<td style="text-align:center;">
|
||||
@if(service.Settings.Enabled)
|
||||
{
|
||||
<span class="text-success fa fa-check"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger fa fa-times"></span>
|
||||
}
|
||||
<td class="text-center">
|
||||
@if (service.Settings.Enabled)
|
||||
{
|
||||
<span class="text-success fa fa-check"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger fa fa-times"></span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="DynamicDnsService" asp-route-hostname="@service.Settings.Hostname">Edit</a>
|
||||
<span> - </span>
|
||||
<a asp-action="DeleteDynamicDnsService" asp-route-hostname="@service.Settings.Hostname" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="Deleting the dynamic DNS service for <strong>@service.Settings.Hostname</strong> means your BTCPay Server will stop updating the associated DNS record periodically." data-confirm-input="DELETE">Delete</a>
|
||||
</td>
|
||||
<td style="text-align:right"><a asp-action="DynamicDnsService" asp-route-hostname="@service.Settings.Hostname">Edit</a> <span> - </span> <a asp-action="DeleteDynamicDnsService" asp-route-hostname="@service.Settings.Hostname">Remove</a></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-secondary mt-3">
|
||||
There are no dynamic DNS services yet.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Delete dynamic DNS service", "Deleting the dynamic DNS service means your BTCPay Server will stop updating the associated DNS record periodically.", "Delete"))" />
|
||||
|
||||
|
|
|
@ -14,14 +14,22 @@
|
|||
<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>
|
||||
<a class="btn btn-primary @(Model.Removed ? "collapse" : "")" id="details" href="#">See confidential seed information</a>
|
||||
<div class="form-group @(Model.Removed ? "" : "collapse")">
|
||||
<div class="input-group">
|
||||
<label asp-for="Seed" class="input-group-text"><span class="input-group-addon fa fa-eye"></span><span class="ms-2">Seed</span></label>
|
||||
<textarea asp-for="Seed" onClick="this.select();" class="form-control" readonly rows="@(Model.Removed ? "1" : "3")"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
@if (!Model.Removed)
|
||||
|
||||
@if (Model.Removed)
|
||||
{
|
||||
<div class="alert alert-light d-flex align-items-center" role="alert">
|
||||
<vc:icon symbol="warning" />
|
||||
<span class="ms-3" id="Seed">@Model.Seed</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group @(Model.Removed ? "" : "collapse")">
|
||||
<div class="input-group">
|
||||
<label asp-for="Seed" class="input-group-text"><span class="input-group-addon fa fa-eye"></span><span class="ms-2">Seed</span></label>
|
||||
<textarea asp-for="Seed" onClick="this.select();" class="form-control" readonly rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group collapse">
|
||||
<div class="input-group">
|
||||
<label asp-for="WalletPassword" class="input-group-text"><span class="input-group-addon fa fa-lock"></span><span class="ms-2">Password</span></label>
|
||||
|
@ -30,7 +38,7 @@
|
|||
</div>
|
||||
<div class="form-group collapse">
|
||||
<form method="get" asp-action="RemoveLndSeed" asp-route-serviceName="@Context.GetRouteValue("serviceName")" asp-route-cryptoCode="@Context.GetRouteValue("cryptoCode")">
|
||||
<button id="delete" class="btn btn-primary" type="submit">Remove Seed from server</button>
|
||||
<button id="delete" class="btn btn-primary" type="submit" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-confirm-input="DELETE">Delete LND seed from server</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
@ -38,8 +46,18 @@
|
|||
</div>
|
||||
}
|
||||
|
||||
@if (!Model.Removed)
|
||||
{
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Delete LND seed", "This action will permanently delete your LND seed and password. You will not be able to recover them if you don't have a backup.", "Delete"))"/>
|
||||
}
|
||||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
const deleteButton = document.getElementById('delete')
|
||||
deleteButton.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
document.getElementById("details").addEventListener("click", function () {
|
||||
document.querySelectorAll(".form-group.collapse").forEach(el => el.classList.remove("collapse"));
|
||||
|
|
|
@ -68,13 +68,25 @@
|
|||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>Increase the security of your instance by disabling the ability to change the SSH Settings in this BTCPay Server instance's user interface.<br /></span>
|
||||
<span>Increase the security of your instance by disabling the ability to change the SSH settings in this BTCPay Server instance's user interface.<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<form method="post">
|
||||
<button name="command" id="disable" type="submit" class="btn btn-outline-danger mb-5" value="disable">Disable</button>
|
||||
<button name="command" id="disable" type="submit" class="btn btn-outline-danger mb-5" value="disable" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-confirm-input="DISABLE">Disable</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Disable modification of SSH settings", "This action is permanent and will remove the ability to change the SSH settings via the BTCPay Server user interface.", "Disable"))"/>
|
||||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
const disableButton = document.getElementById('disable')
|
||||
disableButton.dataset.action = window.location.href + '/disable'
|
||||
disableButton.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
|
|
@ -2,41 +2,23 @@
|
|||
|
||||
@{
|
||||
ViewData["Title"] = Model.Title;
|
||||
Layout = null;
|
||||
Layout = "_LayoutSimple";
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<partial name="LayoutHead" />
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="modal-dialog modal-dialog-centered min-vh-100">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title w-100 text-center">@Model.Title</h4>
|
||||
</div>
|
||||
@section PageHeadContent {
|
||||
<style>
|
||||
body > .content-wrapper { display: flex; min-height: 100vh; }
|
||||
.modal-dialog .btn-close { display: none; }
|
||||
</style>
|
||||
}
|
||||
|
||||
<div class="modal-body text-center">
|
||||
@if (Model.DescriptionHtml)
|
||||
{
|
||||
@Safe.Raw(Model.Description)
|
||||
}
|
||||
else
|
||||
{
|
||||
@Model.Description
|
||||
}
|
||||
</div>
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
document.getElementById('ConfirmCancel').addEventListener('click', function () {
|
||||
history.back();
|
||||
return false;
|
||||
})
|
||||
</script>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Action))
|
||||
{
|
||||
<form method="post" class="modal-footer justify-content-center" action="@Model.ActionUrl" rel="noreferrer noopener">
|
||||
<button type="submit" class="btn @Model.ButtonClass xmx-2" id="continue" style="min-width:25%;">@Model.Action</button>
|
||||
<button type="submit" class="btn btn-secondary mx-2" onclick="history.back(); return false;" style="min-width:25%;">Go back</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<partial name="LayoutFoot" />
|
||||
</body>
|
||||
</html>
|
||||
<partial name="ConfirmModal" model="Model" />
|
||||
|
|
39
BTCPayServer/Views/Shared/ConfirmModal.cshtml
Normal file
39
BTCPayServer/Views/Shared/ConfirmModal.cshtml
Normal file
|
@ -0,0 +1,39 @@
|
|||
@model ConfirmModel
|
||||
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="ConfirmTitle">@Model.Title</h4>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<vc:icon symbol="close" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div id="ConfirmDescription">
|
||||
@if (Model.DescriptionHtml)
|
||||
{
|
||||
@Safe.Raw(Model.Description)
|
||||
}
|
||||
else
|
||||
{
|
||||
@Model.Description
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.Action))
|
||||
{
|
||||
<form id="ConfirmForm" method="post" action="@Model.ActionUrl" rel="noreferrer noopener">
|
||||
<div class="modal-body pt-0" id="ConfirmText" hidden>
|
||||
<label for="ConfirmInput" class="form-label">Confirm the action by typing <strong id="ConfirmInputText"></strong>:</label>
|
||||
<input id="ConfirmInput" class="form-control"/>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary only-for-js" data-bs-dismiss="modal" id="ConfirmCancel">Cancel</button>
|
||||
<button type="submit" class="btn @Model.ButtonClass" id="ConfirmContinue">@Model.Action</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
41
BTCPayServer/Views/Shared/_Confirm.cshtml
Normal file
41
BTCPayServer/Views/Shared/_Confirm.cshtml
Normal file
|
@ -0,0 +1,41 @@
|
|||
@model ConfirmModel
|
||||
|
||||
<div class="modal fade" id="ConfirmModal" tabindex="-1" aria-labelledby="ConfirmTitle" aria-hidden="true">
|
||||
<partial name="ConfirmModal" model="Model" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const modal = document.getElementById('ConfirmModal')
|
||||
modal.addEventListener('show.bs.modal', event => {
|
||||
const $target = event.relatedTarget
|
||||
const $form = document.getElementById('ConfirmForm')
|
||||
const $text = document.getElementById('ConfirmText')
|
||||
const $title = document.getElementById('ConfirmTitle')
|
||||
const $description = document.getElementById('ConfirmDescription')
|
||||
const $input = document.getElementById('ConfirmInput')
|
||||
const $inputText = document.getElementById('ConfirmInputText')
|
||||
const $continue = document.getElementById('ConfirmContinue')
|
||||
const { title, description, confirm, confirmInput } = $target.dataset
|
||||
const action = $target.dataset.action || ($target.nodeName === 'A'
|
||||
? $target.getAttribute('href')
|
||||
: $target.form.getAttribute('action'))
|
||||
|
||||
if ($form) $form.setAttribute('action', action)
|
||||
if (title) $title.textContent = title
|
||||
if (description) $description.innerHTML = description
|
||||
if (confirm) $continue.textContent = confirm
|
||||
if (confirmInput) {
|
||||
$text.removeAttribute('hidden')
|
||||
$continue.setAttribute('disabled', 'disabled')
|
||||
$inputText.textContent = confirmInput
|
||||
$input.addEventListener('input', event => {
|
||||
event.target.value.trim() === confirmInput
|
||||
? $continue.removeAttribute('disabled')
|
||||
: $continue.setAttribute('disabled', 'disabled')
|
||||
})
|
||||
} else {
|
||||
$text.setAttribute('hidden', 'hidden')
|
||||
$continue.removeAttribute('disabled')
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -46,7 +46,8 @@
|
|||
<tr>
|
||||
<td>@token.Label</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="ShowToken" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-tokenId="@token.Id">See information</a> - <a asp-action="RevokeToken" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-tokenId="@token.Id">Revoke</a>
|
||||
<a asp-action="ShowToken" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-tokenId="@token.Id">See information</a> -
|
||||
<a asp-action="RevokeToken" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-tokenId="@token.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The access token with the label <strong>@token.Label</strong> will be revoked." data-confirm-input="REVOKE">Revoke</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -83,3 +84,6 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Revoke access token", "The access token will be revoked. Do you wish to continue?", "Revoke"))" />
|
||||
|
||||
|
|
|
@ -47,9 +47,34 @@
|
|||
</form>
|
||||
<br>
|
||||
<form method="get" asp-controller="Stores" asp-action="DeleteWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" class="mt-5">
|
||||
<a asp-controller="Stores" asp-action="ReplaceWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode" id="ChangeWalletLink" class="btn btn-secondary me-2">
|
||||
<a asp-controller="Stores" asp-action="ReplaceWallet" asp-route-storeId="@Model.StoreId" asp-route-cryptoCode="@Model.CryptoCode"
|
||||
id="ChangeWalletLink"
|
||||
class="btn btn-secondary me-2"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#ConfirmModal"
|
||||
data-title="Replace @Model.CryptoCode wallet"
|
||||
data-description="@ViewData["ReplaceDescription"]"
|
||||
data-confirm="Setup new wallet"
|
||||
data-confirm-input="REPLACE">
|
||||
Replace wallet
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger" id="Delete">Remove wallet</button>
|
||||
<button type="submit" class="btn btn-danger" id="Delete"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#ConfirmModal"
|
||||
data-title="Remove @Model.CryptoCode wallet"
|
||||
data-description="@ViewData["RemoveDescription"]"
|
||||
data-confirm="Remove"
|
||||
data-confirm-input="REMOVE">Remove wallet</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel($"{Model.CryptoCode} wallet", "Change", "Update"))" />
|
||||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
const deleteButton = document.getElementById('Delete')
|
||||
deleteButton.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
|
|
@ -62,20 +62,20 @@
|
|||
<h5>Test results:</h5>
|
||||
<table class="table table-hover table-responsive-md">
|
||||
<tbody>
|
||||
@foreach (var result in Model.TestRateRules)
|
||||
{
|
||||
<tr>
|
||||
@if (result.Error)
|
||||
{
|
||||
<th class="small"><span class="text-danger fa fa-times"></span> @result.CurrencyPair</th>
|
||||
}
|
||||
else
|
||||
{
|
||||
<th class="small"><span class="text-success fa fa-check"></span> @result.CurrencyPair</th>
|
||||
}
|
||||
<td class="small">@result.Rule</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var result in Model.TestRateRules)
|
||||
{
|
||||
<tr>
|
||||
@if (result.Error)
|
||||
{
|
||||
<th class="small"><span class="text-danger fa fa-times"></span> @result.CurrencyPair</th>
|
||||
}
|
||||
else
|
||||
{
|
||||
<th class="small"><span class="text-success fa fa-check"></span> @result.CurrencyPair</th>
|
||||
}
|
||||
<td class="small">@result.Rule</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -147,7 +147,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<button type="submit" class="btn btn-secondary" value="scripting-off" name="command">Turn off advanced rate rule scripting</button>
|
||||
<button type="submit" class="btn btn-secondary" value="scripting-off" name="command" data-bs-toggle="modal" data-bs-target="#ConfirmModal">Turn off advanced rate rule scripting</button>
|
||||
</p>
|
||||
}
|
||||
else
|
||||
|
@ -161,7 +161,7 @@
|
|||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<button type="submit" class="btn btn-secondary" value="scripting-on" name="command">Turn on advanced rate rule scripting</button>
|
||||
<button type="submit" class="btn btn-secondary" value="scripting-on" name="command" data-bs-toggle="modal" data-bs-target="#ConfirmModal">Turn on advanced rate rule scripting</button>
|
||||
</p>
|
||||
}
|
||||
<div class="form-group">
|
||||
|
@ -191,9 +191,17 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Rate rule scripting", Model.ShowScripting ? "This action will delete your rate script. Are you sure to turn off rate rules scripting?" : "This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)", "Continue", Model.ShowScripting ? "btn-danger" : "btn-primary"))" />
|
||||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
var defaultScript = @Safe.Json(Model.DefaultScript);
|
||||
const defaultScript = @Safe.Json(Model.DefaultScript);
|
||||
|
||||
const commandButton = document.querySelector('[data-bs-target="#ConfirmModal"]')
|
||||
commandButton.dataset.action = window.location.href + '/confirm?scripting=@(!Model.ShowScripting)'
|
||||
commandButton.addEventListener('click', event => {
|
||||
event.preventDefault()
|
||||
});
|
||||
</script>
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
<partial name="_ValidationScriptsPartial"/>
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<td>@user.Email</td>
|
||||
<td>@user.Role</td>
|
||||
<td style="text-align:right">
|
||||
<a asp-action="DeleteStoreUser" asp-route-storeId="@Model.StoreId" asp-route-userId="@user.Id">Remove</a>
|
||||
<a asp-action="DeleteStoreUser" asp-route-storeId="@Model.StoreId" asp-route-userId="@user.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="This action will prevent <strong>@user.Email</strong> from accessing this store and its settings." data-confirm-input="REMOVE">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -60,3 +60,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Remove store user", "This action will prevent the user from accessing this store and its settings. Are you sure?", "Delete"))" />
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
|
||||
|
|
|
@ -308,12 +308,14 @@
|
|||
See more actions
|
||||
</button>
|
||||
<div id="danger-zone" class="collapse">
|
||||
<a id="delete-store" class="btn btn-outline-danger mb-5" asp-action="DeleteStore" asp-route-storeId="@Model.Id">Delete this store</a>
|
||||
<a id="delete-store" class="btn btn-outline-danger mb-5" asp-action="DeleteStore" asp-route-storeId="@Model.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@Model.StoreName</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store.">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"))" />
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
{
|
||||
<table class="table table-hover table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Url</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Url</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var wh in Model.Webhooks)
|
||||
|
@ -48,14 +48,16 @@
|
|||
</td>
|
||||
<td class="d-block text-break">@wh.Url</td>
|
||||
<td class="text-end text-md-nowrap">
|
||||
<a asp-action="TestWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Test</a> -
|
||||
<a asp-action="ModifyWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Modify</a> -
|
||||
<a asp-action="DeleteWebhook" asp-route-storeId="@this.Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Delete</a>
|
||||
<a asp-action="TestWebhook" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Test</a> -
|
||||
<a asp-action="ModifyWebhook" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id">Modify</a> -
|
||||
<a asp-action="DeleteWebhook" asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-webhookId="@wh.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-confirm-input="DELETE">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Delete webhook", "This webhook will be removed from this store.", "Delete"))" />
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -67,3 +69,4 @@ else
|
|||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
{
|
||||
<a asp-action="UpdateStore" asp-controller="Stores" asp-route-storeId="@store.Id" id="update-store-@store.Id">Settings</a><span> - </span>
|
||||
}
|
||||
<a asp-action="DeleteStore" asp-controller="Stores" asp-route-storeId="@store.Id">Remove</a>
|
||||
<a asp-action="DeleteStore" asp-controller="Stores" asp-route-storeId="@store.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@store.Name</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -93,3 +93,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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"))" />
|
||||
|
|
|
@ -55,47 +55,52 @@
|
|||
<div class="col-md-12">
|
||||
<table class="table table-hover table-responsive-lg">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th scope="col">Start</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Refunded</th>
|
||||
<th scope="col" class="text-end">Actions</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">Start</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Refunded</th>
|
||||
<th scope="col" class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var pp in Model.PullPayments)
|
||||
{
|
||||
<tr>
|
||||
<td>@pp.StartDate.ToBrowserDate()</td>
|
||||
<td>@pp.Name</td>
|
||||
<td class="align-middle">
|
||||
<div class="progress ppProgress" data-pp="@pp.Id" data-bs-toggle="tooltip" data-bs-html="true">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.CompletedPercent"
|
||||
aria-valuemin="0" aria-valuemax="100" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.CompletedPercent)%;">
|
||||
</div>
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.AwaitingPercent"
|
||||
aria-valuemin="0" aria-valuemax="100" style="background-color:orange; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.AwaitingPercent)%;">
|
||||
</div>
|
||||
@foreach (var pp in Model.PullPayments)
|
||||
{
|
||||
<tr>
|
||||
<td>@pp.StartDate.ToBrowserDate()</td>
|
||||
<td>@pp.Name</td>
|
||||
<td class="align-middle">
|
||||
<div class="progress ppProgress" data-pp="@pp.Id" data-bs-toggle="tooltip" data-bs-html="true">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.CompletedPercent"
|
||||
aria-valuemin="0" aria-valuemax="100" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.CompletedPercent)%;">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="ViewPullPayment"
|
||||
asp-controller="PullPayment"
|
||||
asp-route-pullPaymentId="@pp.Id">View</a> -
|
||||
<a class="pp-payout" asp-action="Payouts"
|
||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
asp-route-pullPaymentId="@pp.Id">Payouts</a> -
|
||||
<a asp-action="ArchivePullPayment"
|
||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
asp-route-pullPaymentId="@pp.Id">Archive</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="@pp.Progress.AwaitingPercent"
|
||||
aria-valuemin="0" aria-valuemax="100" style="background-color:orange; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width:@(pp.Progress.AwaitingPercent)%;">
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a asp-action="ViewPullPayment"
|
||||
asp-controller="PullPayment"
|
||||
asp-route-pullPaymentId="@pp.Id">View</a> -
|
||||
<a class="pp-payout" asp-action="Payouts"
|
||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
asp-route-pullPaymentId="@pp.Id">Payouts</a> -
|
||||
<a asp-action="ArchivePullPayment"
|
||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||
asp-route-pullPaymentId="@pp.Id"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#ConfirmModal"
|
||||
data-description="Do you really want to archive the pull payment <strong>@pp.Name</strong>?">Archive</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<partial name="_Confirm" model="@(new ConfirmModel("Archive pull payment", "Do you really want to archive the pull payment?", "Archive"))" />
|
||||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
var ppProgresses = document.getElementsByClassName("ppProgress");
|
||||
|
|
|
@ -5286,8 +5286,8 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.modal.fade .modal-dialog {
|
||||
transition: transform 0.3s ease-out;
|
||||
transform: translate(0, -50px);
|
||||
transition: transform 0.3s cubic-bezier(0.175, 0.855, 0.32, 1.275);
|
||||
transform: translate(0, -0.5rem);
|
||||
}
|
||||
|
||||
.modal.show .modal-dialog {
|
||||
|
@ -5346,7 +5346,7 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: 0.5;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
|
@ -6081,7 +6081,7 @@ fieldset:disabled .btn {
|
|||
}
|
||||
|
||||
.offcanvas-backdrop.show {
|
||||
opacity: 0.5;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.offcanvas-header {
|
||||
|
|
|
@ -251,6 +251,12 @@ h2 small .fa-question-circle-o {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
.list-group-item .icon-caret-right {
|
||||
flex: 0 0 24px;
|
||||
height: 24px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.account-form {
|
||||
max-width: 36em;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ body {
|
|||
|
||||
#wizard-navbar a {
|
||||
position: relative;
|
||||
color: var(--btcpay-body-color);
|
||||
color: var(--btcpay-body-text);
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -54,7 +54,7 @@ body {
|
|||
}
|
||||
|
||||
#wizard-navbar a:hover::after {
|
||||
background-color: var(--btcpay-border-color-medium);
|
||||
background-color: var(--btcpay-body-bg-hover);
|
||||
}
|
||||
|
||||
#wizard-navbar a:active::after {
|
||||
|
@ -108,8 +108,5 @@ body {
|
|||
}
|
||||
|
||||
.list-group-item .icon-caret-right {
|
||||
flex: 0 0 24px;
|
||||
height: 24px;
|
||||
align-self: center;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue