mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 22:11:48 +01:00
Redesign plugin list items (#4528)
* Redesign plugin list items * Update icon and format code Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
parent
ee70fe85c0
commit
785cf597ad
4 changed files with 188 additions and 55 deletions
|
@ -9,6 +9,7 @@ using BTCPayServer.Configuration;
|
|||
using BTCPayServer.Plugins;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static BTCPayServer.Plugins.PluginService;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
@ -33,9 +34,9 @@ namespace BTCPayServer.Controllers
|
|||
});
|
||||
availablePlugins = Array.Empty<PluginService.AvailablePlugin>();
|
||||
}
|
||||
var docsByIdentifier = new Dictionary<string, string>();
|
||||
foreach (var p in availablePlugins.Where(p => !string.IsNullOrEmpty(p.Documentation)))
|
||||
docsByIdentifier.TryAdd(p.Identifier, p.Documentation);
|
||||
var availablePluginsByIdentifier = new Dictionary<string, AvailablePlugin>();
|
||||
foreach (var p in availablePlugins)
|
||||
availablePluginsByIdentifier.TryAdd(p.Identifier, p);
|
||||
var res = new ListPluginsViewModel()
|
||||
{
|
||||
Installed = pluginService.LoadedPlugins,
|
||||
|
@ -43,7 +44,7 @@ namespace BTCPayServer.Controllers
|
|||
Commands = pluginService.GetPendingCommands(),
|
||||
Disabled = pluginService.GetDisabledPlugins(),
|
||||
CanShowRestart = btcPayServerOptions.DockerDeployment,
|
||||
DocsByIdentifier = docsByIdentifier
|
||||
DownloadedPluginsByIdentifier = availablePluginsByIdentifier
|
||||
};
|
||||
return View(res);
|
||||
}
|
||||
|
@ -55,7 +56,7 @@ namespace BTCPayServer.Controllers
|
|||
public (string command, string plugin)[] Commands { get; set; }
|
||||
public bool CanShowRestart { get; set; }
|
||||
public string[] Disabled { get; set; }
|
||||
public Dictionary<string, string> DocsByIdentifier { get; set; } = new Dictionary<string, string>();
|
||||
public Dictionary<string, AvailablePlugin> DownloadedPluginsByIdentifier { get; set; } = new Dictionary<string, AvailablePlugin>();
|
||||
}
|
||||
|
||||
[HttpPost("server/plugins/uninstall")]
|
||||
|
|
|
@ -1,16 +1,49 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using ExchangeSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static System.Net.WebRequestMethods;
|
||||
|
||||
namespace BTCPayServer.Plugins
|
||||
{
|
||||
public class PublishedVersion
|
||||
{
|
||||
public class BuildInfoClass
|
||||
{
|
||||
public string gitCommit { get; set; }
|
||||
public string pluginDir { get; set; }
|
||||
public string gitRepository { get; set; }
|
||||
#nullable enable
|
||||
static Regex GithubRepositoryRegex = new Regex("^https://(www\\.)?github\\.com/([^/]+)/([^/]+)/?");
|
||||
public record GithubRepository(string Owner, string RepositoryName)
|
||||
{
|
||||
public string? GetSourceUrl(string commit, string pluginDir)
|
||||
{
|
||||
if (commit is null)
|
||||
return null;
|
||||
return $"https://github.com/{Owner}/{RepositoryName}/tree/{commit}/{pluginDir}";
|
||||
}
|
||||
}
|
||||
public GithubRepository? GetGithubRepository()
|
||||
{
|
||||
if (gitRepository is null)
|
||||
return null;
|
||||
var match = GithubRepositoryRegex.Match(gitRepository);
|
||||
if (!match.Success)
|
||||
return null;
|
||||
return new GithubRepository(match.Groups[2].Value, match.Groups[3].Value);
|
||||
}
|
||||
#nullable restore
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
||||
public string ProjectSlug { get; set; }
|
||||
public long BuildId { get; set; }
|
||||
public JObject BuildInfo { get; set; }
|
||||
public BuildInfoClass BuildInfo { get; set; }
|
||||
public JObject ManifestInfo { get; set; }
|
||||
public string Documentation { get; set; }
|
||||
}
|
||||
|
|
|
@ -5,14 +5,17 @@ using System.Linq;
|
|||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -52,6 +55,13 @@ namespace BTCPayServer.Plugins
|
|||
{
|
||||
var p = v.ManifestInfo.ToObject<AvailablePlugin>();
|
||||
p.Documentation = v.Documentation;
|
||||
var github = v.BuildInfo.GetGithubRepository();
|
||||
if (github != null)
|
||||
{
|
||||
p.Source = github.GetSourceUrl(v.BuildInfo.gitCommit, v.BuildInfo.pluginDir);
|
||||
p.Author = github.Owner;
|
||||
p.AuthorLink = $"https://github.com/{github.Owner}";
|
||||
}
|
||||
p.SystemPlugin = false;
|
||||
return p;
|
||||
}).ToArray();
|
||||
|
@ -108,6 +118,9 @@ namespace BTCPayServer.Plugins
|
|||
|
||||
public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
|
||||
public string Documentation { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string AuthorLink { get; set; }
|
||||
|
||||
public void Execute(IApplicationBuilder applicationBuilder,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@using BTCPayServer.Configuration
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@using BTCPayServer.Plugins
|
||||
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
|
||||
@inject BTCPayServerOptions BTCPayServerOptions
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
|
@ -19,7 +19,7 @@
|
|||
var ordered = availableAndNotInstalledItem.OrderByDescending(plugin => plugin.Version).ToArray();
|
||||
availableAndNotInstalled.Add(ordered.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? ordered.FirstOrDefault());
|
||||
}
|
||||
|
||||
|
||||
bool DependentOn(string plugin)
|
||||
{
|
||||
foreach (var installedPlugin in Model.Installed)
|
||||
|
@ -102,8 +102,13 @@
|
|||
}
|
||||
|
||||
<style>
|
||||
.version-switch .nav-link { display: inline; }
|
||||
.version-switch .nav-link.active { display: none; }
|
||||
.version-switch .nav-link {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.version-switch .nav-link.active {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<partial name="_StatusMessage" />
|
||||
|
@ -112,7 +117,7 @@
|
|||
{
|
||||
<div class="alert alert-danger mb-4 d-flex align-items-center justify-content-between">
|
||||
Some plugins were disabled due to fatal errors. They may be incompatible with this version of BTCPay Server.
|
||||
<button class="btn btn-danger" data-bs-toggle="collapse" data-bs-target="#disabled-plugins">View disabled plugins</button>
|
||||
<button class="btn btn-danger" data-bs-toggle="collapse" data-bs-target="#disabled-plugins">View disabled plugins</button>
|
||||
</div>
|
||||
}
|
||||
@if (Model.Commands.Any())
|
||||
|
@ -134,6 +139,7 @@
|
|||
<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 => DependenciesMet(availablePlugin.Dependencies)) ?? matchedAvailable.FirstOrDefault();
|
||||
var updateAvailable = matchedAvailable.Any();
|
||||
|
@ -141,13 +147,18 @@
|
|||
<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 align-items-baseline justify-content-between gap-2">
|
||||
<h4 class="card-title" title="@plugin.Identifier" data-bs-toggle="tooltip">@plugin.Name</h4>
|
||||
@if (Model.DocsByIdentifier.ContainsKey(plugin.Identifier))
|
||||
{
|
||||
<a href="@Model.DocsByIdentifier[plugin.Identifier]" rel="noreferrer noopener" target="_blank">Documentation</a>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex align-items-baseline justify-content-between gap-2">
|
||||
<h4 class="card-title" data-bs-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
|
||||
@if (!string.IsNullOrEmpty(downloadInfo.Author))
|
||||
{
|
||||
<span class="text-muted">
|
||||
by
|
||||
<a href="@downloadInfo.AuthorLink" rel="noreferrer noopener" target="_blank">
|
||||
<span>@downloadInfo.Author</span>
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center mb-2">
|
||||
<h5 class="text-muted d-flex align-items-center mt-1 gap-3">
|
||||
@plugin.Version
|
||||
|
@ -210,6 +221,40 @@
|
|||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (plugin != null)
|
||||
{
|
||||
<h5 class="text-muted mt-4">Resources</h5>
|
||||
<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="github" />
|
||||
<span style="margin-left:.4rem">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>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>No documentation</span>
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@{
|
||||
|
@ -259,20 +304,25 @@
|
|||
{
|
||||
var recommended = BTCPayServerOptions.RecommendedPlugins.Contains(plugin.Identifier.ToLowerInvariant());
|
||||
var disabled = Model.Disabled?.Contains(plugin.Identifier) ?? false;
|
||||
|
||||
|
||||
<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 align-items-baseline justify-content-between gap-2">
|
||||
<h4 class="card-title" data-bs-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
|
||||
@if (!string.IsNullOrEmpty(plugin.Documentation))
|
||||
{
|
||||
<a href="@plugin.Documentation" rel="noreferrer noopener" target="_blank">Documentation</a>
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex align-items-baseline justify-content-between gap-2">
|
||||
<h4 class="card-title" data-bs-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
|
||||
@if (!string.IsNullOrEmpty(plugin.Author))
|
||||
{
|
||||
<span class="text-muted">
|
||||
by
|
||||
<a href="@plugin.AuthorLink" rel="noreferrer noopener" target="_blank">
|
||||
<span>@plugin.Author</span>
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<h5 class="text-muted d-flex align-items-center mt-1 gap-2">
|
||||
@plugin.Version
|
||||
@if (disabled)
|
||||
@if (disabled)
|
||||
{
|
||||
<div class="badge bg-light">Disabled</div>
|
||||
}
|
||||
|
@ -298,9 +348,45 @@
|
|||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@if (plugin != null)
|
||||
{
|
||||
<h5 class="text-muted mt-4">Resources</h5>
|
||||
<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="github" />
|
||||
<span style="margin-left:.4rem">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>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>No documentation</span>
|
||||
</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
<div class="card-footer border-0 pb-3">
|
||||
@{ var pending = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)); }
|
||||
@{
|
||||
var pending = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
@if (!pending.Equals(default))
|
||||
{
|
||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||
|
@ -309,7 +395,7 @@
|
|||
}
|
||||
else if (DependenciesMet(plugin.Dependencies))
|
||||
{
|
||||
@* Don't show the "Install" button if plugin has been disabled *@
|
||||
@* Don't show the "Install" button if plugin has been disabled *@
|
||||
@if (!disabled)
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version">
|
||||
|
@ -323,7 +409,7 @@
|
|||
Cannot install until dependencies are met.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -356,34 +442,34 @@
|
|||
|
||||
@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 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>
|
||||
</div>
|
||||
}
|
||||
@if (Model.Disabled.Any())
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue