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:
Nicolas Dorier
2023-01-16 20:12:51 +09:00
committed by GitHub
parent ee70fe85c0
commit 785cf597ad
4 changed files with 188 additions and 55 deletions

View File

@@ -9,6 +9,7 @@ using BTCPayServer.Configuration;
using BTCPayServer.Plugins; using BTCPayServer.Plugins;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using static BTCPayServer.Plugins.PluginService;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
@@ -33,9 +34,9 @@ namespace BTCPayServer.Controllers
}); });
availablePlugins = Array.Empty<PluginService.AvailablePlugin>(); availablePlugins = Array.Empty<PluginService.AvailablePlugin>();
} }
var docsByIdentifier = new Dictionary<string, string>(); var availablePluginsByIdentifier = new Dictionary<string, AvailablePlugin>();
foreach (var p in availablePlugins.Where(p => !string.IsNullOrEmpty(p.Documentation))) foreach (var p in availablePlugins)
docsByIdentifier.TryAdd(p.Identifier, p.Documentation); availablePluginsByIdentifier.TryAdd(p.Identifier, p);
var res = new ListPluginsViewModel() var res = new ListPluginsViewModel()
{ {
Installed = pluginService.LoadedPlugins, Installed = pluginService.LoadedPlugins,
@@ -43,7 +44,7 @@ namespace BTCPayServer.Controllers
Commands = pluginService.GetPendingCommands(), Commands = pluginService.GetPendingCommands(),
Disabled = pluginService.GetDisabledPlugins(), Disabled = pluginService.GetDisabledPlugins(),
CanShowRestart = btcPayServerOptions.DockerDeployment, CanShowRestart = btcPayServerOptions.DockerDeployment,
DocsByIdentifier = docsByIdentifier DownloadedPluginsByIdentifier = availablePluginsByIdentifier
}; };
return View(res); return View(res);
} }
@@ -55,7 +56,7 @@ namespace BTCPayServer.Controllers
public (string command, string plugin)[] Commands { get; set; } public (string command, string plugin)[] Commands { get; set; }
public bool CanShowRestart { get; set; } public bool CanShowRestart { get; set; }
public string[] Disabled { 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")] [HttpPost("server/plugins/uninstall")]

View File

@@ -1,16 +1,49 @@
using System; using System;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using ExchangeSharp;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using static System.Net.WebRequestMethods;
namespace BTCPayServer.Plugins namespace BTCPayServer.Plugins
{ {
public class PublishedVersion 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 string ProjectSlug { get; set; }
public long BuildId { get; set; } public long BuildId { get; set; }
public JObject BuildInfo { get; set; } public BuildInfoClass BuildInfo { get; set; }
public JObject ManifestInfo { get; set; } public JObject ManifestInfo { get; set; }
public string Documentation { get; set; } public string Documentation { get; set; }
} }

View File

@@ -5,14 +5,17 @@ using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Configuration; using BTCPayServer.Configuration;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Services; using BTCPayServer.Services;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -52,6 +55,13 @@ namespace BTCPayServer.Plugins
{ {
var p = v.ManifestInfo.ToObject<AvailablePlugin>(); var p = v.ManifestInfo.ToObject<AvailablePlugin>();
p.Documentation = v.Documentation; 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; p.SystemPlugin = false;
return p; return p;
}).ToArray(); }).ToArray();
@@ -108,6 +118,9 @@ namespace BTCPayServer.Plugins
public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>(); public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
public string Documentation { get; set; } 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, public void Execute(IApplicationBuilder applicationBuilder,
IServiceProvider applicationBuilderApplicationServices) IServiceProvider applicationBuilderApplicationServices)

View File

@@ -1,6 +1,6 @@
@using BTCPayServer.Configuration @using BTCPayServer.Configuration
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Plugins @using BTCPayServer.Plugins
@using BTCPayServer.Abstractions.Contracts
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel @model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
@inject BTCPayServerOptions BTCPayServerOptions @inject BTCPayServerOptions BTCPayServerOptions
@{ @{
@@ -102,8 +102,13 @@
} }
<style> <style>
.version-switch .nav-link { display: inline; } .version-switch .nav-link {
.version-switch .nav-link.active { display: none; } display: inline;
}
.version-switch .nav-link.active {
display: none;
}
</style> </style>
<partial name="_StatusMessage" /> <partial name="_StatusMessage" />
@@ -134,6 +139,7 @@
<div class="row mb-4"> <div class="row mb-4">
@foreach (var plugin in Model.Installed.Where(i => !i.SystemPlugin)) @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 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 x = matchedAvailable.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? matchedAvailable.FirstOrDefault();
var updateAvailable = matchedAvailable.Any(); var updateAvailable = matchedAvailable.Any();
@@ -142,10 +148,15 @@
<div class="card h-100" id="@plugin.Identifier"> <div class="card h-100" id="@plugin.Identifier">
<div class="card-body"> <div class="card-body">
<div class="d-flex align-items-baseline justify-content-between gap-2"> <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> <h4 class="card-title" data-bs-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
@if (Model.DocsByIdentifier.ContainsKey(plugin.Identifier)) @if (!string.IsNullOrEmpty(downloadInfo.Author))
{ {
<a href="@Model.DocsByIdentifier[plugin.Identifier]" rel="noreferrer noopener" target="_blank">Documentation</a> <span class="text-muted">
by
<a href="@downloadInfo.AuthorLink" rel="noreferrer noopener" target="_blank">
<span>@downloadInfo.Author</span>
</a>
</span>
} }
</div> </div>
<div class="d-flex flex-wrap align-items-center mb-2"> <div class="d-flex flex-wrap align-items-center mb-2">
@@ -210,6 +221,40 @@
} }
</div> </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>
</div> </div>
@{ @{
@@ -265,9 +310,14 @@
<div class="card-body"> <div class="card-body">
<div class="d-flex align-items-baseline justify-content-between gap-2"> <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> <h4 class="card-title" data-bs-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
@if (!string.IsNullOrEmpty(plugin.Documentation)) @if (!string.IsNullOrEmpty(plugin.Author))
{ {
<a href="@plugin.Documentation" rel="noreferrer noopener" target="_blank">Documentation</a> <span class="text-muted">
by
<a href="@plugin.AuthorLink" rel="noreferrer noopener" target="_blank">
<span>@plugin.Author</span>
</a>
</span>
} }
</div> </div>
<h5 class="text-muted d-flex align-items-center mt-1 gap-2"> <h5 class="text-muted d-flex align-items-center mt-1 gap-2">
@@ -298,9 +348,45 @@
} }
</ul> </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>
<div class="card-footer border-0 pb-3"> <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)) @if (!pending.Equals(default))
{ {
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier"> <form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">