mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Plugins: Support plugin git remote with multiple versions of same plugins
This commit is contained in:
@@ -83,11 +83,11 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
[HttpPost("server/plugins/install")]
|
[HttpPost("server/plugins/install")]
|
||||||
public async Task<IActionResult> InstallPlugin(
|
public async Task<IActionResult> InstallPlugin(
|
||||||
[FromServices] PluginService pluginService, string plugin, bool update = false)
|
[FromServices] PluginService pluginService, string plugin, bool update = false, string path ="")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pluginService.DownloadRemotePlugin(plugin);
|
await pluginService.DownloadRemotePlugin(plugin, path);
|
||||||
if (update)
|
if (update)
|
||||||
{
|
{
|
||||||
pluginService.UpdatePlugin(plugin);
|
pluginService.UpdatePlugin(plugin);
|
||||||
|
|||||||
@@ -33,23 +33,36 @@ namespace BTCPayServer.Plugins
|
|||||||
|
|
||||||
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
|
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
|
||||||
|
|
||||||
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins()
|
public async Task<AvailablePlugin[]> GetRemotePlugins(string path = "")
|
||||||
{
|
{
|
||||||
var resp = await _githubClient
|
var resp = await _githubClient
|
||||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
|
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents/{path}"));
|
||||||
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||||
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json", StringComparison.InvariantCulture)).Select(async file =>
|
var dirs = files.Where(file => file.Type == "dir");
|
||||||
{
|
var result = dirs.Select(file => GetRemotePlugins(file.Path));
|
||||||
return await _githubClient.GetStringAsync(file.DownloadUrl).ContinueWith(
|
|
||||||
task => JsonConvert.DeserializeObject<AvailablePlugin>(task.Result), TaskScheduler.Current);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DownloadRemotePlugin(string plugin)
|
var fileTask = Task.WhenAll(files
|
||||||
|
.Where(file => file.Type == "file" && file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json",
|
||||||
|
StringComparison.InvariantCulture)).Select(async file =>
|
||||||
|
{
|
||||||
|
return await _githubClient.GetStringAsync(file.DownloadUrl).ContinueWith(
|
||||||
|
task =>
|
||||||
|
{
|
||||||
|
var r = JsonConvert.DeserializeObject<AvailablePlugin>(task.Result);
|
||||||
|
r.Path = path;
|
||||||
|
return r;
|
||||||
|
}, TaskScheduler.Current);
|
||||||
|
}));
|
||||||
|
return (await Task.WhenAll( result.Concat(new[] { fileTask })).ContinueWith(task => task.Result.SelectMany(plugins => plugins))).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public async Task DownloadRemotePlugin(string plugin, string path)
|
||||||
{
|
{
|
||||||
var dest = _dataDirectories.Value.PluginDir;
|
var dest = _dataDirectories.Value.PluginDir;
|
||||||
var resp = await _githubClient
|
var resp = await _githubClient
|
||||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
|
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents/{path}"));
|
||||||
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||||
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
|
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
|
||||||
if (ext is null)
|
if (ext is null)
|
||||||
@@ -108,6 +121,7 @@ namespace BTCPayServer.Plugins
|
|||||||
public bool SystemPlugin { get; set; } = false;
|
public bool SystemPlugin { get; set; } = false;
|
||||||
|
|
||||||
public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
|
public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
|
||||||
|
public string Path { get; set; }
|
||||||
|
|
||||||
public void Execute(IApplicationBuilder applicationBuilder,
|
public void Execute(IApplicationBuilder applicationBuilder,
|
||||||
IServiceProvider applicationBuilderApplicationServices)
|
IServiceProvider applicationBuilderApplicationServices)
|
||||||
@@ -124,6 +138,8 @@ namespace BTCPayServer.Plugins
|
|||||||
[JsonProperty("name")] public string Name { get; set; }
|
[JsonProperty("name")] public string Name { get; set; }
|
||||||
|
|
||||||
[JsonProperty("sha")] public string Sha { get; set; }
|
[JsonProperty("sha")] public string Sha { get; set; }
|
||||||
|
[JsonProperty("type")] public string Type { get; set; }
|
||||||
|
[JsonProperty("path")] public string Path { get; set; }
|
||||||
|
|
||||||
[JsonProperty("download_url")] public string DownloadUrl { get; set; }
|
[JsonProperty("download_url")] public string DownloadUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
@using BTCPayServer.Configuration
|
@using BTCPayServer.Configuration
|
||||||
@using BTCPayServer.Abstractions.Contracts
|
@using BTCPayServer.Abstractions.Contracts
|
||||||
|
@using BTCPayServer.Plugins
|
||||||
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
|
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
|
||||||
@inject BTCPayServerOptions BTCPayServerOptions
|
@inject BTCPayServerOptions BTCPayServerOptions
|
||||||
@{
|
@{
|
||||||
Layout = "_Layout";
|
Layout = "_Layout";
|
||||||
ViewData.SetActivePage(ServerNavPages.Plugins);
|
ViewData.SetActivePage(ServerNavPages.Plugins);
|
||||||
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier.ToLowerInvariant(), plugin => plugin.Version);
|
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier.ToLowerInvariant(), plugin => plugin.Version);
|
||||||
var availableAndNotInstalled = Model.Available
|
|
||||||
|
var availableAndNotInstalledx = Model.Available
|
||||||
.Where(plugin => !installed.ContainsKey(plugin.Identifier.ToLowerInvariant()))
|
.Where(plugin => !installed.ContainsKey(plugin.Identifier.ToLowerInvariant()))
|
||||||
.OrderBy(plugin => plugin.Identifier)
|
.GroupBy(plugin => plugin.Identifier)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var availableAndNotInstalled = new List<PluginService.AvailablePlugin>();
|
||||||
|
foreach (var availableAndNotInstalledItem in availableAndNotInstalledx)
|
||||||
|
{
|
||||||
|
var ordered = availableAndNotInstalledItem.OrderByDescending(plugin => plugin.Version).ToArray();
|
||||||
|
availableAndNotInstalled.Add(ordered.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? ordered.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DependentOn(string plugin)
|
bool DependentOn(string plugin)
|
||||||
{
|
{
|
||||||
foreach (var installedPlugin in Model.Installed)
|
foreach (var installedPlugin in Model.Installed)
|
||||||
@@ -125,8 +135,10 @@
|
|||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
@foreach (var plugin in Model.Installed)
|
@foreach (var plugin in Model.Installed)
|
||||||
{
|
{
|
||||||
var matchedAvailable = Model.Available.SingleOrDefault(availablePlugin => availablePlugin.Identifier == plugin.Identifier);
|
var matchedAvailable = Model.Available.Where(availablePlugin => availablePlugin.Identifier == plugin.Identifier && availablePlugin.Version > plugin.Version).OrderByDescending(availablePlugin => availablePlugin.Version).ToArray();
|
||||||
var updateAvailable = !plugin.SystemPlugin && matchedAvailable != null && plugin.Version < matchedAvailable.Version;
|
|
||||||
|
var x = matchedAvailable.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? matchedAvailable.FirstOrDefault();
|
||||||
|
var updateAvailable = !plugin.SystemPlugin && matchedAvailable.Any();
|
||||||
var tabId = plugin.Identifier.ToLowerInvariant().Replace(".", "_");
|
var tabId = plugin.Identifier.ToLowerInvariant().Replace(".", "_");
|
||||||
<div class="col col-12 col-lg-6 mb-4">
|
<div class="col col-12 col-lg-6 mb-4">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
@@ -142,7 +154,7 @@
|
|||||||
else if (updateAvailable)
|
else if (updateAvailable)
|
||||||
{
|
{
|
||||||
<div class="badge bg-info ms-2">
|
<div class="badge bg-info ms-2">
|
||||||
@matchedAvailable.Version available
|
@x.Version available
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</h5>
|
</h5>
|
||||||
@@ -179,12 +191,12 @@
|
|||||||
@if (updateAvailable)
|
@if (updateAvailable)
|
||||||
{
|
{
|
||||||
<div class="tab-pane" id="@tabId-update">
|
<div class="tab-pane" id="@tabId-update">
|
||||||
<p class="card-text">@matchedAvailable.Description</p>
|
<p class="card-text">@x.Description</p>
|
||||||
@if (matchedAvailable.Dependencies.Any())
|
@if (x.Dependencies.Any())
|
||||||
{
|
{
|
||||||
<h5 class="text-muted">Dependencies</h5>
|
<h5 class="text-muted">Dependencies</h5>
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
@foreach (var dependency in matchedAvailable.Dependencies)
|
@foreach (var dependency in x.Dependencies)
|
||||||
{
|
{
|
||||||
<li class="list-group-item p-2">
|
<li class="list-group-item p-2">
|
||||||
@dependency
|
@dependency
|
||||||
@@ -203,7 +215,7 @@
|
|||||||
@{
|
@{
|
||||||
var pendingAction = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
var pendingAction = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||||
}
|
}
|
||||||
@if (!plugin.SystemPlugin && (pendingAction || (updateAvailable && DependenciesMet(matchedAvailable.Dependencies)) || !DependentOn(plugin.Identifier)))
|
@if (!plugin.SystemPlugin && (pendingAction || (updateAvailable && DependenciesMet(x.Dependencies)) || !DependentOn(plugin.Identifier)))
|
||||||
{
|
{
|
||||||
<div class="card-footer border-0 pb-3 d-flex">
|
<div class="card-footer border-0 pb-3 d-flex">
|
||||||
@if (pendingAction)
|
@if (pendingAction)
|
||||||
@@ -214,9 +226,9 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (updateAvailable && DependenciesMet(matchedAvailable.Dependencies))
|
@if (updateAvailable && DependenciesMet(x.Dependencies))
|
||||||
{
|
{
|
||||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-update="true" class="me-3">
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-path="@x.Path" asp-route-update="true" class="me-3">
|
||||||
<button type="submit" class="btn btn-secondary">Update</button>
|
<button type="submit" class="btn btn-secondary">Update</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
@@ -245,6 +257,7 @@
|
|||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
@foreach (var plugin in availableAndNotInstalled)
|
@foreach (var plugin in availableAndNotInstalled)
|
||||||
{
|
{
|
||||||
|
|
||||||
var recommended = BTCPayServerOptions.RecommendedPlugins.Contains(plugin.Identifier.ToLowerInvariant());
|
var recommended = BTCPayServerOptions.RecommendedPlugins.Contains(plugin.Identifier.ToLowerInvariant());
|
||||||
var disabled = Model.Disabled?.Contains(plugin.Identifier) ?? false;
|
var disabled = Model.Disabled?.Contains(plugin.Identifier) ?? false;
|
||||||
|
|
||||||
@@ -301,7 +314,7 @@
|
|||||||
@* Don't show the "Install" button if plugin has been disabled *@
|
@* Don't show the "Install" button if plugin has been disabled *@
|
||||||
@if (!disabled)
|
@if (!disabled)
|
||||||
{
|
{
|
||||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier">
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-path="@plugin.Path">
|
||||||
<button type="submit" class="btn btn-primary">Install</button>
|
<button type="submit" class="btn btn-primary">Install</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user