mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Plugins: Load plugins by order, aesthetic plugin dependency system (#2020)
* Plugins: Load plugins by order, aesthetic plugin dependency system Introduces plugins loading in order of installation, BTCPay itself shows up as a system plugin, and that plugins can define other plugins as dependencies. * use a proper type for plugin dependencies * rebase fixes * message when cannot install
This commit is contained in:
@@ -14,8 +14,19 @@ namespace BTCPayServer.Contracts
|
|||||||
Version Version { get; }
|
Version Version { get; }
|
||||||
string Description { get; }
|
string Description { get; }
|
||||||
bool SystemPlugin { get; set; }
|
bool SystemPlugin { get; set; }
|
||||||
string[] Dependencies { get; }
|
PluginDependency[] Dependencies { get; }
|
||||||
void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices);
|
void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices);
|
||||||
void Execute(IServiceCollection applicationBuilder);
|
void Execute(IServiceCollection applicationBuilder);
|
||||||
|
|
||||||
|
public class PluginDependency
|
||||||
|
{
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public string Condition { get; set; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Identifier}: {Condition}";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace BTCPayServer.Models
|
|||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
public bool SystemPlugin { get; set; }
|
public bool SystemPlugin { get; set; }
|
||||||
public bool SystemExtension { get; set; }
|
public bool SystemExtension { get; set; }
|
||||||
public virtual string[] Dependencies { get; } = Array.Empty<string>();
|
public virtual IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
|
||||||
|
|
||||||
public virtual void Execute(IApplicationBuilder applicationBuilder,
|
public virtual void Execute(IApplicationBuilder applicationBuilder,
|
||||||
IServiceProvider applicationBuilderApplicationServices)
|
IServiceProvider applicationBuilderApplicationServices)
|
||||||
@@ -79,12 +79,20 @@ 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)
|
[FromServices] PluginService pluginService, string plugin , bool update = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pluginService.DownloadRemotePlugin(plugin);
|
await pluginService.DownloadRemotePlugin(plugin);
|
||||||
pluginService.InstallPlugin(plugin);
|
if (update)
|
||||||
|
{
|
||||||
|
pluginService.UpdatePlugin(plugin);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
pluginService.InstallPlugin(plugin);
|
||||||
|
}
|
||||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
{
|
{
|
||||||
Message = "Plugin scheduled to be installed.",
|
Message = "Plugin scheduled to be installed.",
|
||||||
|
|||||||
12
BTCPayServer/Plugins/BTCPayServerPlugin.cs
Normal file
12
BTCPayServer/Plugins/BTCPayServerPlugin.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using BTCPayServer.Models;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins
|
||||||
|
{
|
||||||
|
public class BTCPayServerPlugin: BaseBTCPayServerPlugin
|
||||||
|
{
|
||||||
|
public override string Identifier { get; } = nameof(BTCPayServer);
|
||||||
|
public override string Name { get; } = "BTCPay Server";
|
||||||
|
public override string Description { get; }= "BTCPay Server core system";
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,10 +30,12 @@ namespace BTCPayServer.Plugins
|
|||||||
var pluginsFolder = config.GetPluginDir(DefaultConfiguration.GetNetworkType(config));
|
var pluginsFolder = config.GetPluginDir(DefaultConfiguration.GetNetworkType(config));
|
||||||
var plugins = new List<IBTCPayServerPlugin>();
|
var plugins = new List<IBTCPayServerPlugin>();
|
||||||
|
|
||||||
|
|
||||||
_logger.LogInformation($"Loading plugins from {pluginsFolder}");
|
_logger.LogInformation($"Loading plugins from {pluginsFolder}");
|
||||||
Directory.CreateDirectory(pluginsFolder);
|
Directory.CreateDirectory(pluginsFolder);
|
||||||
ExecuteCommands(pluginsFolder);
|
ExecuteCommands(pluginsFolder);
|
||||||
List<(PluginLoader, Assembly, IFileProvider)> loadedPlugins = new List<(PluginLoader, Assembly, IFileProvider)>();
|
List<(PluginLoader, Assembly, IFileProvider)> loadedPlugins =
|
||||||
|
new List<(PluginLoader, Assembly, IFileProvider)>();
|
||||||
var systemExtensions = GetDefaultLoadedPluginAssemblies();
|
var systemExtensions = GetDefaultLoadedPluginAssemblies();
|
||||||
plugins.AddRange(systemExtensions.SelectMany(assembly =>
|
plugins.AddRange(systemExtensions.SelectMany(assembly =>
|
||||||
GetAllPluginTypesFromAssembly(assembly).Select(GetPluginInstanceFromType)));
|
GetAllPluginTypesFromAssembly(assembly).Select(GetPluginInstanceFromType)));
|
||||||
@@ -41,7 +43,29 @@ namespace BTCPayServer.Plugins
|
|||||||
{
|
{
|
||||||
btcPayServerExtension.SystemPlugin = true;
|
btcPayServerExtension.SystemPlugin = true;
|
||||||
}
|
}
|
||||||
foreach (var dir in Directory.GetDirectories(pluginsFolder))
|
|
||||||
|
var orderFilePath = Path.Combine(pluginsFolder, "order");
|
||||||
|
var availableDirs = Directory.GetDirectories(pluginsFolder);
|
||||||
|
var orderedDirs = new List<string>();
|
||||||
|
if (File.Exists(orderFilePath))
|
||||||
|
{
|
||||||
|
var order = File.ReadLines(orderFilePath);
|
||||||
|
foreach (var s in order)
|
||||||
|
{
|
||||||
|
if (availableDirs.Contains(s))
|
||||||
|
{
|
||||||
|
orderedDirs.Add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
orderedDirs.AddRange(availableDirs.Where(s => !orderedDirs.Contains(s)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
orderedDirs = availableDirs.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var dir in orderedDirs)
|
||||||
{
|
{
|
||||||
var pluginName = Path.GetFileName(dir);
|
var pluginName = Path.GetFileName(dir);
|
||||||
|
|
||||||
@@ -71,7 +95,8 @@ namespace BTCPayServer.Plugins
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError($"Error when loading plugin {plugin.Identifier} - {plugin.Version}{Environment.NewLine}{e.Message}");
|
_logger.LogError(
|
||||||
|
$"Error when loading plugin {plugin.Identifier} - {plugin.Version}{Environment.NewLine}{e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,18 +119,17 @@ namespace BTCPayServer.Plugins
|
|||||||
.Select(CreateEmbeddedFileProviderForAssembly));
|
.Select(CreateEmbeddedFileProviderForAssembly));
|
||||||
webHostEnvironment.WebRootFileProvider = new CompositeFileProvider(providers);
|
webHostEnvironment.WebRootFileProvider = new CompositeFileProvider(providers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Assembly[] GetDefaultLoadedPluginAssemblies()
|
private static Assembly[] GetDefaultLoadedPluginAssemblies()
|
||||||
{
|
{
|
||||||
return AppDomain.CurrentDomain.GetAssemblies().Where(assembly =>
|
return AppDomain.CurrentDomain.GetAssemblies()
|
||||||
assembly?.FullName?.StartsWith("BTCPayServer.Plugins",
|
|
||||||
StringComparison.InvariantCultureIgnoreCase) is true)
|
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Type[] GetAllPluginTypesFromAssembly(Assembly assembly)
|
private static Type[] GetAllPluginTypesFromAssembly(Assembly assembly)
|
||||||
{
|
{
|
||||||
return assembly.GetTypes().Where(type =>
|
return assembly.GetTypes().Where(type =>
|
||||||
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) &&
|
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) && type != typeof(PluginService.AvailablePlugin) &&
|
||||||
!type.IsAbstract).ToArray();
|
!type.IsAbstract).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,15 +154,26 @@ namespace BTCPayServer.Plugins
|
|||||||
File.Delete(Path.Combine(pluginsFolder, "commands"));
|
File.Delete(Path.Combine(pluginsFolder, "commands"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ExecuteCommand((string command, string extension) command, string pluginsFolder)
|
private static void ExecuteCommand((string command, string extension) command, string pluginsFolder,
|
||||||
|
bool ignoreOrder = false)
|
||||||
{
|
{
|
||||||
var dirName = Path.Combine(pluginsFolder, command.extension);
|
var dirName = Path.Combine(pluginsFolder, command.extension);
|
||||||
switch (command.command)
|
switch (command.command)
|
||||||
{
|
{
|
||||||
|
case "update":
|
||||||
|
ExecuteCommand(("delete", command.extension), pluginsFolder, true);
|
||||||
|
ExecuteCommand(("install", command.extension), pluginsFolder, true);
|
||||||
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
if (Directory.Exists(dirName))
|
if (Directory.Exists(dirName))
|
||||||
{
|
{
|
||||||
Directory.Delete(dirName, true);
|
Directory.Delete(dirName, true);
|
||||||
|
if (!ignoreOrder && File.Exists(Path.Combine(pluginsFolder, "order")))
|
||||||
|
{
|
||||||
|
var orders = File.ReadAllLines(Path.Combine(pluginsFolder, "order"));
|
||||||
|
File.AppendAllLines(Path.Combine(pluginsFolder, "order"),
|
||||||
|
orders.Where(s => s != command.extension));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -147,6 +182,11 @@ namespace BTCPayServer.Plugins
|
|||||||
if (File.Exists(fileName))
|
if (File.Exists(fileName))
|
||||||
{
|
{
|
||||||
ZipFile.ExtractToDirectory(fileName, dirName, true);
|
ZipFile.ExtractToDirectory(fileName, dirName, true);
|
||||||
|
if (!ignoreOrder)
|
||||||
|
{
|
||||||
|
File.AppendAllLines(Path.Combine(pluginsFolder, "order"), new[] {command.extension});
|
||||||
|
}
|
||||||
|
|
||||||
File.Delete(fileName);
|
File.Delete(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ namespace BTCPayServer.Plugins
|
|||||||
UninstallPlugin(plugin);
|
UninstallPlugin(plugin);
|
||||||
PluginManager.QueueCommands(dest, ("install", plugin));
|
PluginManager.QueueCommands(dest, ("install", plugin));
|
||||||
}
|
}
|
||||||
|
public void UpdatePlugin(string plugin)
|
||||||
|
{
|
||||||
|
var dest = _btcPayServerOptions.PluginDir;
|
||||||
|
PluginManager.QueueCommands(dest, ("update", plugin));
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UploadPlugin(IFormFile plugin)
|
public async Task UploadPlugin(IFormFile plugin)
|
||||||
{
|
{
|
||||||
@@ -93,7 +98,7 @@ namespace BTCPayServer.Plugins
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public bool SystemPlugin { get; set; } = false;
|
public bool SystemPlugin { get; set; } = false;
|
||||||
|
|
||||||
public string[] Dependencies { get; } = Array.Empty<string>();
|
public IBTCPayServerPlugin.PluginDependency[] Dependencies { get; set; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
|
||||||
|
|
||||||
public void Execute(IApplicationBuilder applicationBuilder,
|
public void Execute(IApplicationBuilder applicationBuilder,
|
||||||
IServiceProvider applicationBuilderApplicationServices)
|
IServiceProvider applicationBuilderApplicationServices)
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||||
"BTCPAY_UPDATEURL": "",
|
"BTCPAY_UPDATEURL": "",
|
||||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||||
|
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://127.0.0.1:14142/"
|
"applicationUrl": "http://127.0.0.1:14142/"
|
||||||
},
|
},
|
||||||
@@ -53,7 +54,8 @@
|
|||||||
"BTCPAY_DEBUGLOG": "debug.log",
|
"BTCPAY_DEBUGLOG": "debug.log",
|
||||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||||
|
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test"
|
||||||
},
|
},
|
||||||
"applicationUrl": "https://localhost:14142/"
|
"applicationUrl": "https://localhost:14142/"
|
||||||
},
|
},
|
||||||
@@ -87,7 +89,8 @@
|
|||||||
"BTCPAY_DEBUGLOG": "debug.log",
|
"BTCPAY_DEBUGLOG": "debug.log",
|
||||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||||
|
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test"
|
||||||
},
|
},
|
||||||
"applicationUrl": "https://localhost:14142/"
|
"applicationUrl": "https://localhost:14142/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,93 @@
|
|||||||
@using BTCPayServer.Configuration
|
@using BTCPayServer.Configuration
|
||||||
|
@using BTCPayServer.Contracts
|
||||||
@model BTCPayServer.Controllers.ServerController.ListPluginsViewModel
|
@model BTCPayServer.Controllers.ServerController.ListPluginsViewModel
|
||||||
@inject BTCPayServerOptions BTCPayServerOptions
|
@inject BTCPayServerOptions BTCPayServerOptions
|
||||||
@{
|
@{
|
||||||
ViewData.SetActivePageAndTitle(ServerNavPages.Plugins);
|
ViewData.SetActivePageAndTitle(ServerNavPages.Plugins);
|
||||||
var installed = Model.Installed.Select(plugin => plugin.Identifier);
|
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier.ToLowerInvariant(), plugin => plugin.Version);
|
||||||
var availableAndNotInstalled = Model.Available.Where(plugin => !installed.Contains(plugin.Identifier)).Select(plugin => (plugin, BTCPayServerOptions.RecommendedPlugins.Contains(plugin.Identifier.ToLowerInvariant()))).OrderBy(tuple => tuple.Item1);
|
var availableAndNotInstalled = Model.Available.Where(plugin => !installed.ContainsKey(plugin.Identifier.ToLowerInvariant())).Select(plugin => (plugin, BTCPayServerOptions.RecommendedPlugins.Contains(plugin.Identifier.ToLowerInvariant()))).OrderBy(tuple => tuple.Item1);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DependencyMet(IBTCPayServerPlugin.PluginDependency dependency)
|
||||||
|
{
|
||||||
|
var plugin = dependency.Identifier.ToLowerInvariant();
|
||||||
|
var versionReq = dependency.Condition;
|
||||||
|
if (!installed.ContainsKey(plugin) && !versionReq.Equals("!"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (installed.ContainsKey(plugin) && versionReq.Equals("!"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionConditions = versionReq.Split("||", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
return versionConditions.Any(s =>
|
||||||
|
{
|
||||||
|
s = s.Trim();
|
||||||
|
var v = s.Substring(1);
|
||||||
|
if (s[1] == '=')
|
||||||
|
{
|
||||||
|
v = s.Substring(2);
|
||||||
|
}
|
||||||
|
var parsedV = Version.Parse(v);
|
||||||
|
switch (s)
|
||||||
|
{
|
||||||
|
case { } xx when xx.StartsWith(">="):
|
||||||
|
return installed[plugin] >= parsedV;
|
||||||
|
case { } xx when xx.StartsWith("<="):
|
||||||
|
return installed[plugin] <= parsedV;
|
||||||
|
case { } xx when xx.StartsWith(">"):
|
||||||
|
return installed[plugin] > parsedV;
|
||||||
|
case { } xx when xx.StartsWith("<"):
|
||||||
|
return installed[plugin] >= parsedV;
|
||||||
|
case { } xx when xx.StartsWith("^"):
|
||||||
|
return installed[plugin] >= parsedV && installed[plugin].Major == parsedV.Major;
|
||||||
|
case { } xx when xx.StartsWith("~"):
|
||||||
|
return installed[plugin] >= parsedV && installed[plugin].Major == parsedV.Major && installed[plugin].Minor == parsedV.Minor;
|
||||||
|
case { } xx when xx.StartsWith("!="):
|
||||||
|
return installed[plugin] != parsedV;
|
||||||
|
case { } xx when xx.StartsWith("=="):
|
||||||
|
default:
|
||||||
|
return installed[plugin] == parsedV;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DependenciesMet(IBTCPayServerPlugin.PluginDependency[] dependencies)
|
||||||
|
{
|
||||||
|
foreach (var dependency in dependencies)
|
||||||
|
{
|
||||||
|
if (!DependencyMet(dependency))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<partial name="_StatusMessage"/>
|
<partial name="_StatusMessage"/>
|
||||||
@@ -29,28 +112,94 @@
|
|||||||
@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.SingleOrDefault(availablePlugin => availablePlugin.Identifier == plugin.Identifier);
|
||||||
var updateAvailable = matchedAvailable != null && plugin.Version < matchedAvailable.Version;
|
var updateAvailable = !plugin.SystemPlugin && matchedAvailable != null && plugin.Version < matchedAvailable.Version;
|
||||||
<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">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">@plugin.Name</h4>
|
<div class="row">
|
||||||
<h5 class="card-subtitle mb-3 text-muted d-flex align-items-center">
|
<div class="col-xs-12 col-md-6">
|
||||||
@plugin.Version
|
<h4 class="card-title" title="@plugin.Identifier" data-toggle="tooltip">@plugin.Name</h4>
|
||||||
@if (plugin.SystemPlugin)
|
<h5 class="card-subtitle mb-3 text-muted d-flex align-items-center">
|
||||||
|
|
||||||
|
@plugin.Version
|
||||||
|
@if (plugin.SystemPlugin)
|
||||||
|
{
|
||||||
|
<div class="badge badge-secondary ml-2">System plugin</div>
|
||||||
|
}
|
||||||
|
else if (updateAvailable)
|
||||||
|
{
|
||||||
|
<div class="badge badge-secondary ml-2">@matchedAvailable.Version available</div>
|
||||||
|
}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
@if (updateAvailable)
|
||||||
{
|
{
|
||||||
<div class="badge badge-secondary ml-2">System plugin</div>
|
<ul class="nav nav-pills col-xs-12 col-md-6 text-right">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a data-toggle="tab" href="#@plugin.Identifier.ToLowerInvariant().Replace(".", "_")-current" class="nav-link show active">Current</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a data-toggle="tab" href="#@plugin.Identifier.ToLowerInvariant().Replace(".", "_")-update" class="nav-link">Update</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
}
|
}
|
||||||
else if (updateAvailable)
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane show active" id="@plugin.Identifier.ToLowerInvariant().Replace(".", "_")-current">
|
||||||
|
<p class="card-text">@plugin.Description</p>
|
||||||
|
|
||||||
|
@if (plugin.Dependencies.Any())
|
||||||
|
{
|
||||||
|
<h5 class=" text-muted">Dependencies</h5>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
@foreach (var dependency in plugin.Dependencies)
|
||||||
|
{
|
||||||
|
<li class="list-group-item">
|
||||||
|
@dependency
|
||||||
|
@if (!DependencyMet(dependency))
|
||||||
|
{
|
||||||
|
<span title="Dependency not met." data-toggle="tooltip" class="fa fa-warning text-danger"></span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (updateAvailable)
|
||||||
{
|
{
|
||||||
<div class="badge badge-secondary ml-2">@matchedAvailable.Version available</div>
|
<div class="tab-pane" id="@plugin.Identifier.ToLowerInvariant().Replace(".", "_")-update">
|
||||||
|
<p class="card-text">@matchedAvailable.Description</p>
|
||||||
|
|
||||||
|
@if (matchedAvailable.Dependencies.Any())
|
||||||
|
{
|
||||||
|
<h5 class=" text-muted">Dependencies</h5>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
@foreach (var dependency in matchedAvailable.Dependencies)
|
||||||
|
{
|
||||||
|
<li class="list-group-item ">
|
||||||
|
@dependency
|
||||||
|
@if (!DependencyMet(dependency))
|
||||||
|
{
|
||||||
|
<span title="Dependency not met." data-toggle="tooltip" class="fa fa-warning text-danger"></span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</h5>
|
</div>
|
||||||
<p class="card-text">@plugin.Description</p>
|
|
||||||
</div>
|
</div>
|
||||||
@if (!plugin.SystemPlugin)
|
@{
|
||||||
|
var pendingAction = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
}
|
||||||
|
@if (!plugin.SystemPlugin && (pendingAction || (updateAvailable && DependenciesMet(matchedAvailable.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 (Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)))
|
@if (pendingAction)
|
||||||
{
|
{
|
||||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending action</button>
|
<button type="submit" class="btn btn-outline-secondary">Cancel pending action</button>
|
||||||
@@ -58,15 +207,22 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (updateAvailable)
|
@if (updateAvailable && DependenciesMet(matchedAvailable.Dependencies))
|
||||||
{
|
{
|
||||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" class="mr-3">
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-update="true" class="mr-3">
|
||||||
<button type="submit" class="btn btn-secondary">Update</button>
|
<button type="submit" class="btn btn-secondary">Update</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin.Identifier">
|
@if (DependentOn(plugin.Identifier))
|
||||||
<button type="submit" class="btn btn-outline-danger">Uninstall</button>
|
{
|
||||||
</form>
|
<button type="button" class="btn btn-outline-danger" data-toggle="tooltip" title="This plugin cannot be uninstalled as it is depended on by other plugins.">Uninstall <span class="fa fa-exclamation"></span></button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin.Identifier">
|
||||||
|
<button type="submit" class="btn btn-outline-danger">Uninstall</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -85,7 +241,7 @@
|
|||||||
<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">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">@plugin.Name</h4>
|
<h4 class="card-title" data-toggle="tooltip" title="@plugin.Identifier">@plugin.Name</h4>
|
||||||
<h5 class="card-subtitle mb-3 text-muted d-flex justify-content-between">
|
<h5 class="card-subtitle mb-3 text-muted d-flex justify-content-between">
|
||||||
<span>@plugin.Version</span>
|
<span>@plugin.Version</span>
|
||||||
@if (pluginT.Item2)
|
@if (pluginT.Item2)
|
||||||
@@ -94,20 +250,46 @@
|
|||||||
}
|
}
|
||||||
</h5>
|
</h5>
|
||||||
<p class="card-text">@plugin.Description</p>
|
<p class="card-text">@plugin.Description</p>
|
||||||
|
@if (plugin.Dependencies?.Any() is true)
|
||||||
|
{
|
||||||
|
<h5 class=" text-muted">Dependencies</h5>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
|
||||||
|
@foreach (var dependency in plugin.Dependencies)
|
||||||
|
{
|
||||||
|
<li class="list-group-item">
|
||||||
|
@dependency
|
||||||
|
@if (!DependencyMet(dependency))
|
||||||
|
{
|
||||||
|
<span title="Dependency not met." data-toggle="tooltip" class="fa fa-warning text-danger"></span>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@{
|
||||||
|
var pending = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
}
|
||||||
<div class="card-footer border-0 pb-3">
|
<div class="card-footer border-0 pb-3">
|
||||||
@if (Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)))
|
@if (pending)
|
||||||
{
|
{
|
||||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending install</button>
|
<button type="submit" class="btn btn-outline-secondary">Cancel pending install</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
else
|
else if (DependenciesMet(plugin.Dependencies))
|
||||||
{
|
{
|
||||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier">
|
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier">
|
||||||
<button type="submit" class="btn btn-primary">Install</button>
|
<button type="submit" class="btn btn-primary">Install</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-danger">
|
||||||
|
Cannot install until dependencies are met
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user