mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 21:32:27 +01:00
a962e60de9
* Store selector * Footer * Notifications * Checkout Appearance * Users list * Forms * Emails * Pay Button * Edit Dictionary * Remove newlines, fix typos * Forms * Pull payments and payouts * Various pages * Use local docs link * Fix * Even more translations * Fixes #6325 * Account pages * Notifications * Placeholders * Various pages and components * Add more
557 lines
31 KiB
Plaintext
557 lines
31 KiB
Plaintext
@using BTCPayServer.Configuration
|
|
@using BTCPayServer.Plugins
|
|
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
|
|
@inject BTCPayServerOptions BTCPayServerOptions
|
|
@inject PluginService PluginService
|
|
@{
|
|
Layout = "_Layout";
|
|
ViewData.SetActivePage(ServerNavPages.Plugins);
|
|
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier, plugin => plugin.Version);
|
|
var installedWithoutSystemPlugins = Model.Installed.Where(i => !i.SystemPlugin).ToList();
|
|
var availableAndNotInstalled = new List<PluginService.AvailablePlugin>();
|
|
var availableAndNotInstalledx = Model.Available
|
|
.Where(plugin => !installed.ContainsKey(plugin.Identifier))
|
|
.GroupBy(plugin => plugin.Identifier)
|
|
.ToList();
|
|
|
|
foreach (var availableAndNotInstalledItem in availableAndNotInstalledx)
|
|
{
|
|
var ordered = availableAndNotInstalledItem.OrderByDescending(plugin => plugin.Version).ToArray();
|
|
availableAndNotInstalled.Add(ordered.FirstOrDefault(availablePlugin => PluginManager.DependenciesMet(availablePlugin.Dependencies, installed)) ?? ordered.FirstOrDefault());
|
|
}
|
|
|
|
bool DependentOn(string plugin)
|
|
{
|
|
foreach (var installedPlugin in Model.Installed)
|
|
{
|
|
if (installedPlugin.Dependencies.Any(dep => dep.Identifier.Equals(plugin, StringComparison.InvariantCultureIgnoreCase)))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
var pendingInstalls = Model.Commands.Where(tuple => tuple.command != "uninstall").Select(tuple => tuple.plugin).Distinct();
|
|
foreach (var pendingInstall in pendingInstalls)
|
|
{
|
|
if (Model.Available.Any(availablePlugin => availablePlugin.Identifier.Equals(pendingInstall, StringComparison.InvariantCultureIgnoreCase) &&
|
|
availablePlugin.Dependencies.Any(dep => dep.Identifier.Equals(plugin, StringComparison.InvariantCultureIgnoreCase))))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
<style>
|
|
.version-switch .nav-link { display: inline; }
|
|
.version-switch .nav-link.active { display: none; }
|
|
</style>
|
|
|
|
<partial name="_StatusMessage" />
|
|
|
|
<div class="alert alert-warning alert-dismissible mb-4" role="alert">
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="@StringLocalizer["Close"]">
|
|
<vc:icon symbol="close" />
|
|
</button>
|
|
<h5 class="alert-heading" text-translate="true">Important notice about plugins</h5>
|
|
<p class="mb-0">
|
|
<span text-translate="true">Plugins are developed by third parties. They need to be updated and maintained regularly in addition to BTCPay Server. Use plugins at your own risk.</span>
|
|
<a href="#warning-details" data-bs-toggle="collapse" class="alert-link" text-translate="true">Read more</a>
|
|
</p>
|
|
<div class="collapse" id="warning-details">
|
|
<p class="my-3"><strong>Use at Your Own Risk:</strong> Plugins in this store are developed by independent third parties. These plugins have not undergone review by the BTCPay Server team.</p>
|
|
<p class="mb-3"><strong>Disclaimer of Responsibility:</strong> BTCPay Server contributors or Foundation are not liable for any harm, loss, or damage resulting from the installation or use of the plugins. Users assume full responsibility for their installation, use, familiarity with licensing and terms of service and maintenance.</p>
|
|
<p class="mb-3"><strong>No Official Endorsement:</strong> Inclusion in the list of BTCPay Server plugins does not constitute an endorsement or guarantee of quality, safety, or compatibility.</p>
|
|
<p class="mb-3"><strong>Due Diligence Advised:</strong> We recommend users exercise caution and conduct their own research or consult the community before installing any plugin.</p>
|
|
<p class="mb-0"><strong>Feedback and Reporting:</strong> Should you experience issues with a plugin, please provide feedback or report concerns directly to the respective plugin developers.</p>
|
|
</div>
|
|
</div>
|
|
|
|
@if (Model.Commands.Any())
|
|
{
|
|
<div class="alert alert-info mb-4 d-flex align-items-center justify-content-between">
|
|
<span text-translate="true">You need to restart BTCPay Server in order to update your active plugins.</span>
|
|
@if (Model.CanShowRestart)
|
|
{
|
|
<form method="post" asp-action="Maintenance" class="mt-2">
|
|
<button type="submit" name="command" value="soft-restart" class="btn btn-info" asp-action="Maintenance" text-translate="true">Restart now</button>
|
|
</form>
|
|
}
|
|
</div>
|
|
}
|
|
@if (Model.Disabled.Any())
|
|
{
|
|
<div class="alert alert-danger mb-4 d-flex align-items-center justify-content-between">
|
|
<span text-translate="true">Some plugins were disabled due to fatal errors. They may be incompatible with this version of BTCPay Server.</span>
|
|
<form asp-action="UnInstallAllDisabledPlugin" class="mt-2">
|
|
<button type="submit" name="command" value="soft-restart" class="btn btn-danger" text-translate="true">Uninstall all disabled plugins</button>
|
|
</form>
|
|
</div>
|
|
<div class="mb-5">
|
|
<h3 class="mb-4" text-translate="true">Disabled Plugins</h3>
|
|
<ul class="list-group list-group-flush d-inline-block">
|
|
@foreach (var (plugin, version) in Model.Disabled)
|
|
{
|
|
<li class="list-group-item px-0">
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
|
|
<span>
|
|
@plugin
|
|
@if (version != null)
|
|
{
|
|
<span>(@version)</span>
|
|
}
|
|
</span>
|
|
@{
|
|
var uninstalled = Model.Commands.Any(c => c.plugin == plugin && c.command == "delete");
|
|
}
|
|
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin">
|
|
@if (uninstalled)
|
|
{
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" disabled text-translate="true">Marked for deletion</button>
|
|
}
|
|
else
|
|
{
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" text-translate="true">Uninstall</button>
|
|
}
|
|
</form>
|
|
</div>
|
|
</li>
|
|
}
|
|
</ul>
|
|
</div>
|
|
}
|
|
|
|
@if (Model.Installed.Any())
|
|
{
|
|
<h3 class="mb-4">Installed Plugins</h3>
|
|
<div class="row mb-4">
|
|
@foreach (var plugin in Model.Installed.Where(i => !i.SystemPlugin))
|
|
{
|
|
Model.DownloadedPluginsByIdentifier.TryGetValue(plugin.Identifier, out var downloadInfo);
|
|
var matchedAvailable = Model.Available.Where(availablePlugin => availablePlugin.Identifier == plugin.Identifier && availablePlugin.Version > plugin.Version).OrderByDescending(availablePlugin => availablePlugin.Version).ToArray();
|
|
var x = matchedAvailable.FirstOrDefault(availablePlugin => PluginManager.DependenciesMet(availablePlugin.Dependencies, installed)) ?? matchedAvailable.FirstOrDefault();
|
|
var updateAvailable = matchedAvailable.Any();
|
|
var tabId = plugin.Identifier.ToLowerInvariant().Replace(".", "_");
|
|
<div class="col col-12 col-md-6 col-lg-12 col-xl-6 col-xxl-4 mb-4">
|
|
<div class="card h-100" id="@plugin.Identifier">
|
|
<div class="card-body">
|
|
<div class="d-flex flex-wrap align-items-baseline justify-content-between gap-2 mb-3">
|
|
<h4 class="card-title mb-0" data-bs-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
|
|
@if (!string.IsNullOrEmpty(downloadInfo?.Author))
|
|
{
|
|
<span class="text-muted text-nowrap">
|
|
by
|
|
<a href="@downloadInfo.AuthorLink" rel="noreferrer noopener" target="_blank">
|
|
@downloadInfo.Author
|
|
</a>
|
|
</span>
|
|
}
|
|
</div>
|
|
<div class="d-flex flex-wrap align-items-center mb-2 gap-3">
|
|
<h5 class="text-muted d-flex align-items-center mt-1 gap-3">
|
|
@plugin.Version
|
|
@if (updateAvailable && x != null)
|
|
{
|
|
<div class="badge bg-info">
|
|
@x.Version <span text-translate="true">available</span>
|
|
</div>
|
|
}
|
|
</h5>
|
|
@if (updateAvailable)
|
|
{
|
|
<span class="nav version-switch mt-n1" role="tablist">
|
|
<a data-bs-toggle="tab" href="#@tabId-current" class="nav-link text-info p-0 show active" text-translate="true">Show current info</a>
|
|
<a data-bs-toggle="tab" href="#@tabId-update" class="nav-link text-info p-0" text-translate="true">Show update info</a>
|
|
</span>
|
|
}
|
|
</div>
|
|
|
|
<div class="tab-content">
|
|
<div class="tab-pane active" id="@tabId-current">
|
|
<p class="card-text">@plugin.Description</p>
|
|
@if (plugin.Dependencies.Any())
|
|
{
|
|
<h6 class="text-muted fw-semibold" text-translate="true">Dependencies</h6>
|
|
<ul class="list-group list-group-flush">
|
|
@foreach (var dependency in plugin.Dependencies)
|
|
{
|
|
<li class="list-group-item p-2 d-inline-flex align-items-center gap-2">
|
|
@dependency
|
|
@if (!PluginManager.DependencyMet(dependency, installed))
|
|
{
|
|
<span title="Dependency not met." data-bs-toggle="tooltip" class="text-danger">
|
|
<vc:icon symbol="warning" />
|
|
</span>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
}
|
|
</div>
|
|
|
|
@if (updateAvailable && x != null)
|
|
{
|
|
<div class="tab-pane" id="@tabId-update">
|
|
<p class="card-text">@x.Description</p>
|
|
@if (x.Dependencies.Any())
|
|
{
|
|
<h6 class="text-muted fw-semibold" text-translate="true">Dependencies</h6>
|
|
<ul class="list-group list-group-flush">
|
|
@foreach (var dependency in x.Dependencies)
|
|
{
|
|
<li class="list-group-item p-2 d-inline-flex align-items-center gap-2">
|
|
@dependency
|
|
@if (!PluginManager.DependencyMet(dependency, installed))
|
|
{
|
|
<span title="Dependency not met." data-bs-toggle="tooltip" class="text-danger">
|
|
<vc:icon symbol="warning" />
|
|
</span>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@if (plugin != null)
|
|
{
|
|
<h6 class="text-muted fw-semibold mt-4" text-translate="true">Resources</h6>
|
|
<ul class="list-group list-group-flush list-unstyled">
|
|
@if (downloadInfo?.Source is not null)
|
|
{
|
|
<li>
|
|
<a href="@downloadInfo.Source" rel="noreferrer noopener" class="d-flex align-items-center" target="_blank">
|
|
<vc:icon symbol="social-github" />
|
|
<span style="margin-left:.4rem" text-translate="true">Sources</span>
|
|
</a>
|
|
</li>
|
|
}
|
|
@if (!string.IsNullOrEmpty(downloadInfo?.Documentation))
|
|
{
|
|
<li>
|
|
<a href="@downloadInfo.Documentation" rel="noreferrer noopener" class="d-flex align-items-center gap-2" target="_blank">
|
|
<vc:icon symbol="docs" />
|
|
<span text-translate="true">Documentation</span>
|
|
</a>
|
|
</li>
|
|
}
|
|
else
|
|
{
|
|
<li>
|
|
<span rel="noreferrer noopener" class="d-flex align-items-center gap-2 text-danger" target="_blank">
|
|
<vc:icon symbol="docs" />
|
|
<span text-translate="true">No documentation</span>
|
|
</span>
|
|
</li>
|
|
}
|
|
</ul>
|
|
}
|
|
</div>
|
|
</div>
|
|
@{
|
|
var pendingAction = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)).command;
|
|
var exclusivePendingAction = true;
|
|
|
|
var versionOfPendingInstall = PluginService.GetVersionOfPendingInstall(plugin.Identifier);
|
|
}
|
|
<div class="card-footer border-0 pb-3 d-flex gap-2">
|
|
@if (pendingAction is not null && updateAvailable)
|
|
{
|
|
var isUpdateAction = Model.Commands.Last(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)).command == "update";
|
|
if (isUpdateAction)
|
|
{
|
|
exclusivePendingAction = versionOfPendingInstall == x.Version;
|
|
}
|
|
}
|
|
@if (pendingAction is not null)
|
|
{
|
|
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
|
<button type="submit" class="btn btn-outline-secondary">Cancel pending @pendingAction @(versionOfPendingInstall is null? "": $"of {versionOfPendingInstall}")</button>
|
|
</form>
|
|
}
|
|
@if (pendingAction is null || !exclusivePendingAction)
|
|
{
|
|
@if (updateAvailable && x != null)
|
|
{
|
|
if (PluginManager.DependenciesMet(x.Dependencies, installed))
|
|
{
|
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@x.Version" asp-route-update="true" class="me-3">
|
|
<button type="submit" class="btn btn-secondary" text-translate="true">Update</button>
|
|
</form>
|
|
}
|
|
else
|
|
{
|
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@x.Version" asp-route-update="true" class="me-3">
|
|
<button title="Schedule upgrade for when the dependencies have been met to ensure a smooth update" data-bs-toggle="tooltip" type="submit" class="btn btn-secondary" text-translate="true">Schedule update</button>
|
|
</form>
|
|
}
|
|
}
|
|
@if (DependentOn(plugin.Identifier))
|
|
{
|
|
<button type="button" class="btn btn-outline-danger" data-bs-toggle="tooltip" title="This plugin cannot be uninstalled as it is depended on by other plugins.">
|
|
<span text-translate="true">Uninstall</span>
|
|
<vc:icon symbol="warning"/>
|
|
</button>
|
|
}
|
|
else
|
|
{
|
|
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin.Identifier">
|
|
<button type="submit" class="btn btn-outline-danger" text-translate="true">Uninstall</button>
|
|
</form>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<form method="get" asp-action="ListPlugins" class="row mb-4" id="searchForm">
|
|
<div class="col-12 col-lg-6">
|
|
<div class="input-group">
|
|
<input type="text" name="search" class="form-control" placeholder="@StringLocalizer["Search…"]"
|
|
value="@ViewContext.HttpContext.Request.Query["search"]" />
|
|
<button type="submit" class="btn btn-primary">
|
|
<vc:icon symbol="actions-search" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<script>
|
|
// if we search using form, scroll to it
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
const searchForm = document.getElementById('searchForm');
|
|
|
|
// Set session storage flag on form submit
|
|
searchForm.addEventListener('submit', function() {
|
|
sessionStorage.setItem('formSubmitted', 'true');
|
|
});
|
|
|
|
// Check session storage to see if form was submitted, and scroll + clear it
|
|
if (sessionStorage.getItem('formSubmitted') === 'true') {
|
|
sessionStorage.removeItem('formSubmitted');
|
|
if (searchForm) {
|
|
searchForm.scrollIntoView();
|
|
}
|
|
}
|
|
});
|
|
|
|
</script>
|
|
|
|
@if (!availableAndNotInstalled.Any())
|
|
{
|
|
<div class="row mb-4">
|
|
<div class="col mb-4" style="margin-left:10px;" text-translate="true">No plugins found</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<h3 class="mb-4">Available Plugins</h3>
|
|
<div class="row mb-4">
|
|
@foreach (var plugin in availableAndNotInstalled)
|
|
{
|
|
var recommended = BTCPayServerOptions.RecommendedPlugins.Any(id => string.Equals(id, plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
|
Model.Disabled.TryGetValue(plugin.Identifier, out var disabled);
|
|
|
|
<div class="col col-12 col-md-6 col-lg-12 col-xl-6 col-xxl-4 mb-4">
|
|
<div class="card h-100" id="@plugin.Identifier">
|
|
<div class="card-body">
|
|
<div class="d-flex flex-wrap align-items-baseline justify-content-between gap-2 mb-3">
|
|
<h4 class="card-title mb-0" data-bs-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
|
|
@if (!string.IsNullOrEmpty(plugin.Author))
|
|
{
|
|
<span class="text-muted text-nowrap">
|
|
by
|
|
<a href="@plugin.AuthorLink" rel="noreferrer noopener" target="_blank">
|
|
@plugin.Author
|
|
</a>
|
|
</span>
|
|
}
|
|
</div>
|
|
<h5 class="text-muted d-flex align-items-center mt-1 gap-2">
|
|
@plugin.Version
|
|
@if (disabled is { } && disabled != plugin.Version)
|
|
{
|
|
<div class="badge bg-light"><span text-translate="true">Disabled</span> (@disabled)</div>
|
|
}
|
|
else if (disabled is { } && disabled == plugin.Version)
|
|
{
|
|
<div class="badge bg-light" text-translate="true">Disabled</div>
|
|
}
|
|
else if (recommended)
|
|
{
|
|
<div class="badge bg-light text-nowrap" data-bs-toggle="tooltip" title="This plugin has been recommended to be installed by your deployment method.">
|
|
<span text-translate="true">Recommended</span>
|
|
<vc:icon symbol="info" />
|
|
</div>
|
|
}
|
|
</h5>
|
|
<p class="card-text">@plugin.Description</p>
|
|
@if (plugin.Dependencies?.Any() is true)
|
|
{
|
|
<h6 class="text-muted fw-semibold" text-translate="true">Dependencies</h6>
|
|
<ul class="list-group list-group-flush">
|
|
@foreach (var dependency in plugin.Dependencies)
|
|
{
|
|
<li class="list-group-item p-2 d-inline-flex align-items-center gap-2">
|
|
@dependency
|
|
@if (!PluginManager.DependencyMet(dependency, installed))
|
|
{
|
|
<span title="Dependency not met." data-bs-toggle="tooltip" class="text-danger">
|
|
<vc:icon symbol="warning" />
|
|
</span>
|
|
}
|
|
</li>
|
|
}
|
|
</ul>
|
|
if(!PluginManager.DependenciesMet(plugin.Dependencies, installed))
|
|
{
|
|
<div class="text-warning py-2" text-translate="true">
|
|
Dependencies not met.
|
|
</div>
|
|
}
|
|
}
|
|
|
|
@if (plugin != null)
|
|
{
|
|
<h6 class="text-muted fw-semibold mt-4" text-translate="true">Resources</h6>
|
|
<ul class="list-group list-group-flush list-unstyled">
|
|
@if (plugin.Source is not null)
|
|
{
|
|
<li>
|
|
<a href="@plugin.Source" rel="noreferrer noopener" class="d-flex align-items-center" target="_blank">
|
|
<vc:icon symbol="social-github" />
|
|
<span style="margin-left:.4rem" text-translate="true">Sources</span>
|
|
</a>
|
|
</li>
|
|
}
|
|
@if (!string.IsNullOrEmpty(plugin.Documentation))
|
|
{
|
|
<li>
|
|
<a href="@plugin.Documentation" rel="noreferrer noopener" class="d-flex align-items-center gap-2" target="_blank">
|
|
<vc:icon symbol="docs" />
|
|
<span text-translate="true">Documentation</span>
|
|
</a>
|
|
</li>
|
|
}
|
|
else
|
|
{
|
|
<li>
|
|
<span rel="noreferrer noopener" class="d-flex align-items-center gap-2 text-danger" target="_blank">
|
|
<vc:icon symbol="docs" />
|
|
<span text-translate="true">No documentation</span>
|
|
</span>
|
|
</li>
|
|
}
|
|
</ul>
|
|
}
|
|
</div>
|
|
<div class="card-footer border-0 pb-3 d-flex gap-2">
|
|
@{
|
|
var res = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
|
var pendingAction = res != default ? res.command : null;
|
|
var versionOfPendingInstall = PluginService.GetVersionOfPendingInstall(plugin.Identifier);
|
|
var exclusivePendingAction = pendingAction is not null && (pendingAction == "delete" || versionOfPendingInstall == plugin.Version);
|
|
}
|
|
@if (pendingAction is not null)
|
|
{
|
|
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
|
<button type="submit" class="btn btn-outline-secondary">Cancel pending @pendingAction @(versionOfPendingInstall is null? "": $"of {versionOfPendingInstall}")</button>
|
|
</form>
|
|
}
|
|
@if (pendingAction is null|| !exclusivePendingAction)
|
|
{
|
|
if (PluginManager.DependenciesMet(plugin.Dependencies, installed))
|
|
{
|
|
@if (disabled is null)
|
|
{
|
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version">
|
|
<button type="submit" class="btn btn-primary" text-translate="true">Install</button>
|
|
</form>
|
|
}
|
|
else if (disabled != plugin.Version)
|
|
{
|
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version" asp-route-update="true">
|
|
<button type="submit" class="btn btn-primary" text-translate="true">Update</button>
|
|
</form>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version">
|
|
<button title="Schedule install for when the dependencies have been met to ensure a smooth update" data-bs-toggle="tooltip" type="submit" class="btn btn-primary" text-translate="true">Schedule install</button>
|
|
</form>
|
|
}
|
|
}
|
|
@if (disabled is not null && pendingAction is null)
|
|
{
|
|
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin.Identifier">
|
|
<button type="submit" class="btn btn-sm btn-outline-danger" text-translate="true">Uninstall</button>
|
|
</form>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<div class="mb-4">
|
|
<h3 class="mb-4" text-translate="true">Upload Plugin</h3>
|
|
<button class="btn btn-secondary mb-4" type="button" data-bs-toggle="collapse" data-bs-target="#manual-upload" text-translate="true">
|
|
Upload Plugin
|
|
</button>
|
|
<div class="row collapse" id="manual-upload">
|
|
<div class="col col-xl-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h4 class="card-title" text-translate="true">Add plugin manually</h4>
|
|
<div class="alert alert-warning my-3">
|
|
<h6 class="me-1" text-translate="true">This is an extremely dangerous operation!</h6>
|
|
<span text-translate="true">Only upload plugins from trusted sources.</span>
|
|
</div>
|
|
<form method="post" enctype="multipart/form-data" asp-action="UploadPlugin">
|
|
<input type="file" class="form-control mb-3" required name="files" accept=".btcpay" id="files">
|
|
<button class="btn btn-primary" type="submit" text-translate="true">Upload</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (Model.Commands.Any())
|
|
{
|
|
<div class="mb-4">
|
|
<h3 class="mb-4">Pending Action</h3>
|
|
<button class="btn btn-secondary mb-4" type="button" data-bs-toggle="collapse" data-bs-target="#pending-actions">
|
|
Pending Actions
|
|
</button>
|
|
<div class="row collapse" id="pending-actions">
|
|
<div class="col col-12 col-lg-6 mb-4">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h4 class="card-title">Pending actions</h4>
|
|
<ul class="list-group list-group-flush">
|
|
@foreach (var extComm in Model.Commands.GroupBy(tuple => tuple.plugin))
|
|
{
|
|
<li class="list-group-item p-2">
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between">
|
|
<span class="my-2 me-3">@extComm.Key</span>
|
|
<form asp-action="CancelPluginCommands" asp-route-plugin="@extComm.Key">
|
|
<button type="submit" class="btn btn-outline-secondary">Cancel pending @extComm.Last().command</button>
|
|
</form>
|
|
</div>
|
|
</li>
|
|
}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|