Compare commits
10 Commits
webhook
...
extensions
Author | SHA1 | Date | |
---|---|---|---|
34651e481e | |||
d11c00518b | |||
d8e55707a5 | |||
bc57c9d56a | |||
38fb64130d | |||
493b10393b | |||
b406f52670 | |||
ef3f314754 | |||
d2910686cd | |||
793b1b56d9 |
@ -14,8 +14,19 @@ namespace BTCPayServer.Contracts
|
||||
Version Version { get; }
|
||||
string Description { get; }
|
||||
bool SystemPlugin { get; set; }
|
||||
string[] Dependencies { get; }
|
||||
PluginDependency[] Dependencies { get; }
|
||||
void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices);
|
||||
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 bool SystemPlugin { 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,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
@ -25,7 +25,7 @@ namespace BTCPayServer
|
||||
_Settings.Add(chainType, settings);
|
||||
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
|
||||
settings.DefaultPluginDirectory =
|
||||
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Extensions");
|
||||
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Plugins");
|
||||
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
|
||||
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
|
||||
chainType == NetworkType.Regtest ? 23002 :
|
||||
|
@ -206,9 +206,9 @@ namespace BTCPayServer.Tests
|
||||
bitflyerMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_JPY"), new BidAsk(700000m)));
|
||||
rateProvider.Providers.Add("bitflyer", bitflyerMock);
|
||||
|
||||
var quadrigacx = new MockRateProvider();
|
||||
quadrigacx.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
|
||||
rateProvider.Providers.Add("quadrigacx", quadrigacx);
|
||||
var ndax = new MockRateProvider();
|
||||
ndax.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
|
||||
rateProvider.Providers.Add("ndax", ndax);
|
||||
|
||||
var bittrex = new MockRateProvider();
|
||||
bittrex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("DOGE_BTC"), new BidAsk(0.004m)));
|
||||
|
@ -1893,7 +1893,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
|
||||
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
|
||||
"X_CAD = quadrigacx(X_CAD);\n" +
|
||||
"X_CAD = ndax(X_CAD);\n" +
|
||||
"X_X = coingecko(X_X);";
|
||||
rateVm.Spread = 50;
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test"))
|
||||
|
@ -241,9 +241,13 @@ namespace BTCPayServer.Configuration
|
||||
}
|
||||
|
||||
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
|
||||
PluginRemote = conf.GetOrDefault("plugin-remote", "btcpayserver/btcpayserver-plugins");
|
||||
RecommendedPlugins = conf.GetOrDefault("recommended-plugins", "").ToLowerInvariant().Split('\r','\n','\t',' ').Where(s => !string.IsNullOrEmpty(s)).Distinct().ToArray();
|
||||
}
|
||||
|
||||
public string PluginDir { get; set; }
|
||||
public string PluginRemote { get; set; }
|
||||
public string[] RecommendedPlugins { get; set; }
|
||||
|
||||
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
|
||||
{
|
||||
|
@ -43,6 +43,8 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
|
||||
app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
|
||||
app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);
|
||||
app.Option("--plugin-remote", "Which github repository to fetch the available plugins list (default:btcpayserver/btcpayserver-plugins)", CommandOptionType.SingleValue);
|
||||
app.Option("--recommended-plugins", "Plugins which would be marked as recommended to be installed. Separated by newline or space", CommandOptionType.MultipleValue);
|
||||
app.Option("--xforwardedproto", "If specified, set X-Forwarded-Proto to the specified value, this may be useful if your reverse proxy handle https but is not configured to add X-Forwarded-Proto (example: --xforwardedproto https)", CommandOptionType.SingleValue);
|
||||
foreach (var network in provider.GetAll().OfType<BTCPayNetwork>())
|
||||
{
|
||||
|
@ -41,12 +41,13 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet("/apps/{appId}")]
|
||||
public async Task<IActionResult> RedirectToApp(string appId)
|
||||
{
|
||||
switch (await _AppService.GetAppInfo(appId))
|
||||
|
||||
switch ((await _AppService.GetApp(appId, null)).AppType)
|
||||
{
|
||||
case ViewCrowdfundViewModel _:
|
||||
case nameof(AppType.Crowdfund):
|
||||
return RedirectToAction("ViewCrowdfund", new {appId});
|
||||
|
||||
case ViewPointOfSaleViewModel _:
|
||||
case nameof(AppType.PointOfSale):
|
||||
return RedirectToAction("ViewPointOfSale", new {appId});
|
||||
}
|
||||
|
||||
|
@ -16,13 +16,12 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet("server/plugins")]
|
||||
public async Task<IActionResult> ListPlugins(
|
||||
[FromServices] PluginService pluginService,
|
||||
[FromServices] BTCPayServerOptions btcPayServerOptions,
|
||||
string remote = "btcpayserver/btcpayserver-plugins")
|
||||
[FromServices] BTCPayServerOptions btcPayServerOptions)
|
||||
{
|
||||
IEnumerable<PluginService.AvailablePlugin> availablePlugins;
|
||||
try
|
||||
{
|
||||
availablePlugins = await pluginService.GetRemotePlugins(remote);
|
||||
availablePlugins = await pluginService.GetRemotePlugins();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@ -37,7 +36,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Installed = pluginService.LoadedPlugins,
|
||||
Available = availablePlugins,
|
||||
Remote = remote,
|
||||
Commands = pluginService.GetPendingCommands(),
|
||||
CanShowRestart = btcPayServerOptions.DockerDeployment
|
||||
};
|
||||
@ -46,7 +44,6 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
public class ListPluginsViewModel
|
||||
{
|
||||
public string Remote { get; set; }
|
||||
public IEnumerable<IBTCPayServerPlugin> Installed { get; set; }
|
||||
public IEnumerable<PluginService.AvailablePlugin> Available { get; set; }
|
||||
public (string command, string plugin)[] Commands { get; set; }
|
||||
@ -82,12 +79,20 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost("server/plugins/install")]
|
||||
public async Task<IActionResult> InstallPlugin(
|
||||
[FromServices] PluginService pluginService, string remote, string plugin)
|
||||
[FromServices] PluginService pluginService, string plugin , bool update = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
await pluginService.DownloadRemotePlugin(remote, plugin);
|
||||
pluginService.InstallPlugin(plugin);
|
||||
await pluginService.DownloadRemotePlugin(plugin);
|
||||
if (update)
|
||||
{
|
||||
pluginService.UpdatePlugin(plugin);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
pluginService.InstallPlugin(plugin);
|
||||
}
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Plugin scheduled to be installed.",
|
||||
|
@ -420,6 +420,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
SetCryptoCurrencies(model, CurrentStore);
|
||||
model.SetLanguages(_LangService, model.DefaultLang);
|
||||
model.PaymentMethodCriteria??= new List<PaymentMethodCriteriaViewModel>();
|
||||
for (var index = 0; index < model.PaymentMethodCriteria.Count; index++)
|
||||
{
|
||||
var methodCriterion = model.PaymentMethodCriteria[index];
|
||||
@ -427,10 +428,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!CurrencyValue.TryParse(methodCriterion.Value, out var value))
|
||||
{
|
||||
model.AddModelError(viewModel => viewModel.PaymentMethodCriteria[index].Value, $"{methodCriterion.PaymentMethod}: invalid format (1.0 USD)", this);
|
||||
model.AddModelError(viewModel => viewModel.PaymentMethodCriteria[index].Value,
|
||||
$"{methodCriterion.PaymentMethod}: invalid format (1.0 USD)", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
|
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 plugins = new List<IBTCPayServerPlugin>();
|
||||
|
||||
|
||||
_logger.LogInformation($"Loading plugins from {pluginsFolder}");
|
||||
Directory.CreateDirectory(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();
|
||||
plugins.AddRange(systemExtensions.SelectMany(assembly =>
|
||||
GetAllPluginTypesFromAssembly(assembly).Select(GetPluginInstanceFromType)));
|
||||
@ -41,7 +43,29 @@ namespace BTCPayServer.Plugins
|
||||
{
|
||||
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);
|
||||
|
||||
@ -71,7 +95,8 @@ namespace BTCPayServer.Plugins
|
||||
}
|
||||
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));
|
||||
webHostEnvironment.WebRootFileProvider = new CompositeFileProvider(providers);
|
||||
}
|
||||
|
||||
private static Assembly[] GetDefaultLoadedPluginAssemblies()
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies().Where(assembly =>
|
||||
assembly?.FullName?.StartsWith("BTCPayServer.Plugins",
|
||||
StringComparison.InvariantCultureIgnoreCase) is true)
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static Type[] GetAllPluginTypesFromAssembly(Assembly assembly)
|
||||
{
|
||||
return assembly.GetTypes().Where(type =>
|
||||
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) &&
|
||||
typeof(IBTCPayServerPlugin).IsAssignableFrom(type) && type != typeof(PluginService.AvailablePlugin) &&
|
||||
!type.IsAbstract).ToArray();
|
||||
}
|
||||
|
||||
@ -130,15 +154,26 @@ namespace BTCPayServer.Plugins
|
||||
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);
|
||||
switch (command.command)
|
||||
{
|
||||
case "update":
|
||||
ExecuteCommand(("delete", command.extension), pluginsFolder, true);
|
||||
ExecuteCommand(("install", command.extension), pluginsFolder, true);
|
||||
break;
|
||||
case "delete":
|
||||
if (Directory.Exists(dirName))
|
||||
{
|
||||
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;
|
||||
@ -147,6 +182,11 @@ namespace BTCPayServer.Plugins
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
ZipFile.ExtractToDirectory(fileName, dirName, true);
|
||||
if (!ignoreOrder)
|
||||
{
|
||||
File.AppendAllLines(Path.Combine(pluginsFolder, "order"), new[] {command.extension});
|
||||
}
|
||||
|
||||
File.Delete(fileName);
|
||||
}
|
||||
|
||||
|
@ -31,10 +31,10 @@ namespace BTCPayServer.Plugins
|
||||
|
||||
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
|
||||
|
||||
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins(string remote)
|
||||
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins()
|
||||
{
|
||||
var resp = await _githubClient
|
||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{remote}/contents"));
|
||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
|
||||
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json", StringComparison.InvariantCulture)).Select(async file =>
|
||||
{
|
||||
@ -43,11 +43,11 @@ namespace BTCPayServer.Plugins
|
||||
}));
|
||||
}
|
||||
|
||||
public async Task DownloadRemotePlugin(string remote, string plugin)
|
||||
public async Task DownloadRemotePlugin(string plugin)
|
||||
{
|
||||
var dest = _btcPayServerOptions.PluginDir;
|
||||
var resp = await _githubClient
|
||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{remote}/contents"));
|
||||
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
|
||||
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
|
||||
if (ext is null)
|
||||
@ -66,6 +66,11 @@ namespace BTCPayServer.Plugins
|
||||
UninstallPlugin(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)
|
||||
{
|
||||
@ -93,7 +98,7 @@ namespace BTCPayServer.Plugins
|
||||
public string Description { get; set; }
|
||||
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,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
||||
|
@ -21,7 +21,8 @@
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_UPDATEURL": "",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test"
|
||||
},
|
||||
"applicationUrl": "http://127.0.0.1:14142/"
|
||||
},
|
||||
@ -53,7 +54,8 @@
|
||||
"BTCPAY_DEBUGLOG": "debug.log",
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
},
|
||||
@ -87,7 +89,8 @@
|
||||
"BTCPAY_DEBUGLOG": "debug.log",
|
||||
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
|
||||
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
||||
"BTCPAY_DOCKERDEPLOYMENT": "true",
|
||||
"BTCPAY_RECOMMENDED-PLUGINS": "BTCPayServer.Plugins.Test"
|
||||
},
|
||||
"applicationUrl": "https://localhost:14142/"
|
||||
}
|
||||
|
@ -248,13 +248,13 @@ namespace BTCPayServer.Services.Apps
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AppData> GetApp(string appId, AppType appType, bool includeStore = false)
|
||||
public async Task<AppData> GetApp(string appId, AppType? appType, bool includeStore = false)
|
||||
{
|
||||
using (var ctx = _ContextFactory.CreateContext())
|
||||
{
|
||||
var query = ctx.Apps
|
||||
.Where(us => us.Id == appId &&
|
||||
us.AppType == appType.ToString());
|
||||
(appType == null || us.AppType == appType.ToString()));
|
||||
|
||||
if (includeStore)
|
||||
{
|
||||
|
@ -1,8 +1,93 @@
|
||||
@using BTCPayServer.Configuration
|
||||
@using BTCPayServer.Contracts
|
||||
@model BTCPayServer.Controllers.ServerController.ListPluginsViewModel
|
||||
@inject BTCPayServerOptions BTCPayServerOptions
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Plugins);
|
||||
var installed = Model.Installed.Select(plugin => plugin.Identifier);
|
||||
var availableAndNotInstalled = Model.Available.Where(plugin => !installed.Contains(plugin.Identifier));
|
||||
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier.ToLowerInvariant(), plugin => plugin.Version);
|
||||
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"/>
|
||||
@ -27,28 +112,94 @@
|
||||
@foreach (var plugin in Model.Installed)
|
||||
{
|
||||
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="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">@plugin.Name</h4>
|
||||
<h5 class="card-subtitle mb-3 text-muted d-flex align-items-center">
|
||||
@plugin.Version
|
||||
@if (plugin.SystemPlugin)
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<h4 class="card-title" title="@plugin.Identifier" data-toggle="tooltip">@plugin.Name</h4>
|
||||
<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>
|
||||
<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">
|
||||
@if (Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)))
|
||||
@if (pendingAction)
|
||||
{
|
||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending action</button>
|
||||
@ -56,15 +207,22 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (updateAvailable)
|
||||
@if (updateAvailable && DependenciesMet(matchedAvailable.Dependencies))
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-remote="@Model.Remote" 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>
|
||||
</form>
|
||||
}
|
||||
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-outline-danger">Uninstall</button>
|
||||
</form>
|
||||
@if (DependentOn(plugin.Identifier))
|
||||
{
|
||||
<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>
|
||||
}
|
||||
@ -77,28 +235,61 @@
|
||||
{
|
||||
<h3 class="mb-3">Available Plugins</h3>
|
||||
<div class="row mb-4">
|
||||
@foreach (var plugin in availableAndNotInstalled)
|
||||
@foreach (var pluginT in availableAndNotInstalled)
|
||||
{
|
||||
var plugin = pluginT.Item1;
|
||||
<div class="col col-12 col-lg-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">@plugin.Name</h4>
|
||||
<h5 class="card-subtitle mb-3 text-muted">@plugin.Version</h5>
|
||||
<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">
|
||||
<span>@plugin.Version</span>
|
||||
@if (pluginT.Item2)
|
||||
{
|
||||
<span data-toggle="tooltip" title="This plugin has been recommended to be installed by your deployment method.">Recommended <span class="fa fa-question-circle-o"></span></span>
|
||||
}
|
||||
</h5>
|
||||
<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>
|
||||
@{
|
||||
var pending = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
<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">
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending install</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
else if (DependenciesMet(plugin.Dependencies))
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-remote="@Model.Remote">
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-primary">Install</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger">
|
||||
Cannot install until dependencies are met
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,7 @@
|
||||
@using BTCPayServer.Services
|
||||
@using BTCPayServer.Services.Mails;
|
||||
@model BTCPayServer.Services.PoliciesSettings
|
||||
@inject BTCPayServer.Services.SettingsRepository _SettingsRepository
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Policies);
|
||||
}
|
||||
@ -14,9 +16,22 @@
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input asp-for="RequiresConfirmedEmail" type="checkbox" class="form-check-input"/>
|
||||
@{
|
||||
var emailSettings = (await _SettingsRepository.GetSettingAsync<EmailSettings>()) ?? new EmailSettings();
|
||||
/**
|
||||
* The "|| Model.RequiresConfirmedEmail" check is for the case when a user had checked
|
||||
* the checkbox without first configuring the e-mail settings so that they can uncheck it.
|
||||
**/
|
||||
var isEmailConfigured = emailSettings.IsComplete() || Model.RequiresConfirmedEmail;
|
||||
}
|
||||
<input asp-for="RequiresConfirmedEmail" type="checkbox" class="form-check-input" disabled="@(isEmailConfigured ? null : "disabled")" />
|
||||
<label asp-for="RequiresConfirmedEmail" class="form-check-label"></label>
|
||||
<span asp-validation-for="RequiresConfirmedEmail" class="text-danger"></span>
|
||||
@if (!isEmailConfigured) {
|
||||
<div>
|
||||
<span class="text-secondary">Your email server has not been configured. <a asp-controller="Server" asp-action="Emails">Please configure it first.</a></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input asp-for="LockSubscription" type="checkbox" class="form-check-input"/>
|
||||
|
@ -105,10 +105,10 @@
|
||||
X_X = kraken(X_X);
|
||||
</code>
|
||||
</pre>
|
||||
<p>However, <code>kraken</code> does not support the <code>BTC_CAD</code> pair. For this reason you can add a rule mapping all <code>X_CAD</code> to <code>quadrigacx</code>, a Canadian exchange.</p>
|
||||
<p>However, <code>kraken</code> does not support the <code>BTC_CAD</code> pair. For this reason you can add a rule mapping all <code>X_CAD</code> to <code>ndax</code>, a Canadian exchange.</p>
|
||||
<pre>
|
||||
<code>
|
||||
X_CAD = quadrigacx(X_CAD);
|
||||
X_CAD = ndax(X_CAD);
|
||||
X_X = kraken(X_X);
|
||||
</code>
|
||||
</pre>
|
||||
@ -120,19 +120,19 @@
|
||||
<pre>
|
||||
<code>
|
||||
DOGE_X = bittrex(DOGE_BTC) * BTC_X
|
||||
X_CAD = quadrigacx(X_CAD);
|
||||
X_CAD = ndax(X_CAD);
|
||||
X_X = kraken(X_X);
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
With <code>DOGE_USD</code> will be expanded to <code>bittrex(DOGE_BTC) * kraken(BTC_USD)</code>. And <code>DOGE_CAD</code> will be expanded to <code>bittrex(DOGE_BTC) * quadrigacx(BTC_CAD)</code>. <br />
|
||||
With <code>DOGE_USD</code> will be expanded to <code>bittrex(DOGE_BTC) * kraken(BTC_USD)</code>. And <code>DOGE_CAD</code> will be expanded to <code>bittrex(DOGE_BTC) * ndax(BTC_CAD)</code>. <br />
|
||||
However, we advise you to write it that way to increase coverage so that <code>DOGE_BTC</code> is also supported:
|
||||
</p>
|
||||
<pre>
|
||||
<code>
|
||||
DOGE_X = DOGE_BTC * BTC_X
|
||||
DOGE_BTC = bittrex(DOGE_BTC)
|
||||
X_CAD = quadrigacx(X_CAD);
|
||||
X_CAD = ndax(X_CAD);
|
||||
X_X = kraken(X_X);
|
||||
</code>
|
||||
</pre>
|
||||
|
@ -246,12 +246,20 @@
|
||||
<span class="fa fa-question-circle-o" title="More information..."></span>
|
||||
</a>
|
||||
</div>
|
||||
<select asp-for="SpeedPolicy" class="form-control w-auto">
|
||||
<script>
|
||||
function handleSpeedPolicyChange(select) {
|
||||
document.getElementById('unconfirmed-warning').hidden = select.value !== '0';
|
||||
}
|
||||
</script>
|
||||
<select asp-for="SpeedPolicy" class="form-control w-auto" onchange="handleSpeedPolicyChange(this)">
|
||||
<option value="0">Is unconfirmed</option>
|
||||
<option value="1">Has at least 1 confirmation</option>
|
||||
<option value="3">Has at least 2 confirmations</option>
|
||||
<option value="2">Has at least 6 confirmations</option>
|
||||
</select>
|
||||
<div class="alert alert-warning my-2" hidden="@(Model.SpeedPolicy != 0)" id="unconfirmed-warning" role="alert">
|
||||
Choosing to accept an unconfirmed invoice can lead to double-spending and is strongly discouraged.
|
||||
</div>
|
||||
<span asp-validation-for="SpeedPolicy" class="text-danger"></span>
|
||||
<button name="command" type="submit" class="btn btn-primary mt-3" value="Save" id="Save">Save Store Settings</button>
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
WORKDIR /source
|
||||
COPY nuget.config nuget.config
|
||||
COPY Build/Common.csproj Build/Common.csproj
|
||||
COPY BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
|
||||
COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj
|
||||
COPY BTCPayServer.Rating/BTCPayServer.Rating.csproj BTCPayServer.Rating/BTCPayServer.Rating.csproj
|
||||
@ -13,6 +14,7 @@ COPY BTCPayServer.Common/. BTCPayServer.Common/.
|
||||
COPY BTCPayServer.Rating/. BTCPayServer.Rating/.
|
||||
COPY BTCPayServer.Data/. BTCPayServer.Data/.
|
||||
COPY BTCPayServer.Client/. BTCPayServer.Client/.
|
||||
COPY BTCPayServer.Abstractions/. BTCPayServer.Abstractions/.
|
||||
COPY BTCPayServer/. BTCPayServer/.
|
||||
COPY Build/Version.csproj Build/Version.csproj
|
||||
ARG CONFIGURATION_NAME=Release
|
||||
|
@ -7,6 +7,7 @@ RUN apt-get update \
|
||||
WORKDIR /source
|
||||
COPY nuget.config nuget.config
|
||||
COPY Build/Common.csproj Build/Common.csproj
|
||||
COPY BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
|
||||
COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj
|
||||
COPY BTCPayServer.Rating/BTCPayServer.Rating.csproj BTCPayServer.Rating/BTCPayServer.Rating.csproj
|
||||
@ -17,6 +18,7 @@ COPY BTCPayServer.Common/. BTCPayServer.Common/.
|
||||
COPY BTCPayServer.Rating/. BTCPayServer.Rating/.
|
||||
COPY BTCPayServer.Data/. BTCPayServer.Data/.
|
||||
COPY BTCPayServer.Client/. BTCPayServer.Client/.
|
||||
COPY BTCPayServer.Abstractions/. BTCPayServer.Abstractions/.
|
||||
COPY BTCPayServer/. BTCPayServer/.
|
||||
COPY Build/Version.csproj Build/Version.csproj
|
||||
ARG CONFIGURATION_NAME=Release
|
||||
|
@ -8,6 +8,7 @@ RUN apt-get update \
|
||||
WORKDIR /source
|
||||
COPY nuget.config nuget.config
|
||||
COPY Build/Common.csproj Build/Common.csproj
|
||||
COPY BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
|
||||
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
|
||||
COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj
|
||||
COPY BTCPayServer.Rating/BTCPayServer.Rating.csproj BTCPayServer.Rating/BTCPayServer.Rating.csproj
|
||||
@ -18,6 +19,7 @@ COPY BTCPayServer.Common/. BTCPayServer.Common/.
|
||||
COPY BTCPayServer.Rating/. BTCPayServer.Rating/.
|
||||
COPY BTCPayServer.Data/. BTCPayServer.Data/.
|
||||
COPY BTCPayServer.Client/. BTCPayServer.Client/.
|
||||
COPY BTCPayServer.Abstractions/. BTCPayServer.Abstractions/.
|
||||
COPY BTCPayServer/. BTCPayServer/.
|
||||
COPY Build/Version.csproj Build/Version.csproj
|
||||
ARG CONFIGURATION_NAME=Release
|
||||
|
Reference in New Issue
Block a user