mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
BTCPay Server Extensions (#1925)
* BTCPay Server Extensions  * cleanup * fix * Polish UI a bit,detect when docker deployment
This commit is contained in:
11
BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Normal file
11
BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||||
|
<Import Project="../Build/Common.csproj" />
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||||
|
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Contracts
|
||||||
|
{
|
||||||
|
public interface IBTCPayServerExtension
|
||||||
|
{
|
||||||
|
public string Identifier { get;}
|
||||||
|
string Name { get; }
|
||||||
|
Version Version { get; }
|
||||||
|
string Description { get; }
|
||||||
|
|
||||||
|
void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices);
|
||||||
|
void Execute(IServiceCollection applicationBuilder);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
BTCPayServer.Abstractions/Contracts/INotificationHandler.cs
Normal file
20
BTCPayServer.Abstractions/Contracts/INotificationHandler.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Contracts
|
||||||
|
{
|
||||||
|
public interface INotificationHandler
|
||||||
|
{
|
||||||
|
string NotificationType { get; }
|
||||||
|
Type NotificationBlobType { get; }
|
||||||
|
void FillViewModel(object notification, NotificationViewModel vm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NotificationViewModel
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public DateTimeOffset Created { get; set; }
|
||||||
|
public string Body { get; set; }
|
||||||
|
public string ActionLink { get; set; }
|
||||||
|
public bool Seen { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
21
BTCPayServer.Abstractions/Contracts/IStoreNavExtension.cs
Normal file
21
BTCPayServer.Abstractions/Contracts/IStoreNavExtension.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
namespace BTCPayServer.Contracts
|
||||||
|
{
|
||||||
|
public interface INavExtension
|
||||||
|
{
|
||||||
|
string Partial { get; }
|
||||||
|
|
||||||
|
string Location { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NavExtension: INavExtension
|
||||||
|
{
|
||||||
|
public NavExtension(string partial, string location)
|
||||||
|
{
|
||||||
|
Partial = partial;
|
||||||
|
Location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Partial { get; }
|
||||||
|
public string Location { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,5 +6,4 @@ namespace BTCPayServer.Contracts
|
|||||||
|
|
||||||
string Partial { get; }
|
string Partial { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,8 @@ namespace BTCPayServer
|
|||||||
var settings = new BTCPayDefaultSettings();
|
var settings = new BTCPayDefaultSettings();
|
||||||
_Settings.Add(chainType, settings);
|
_Settings.Add(chainType, settings);
|
||||||
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
|
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
|
||||||
|
settings.DefaultExtensionDirectory =
|
||||||
|
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Extensions");
|
||||||
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
|
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
|
||||||
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
|
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
|
||||||
chainType == NetworkType.Regtest ? 23002 :
|
chainType == NetworkType.Regtest ? 23002 :
|
||||||
@@ -39,6 +41,7 @@ namespace BTCPayServer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string DefaultDataDirectory { get; set; }
|
public string DefaultDataDirectory { get; set; }
|
||||||
|
public string DefaultExtensionDirectory { get; set; }
|
||||||
public string DefaultConfigurationFile { get; set; }
|
public string DefaultConfigurationFile { get; set; }
|
||||||
public int DefaultPort { get; set; }
|
public int DefaultPort { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
14
BTCPayServer.Test/BTCPayServer.Test.csproj
Normal file
14
BTCPayServer.Test/BTCPayServer.Test.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||||
|
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||||
|
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
|
||||||
|
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
|
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||||
|
<EmbeddedResource Include="Resources\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
BIN
BTCPayServer.Test/Resources/img/screengrab.png
Normal file
BIN
BTCPayServer.Test/Resources/img/screengrab.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
25
BTCPayServer.Test/TestExtension.cs
Normal file
25
BTCPayServer.Test/TestExtension.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Test
|
||||||
|
{
|
||||||
|
public class TestExtension: IBTCPayServerExtension
|
||||||
|
{
|
||||||
|
public string Identifier { get; } = "BTCPayServer.Test";
|
||||||
|
public string Name { get; } = "Test Plugin!";
|
||||||
|
public Version Version { get; } = new Version(1,0,0,0);
|
||||||
|
public string Description { get; } = "This is a description of the loaded test extension!";
|
||||||
|
public void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<INavExtension>(new NavExtension("TestExtensionNavExtension", "header-nav"));
|
||||||
|
services.AddHostedService<ApplicationPartsLogger>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
BTCPayServer.Test/TestExtensionController.cs
Normal file
16
BTCPayServer.Test/TestExtensionController.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Test
|
||||||
|
{
|
||||||
|
[Route("extensions/test")]
|
||||||
|
public class TestExtensionController : Controller
|
||||||
|
{
|
||||||
|
// GET
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
<li class="nav-item"><a asp-controller="TestExtension" asp-action="Index" class="nav-link js-scroll-trigger" >Dear Nicolas Dorier</a></li>
|
||||||
9
BTCPayServer.Test/Views/TestExtension/Index.cshtml
Normal file
9
BTCPayServer.Test/Views/TestExtension/Index.cshtml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<section>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Challenge Completed!!</h1>
|
||||||
|
Here is also an image loaded from the plugin<br/>
|
||||||
|
<a href="https://twitter.com/NicolasDorier/status/1307221679014256640">
|
||||||
|
<img src="/Resources/img/screengrab.png"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
1
BTCPayServer.Test/Views/_ViewImports.cshtml
Normal file
1
BTCPayServer.Test/Views/_ViewImports.cshtml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
44
BTCPayServer.Test/ss.cs
Normal file
44
BTCPayServer.Test/ss.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ApplicationParts;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Test
|
||||||
|
{
|
||||||
|
public class ApplicationPartsLogger : IHostedService
|
||||||
|
{
|
||||||
|
private readonly ILogger<ApplicationPartsLogger> _logger;
|
||||||
|
private readonly ApplicationPartManager _partManager;
|
||||||
|
|
||||||
|
public ApplicationPartsLogger(ILogger<ApplicationPartsLogger> logger, ApplicationPartManager partManager)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_partManager = partManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Get the names of all the application parts. This is the short assembly name for AssemblyParts
|
||||||
|
var applicationParts = _partManager.ApplicationParts.Select(x => x.Name);
|
||||||
|
|
||||||
|
// Create a controller feature, and populate it from the application parts
|
||||||
|
var controllerFeature = new ControllerFeature();
|
||||||
|
_partManager.PopulateFeature(controllerFeature);
|
||||||
|
|
||||||
|
// Get the names of all of the controllers
|
||||||
|
var controllers = controllerFeature.Controllers.Select(x => x.Name);
|
||||||
|
|
||||||
|
// Log the application parts and controllers
|
||||||
|
_logger.LogInformation("Found the following application parts: '{ApplicationParts}' with the following controllers: '{Controllers}'",
|
||||||
|
string.Join(", ", applicationParts), string.Join(", ", controllers));
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by the interface
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
160
BTCPayServer/BTCPayExtensions/ExtensionManager.cs
Normal file
160
BTCPayServer/BTCPayExtensions/ExtensionManager.cs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using BTCPayServer.Configuration;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
|
using McMaster.NETCore.Plugins;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public static class ExtensionManager
|
||||||
|
{
|
||||||
|
public const string BTCPayExtensionSuffix =".btcpay";
|
||||||
|
private static readonly List<Assembly> _pluginAssemblies = new List<Assembly>();
|
||||||
|
private static ILogger _logger;
|
||||||
|
|
||||||
|
public static IMvcBuilder AddExtensions(this IMvcBuilder mvcBuilder, IServiceCollection serviceCollection,
|
||||||
|
IConfiguration config, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_logger = loggerFactory.CreateLogger(typeof(ExtensionManager));
|
||||||
|
var extensionsFolder = config.GetExtensionDir(DefaultConfiguration.GetNetworkType(config));
|
||||||
|
var extensions = new List<IBTCPayServerExtension>();
|
||||||
|
|
||||||
|
_logger.LogInformation($"Loading extensions from {extensionsFolder}");
|
||||||
|
Directory.CreateDirectory(extensionsFolder);
|
||||||
|
ExecuteCommands(extensionsFolder);
|
||||||
|
List<(PluginLoader, Assembly, IFileProvider)> plugins = new List<(PluginLoader, Assembly, IFileProvider)>();
|
||||||
|
foreach (var dir in Directory.GetDirectories(extensionsFolder))
|
||||||
|
{
|
||||||
|
var pluginName = Path.GetFileName(dir);
|
||||||
|
|
||||||
|
var plugin = PluginLoader.CreateFromAssemblyFile(
|
||||||
|
Path.Combine(dir, pluginName + ".dll"), // create a plugin from for the .dll file
|
||||||
|
config =>
|
||||||
|
// this ensures that the version of MVC is shared between this app and the plugin
|
||||||
|
config.PreferSharedTypes = true);
|
||||||
|
|
||||||
|
mvcBuilder.AddPluginLoader(plugin);
|
||||||
|
var pluginAssembly = plugin.LoadDefaultAssembly();
|
||||||
|
_pluginAssemblies.Add(pluginAssembly);
|
||||||
|
var fileProvider = CreateEmbeddedFileProviderForAssembly(pluginAssembly);
|
||||||
|
plugins.Add((plugin, pluginAssembly, fileProvider));
|
||||||
|
extensions.AddRange(GetAllExtensionTypesFromAssembly(pluginAssembly)
|
||||||
|
.Select(GetExtensionInstanceFromType));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var extension in extensions)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Adding and executing extension {extension.Identifier} - {extension.Version}");
|
||||||
|
serviceCollection.AddSingleton(extension);
|
||||||
|
extension.Execute(serviceCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mvcBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UseExtensions(this IApplicationBuilder applicationBuilder)
|
||||||
|
{
|
||||||
|
foreach (var extension in applicationBuilder.ApplicationServices
|
||||||
|
.GetServices<IBTCPayServerExtension>())
|
||||||
|
{
|
||||||
|
extension.Execute(applicationBuilder,
|
||||||
|
applicationBuilder.ApplicationServices);
|
||||||
|
}
|
||||||
|
|
||||||
|
var webHostEnvironment = applicationBuilder.ApplicationServices.GetService<IWebHostEnvironment>();
|
||||||
|
List<IFileProvider> providers = new List<IFileProvider>() {webHostEnvironment.WebRootFileProvider};
|
||||||
|
providers.AddRange(
|
||||||
|
_pluginAssemblies
|
||||||
|
.Select(CreateEmbeddedFileProviderForAssembly));
|
||||||
|
webHostEnvironment.WebRootFileProvider = new CompositeFileProvider(providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Type[] GetAllExtensionTypesFromAssembly(Assembly assembly)
|
||||||
|
{
|
||||||
|
return assembly.GetTypes().Where(type =>
|
||||||
|
typeof(IBTCPayServerExtension).IsAssignableFrom(type) &&
|
||||||
|
!type.IsAbstract).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IBTCPayServerExtension GetExtensionInstanceFromType(Type type)
|
||||||
|
{
|
||||||
|
return (IBTCPayServerExtension)Activator.CreateInstance(type, Array.Empty<object>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IFileProvider CreateEmbeddedFileProviderForAssembly(Assembly assembly)
|
||||||
|
{
|
||||||
|
return new EmbeddedFileProvider(assembly);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExecuteCommands(string extensionsFolder)
|
||||||
|
{
|
||||||
|
var pendingCommands = GetPendingCommands(extensionsFolder);
|
||||||
|
foreach (var command in pendingCommands)
|
||||||
|
{
|
||||||
|
ExecuteCommand(command, extensionsFolder);
|
||||||
|
}
|
||||||
|
File.Delete(Path.Combine(extensionsFolder, "commands"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ExecuteCommand((string command, string extension) command, string extensionsFolder)
|
||||||
|
{
|
||||||
|
var dirName = Path.Combine(extensionsFolder, command.extension);
|
||||||
|
switch (command.command)
|
||||||
|
{
|
||||||
|
case "delete":
|
||||||
|
if (Directory.Exists(dirName))
|
||||||
|
{
|
||||||
|
Directory.Delete(dirName, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "install":
|
||||||
|
var fileName = dirName + BTCPayExtensionSuffix;
|
||||||
|
if (File.Exists(fileName))
|
||||||
|
{
|
||||||
|
ZipFile.ExtractToDirectory(fileName, dirName, true);
|
||||||
|
File.Delete(fileName);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (string command, string extension)[] GetPendingCommands(string extensionsFolder)
|
||||||
|
{
|
||||||
|
if (!File.Exists(Path.Combine(extensionsFolder, "commands")))
|
||||||
|
return Array.Empty<(string command, string extension)>();
|
||||||
|
var commands = File.ReadAllLines(Path.Combine(extensionsFolder, "commands"));
|
||||||
|
return commands.Select(s =>
|
||||||
|
{
|
||||||
|
var split = s.Split(':');
|
||||||
|
return (split[0].ToLower(CultureInfo.InvariantCulture), split[1]);
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void QueueCommands(string extensionsFolder, params ( string action, string extension)[] commands)
|
||||||
|
{
|
||||||
|
File.AppendAllLines(Path.Combine(extensionsFolder, "commands"),
|
||||||
|
commands.Select((tuple) => $"{tuple.action}:{tuple.extension}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CancelCommands(string extensionDir, string extension)
|
||||||
|
{
|
||||||
|
var cmds = GetPendingCommands(extensionDir).Where(tuple =>
|
||||||
|
!tuple.extension.Equals(extension, StringComparison.InvariantCultureIgnoreCase)).ToArray();
|
||||||
|
|
||||||
|
File.Delete(Path.Combine(extensionDir, "commands"));
|
||||||
|
QueueCommands(extensionDir, cmds);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
BTCPayServer/BTCPayExtensions/ExtensionService.cs
Normal file
124
BTCPayServer/BTCPayExtensions/ExtensionService.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Configuration;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public class ExtensionService
|
||||||
|
{
|
||||||
|
private readonly BTCPayServerOptions _btcPayServerOptions;
|
||||||
|
private readonly HttpClient _githubClient;
|
||||||
|
|
||||||
|
public ExtensionService(IEnumerable<IBTCPayServerExtension> btcPayServerExtensions,
|
||||||
|
IHttpClientFactory httpClientFactory, BTCPayServerOptions btcPayServerOptions)
|
||||||
|
{
|
||||||
|
LoadedExtensions = btcPayServerExtensions;
|
||||||
|
_githubClient = httpClientFactory.CreateClient();
|
||||||
|
_githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1"));
|
||||||
|
_btcPayServerOptions = btcPayServerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IBTCPayServerExtension> LoadedExtensions { get; }
|
||||||
|
|
||||||
|
public async Task<IEnumerable<AvailableExtension>> GetRemoteExtensions(string remote)
|
||||||
|
{
|
||||||
|
var resp = await _githubClient
|
||||||
|
.GetStringAsync(new Uri($"https://api.github.com/repos/{remote}/contents"));
|
||||||
|
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||||
|
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{ExtensionManager.BTCPayExtensionSuffix}.json", StringComparison.InvariantCulture)).Select(async file =>
|
||||||
|
{
|
||||||
|
return await _githubClient.GetStringAsync(file.DownloadUrl).ContinueWith(
|
||||||
|
task => JsonConvert.DeserializeObject<AvailableExtension>(task.Result), TaskScheduler.Current);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DownloadRemoteExtension(string remote, string extension)
|
||||||
|
{
|
||||||
|
var dest = _btcPayServerOptions.ExtensionDir;
|
||||||
|
var resp = await _githubClient
|
||||||
|
.GetStringAsync(new Uri($"https://api.github.com/repos/{remote}/contents"));
|
||||||
|
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
|
||||||
|
var ext = files.SingleOrDefault(file => file.Name == $"{extension}{ExtensionManager.BTCPayExtensionSuffix}");
|
||||||
|
if (ext is null)
|
||||||
|
{
|
||||||
|
throw new Exception("Extension not found on remote");
|
||||||
|
}
|
||||||
|
|
||||||
|
var filedest = Path.Combine(dest, ext.Name);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
|
||||||
|
new WebClient().DownloadFile(new Uri(ext.DownloadUrl), filedest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstallExtension(string extension)
|
||||||
|
{
|
||||||
|
var dest = _btcPayServerOptions.ExtensionDir;
|
||||||
|
UninstallExtension(extension);
|
||||||
|
ExtensionManager.QueueCommands(dest, ("install", extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadExtension(IFormFile extension)
|
||||||
|
{
|
||||||
|
var dest = _btcPayServerOptions.ExtensionDir;
|
||||||
|
var filedest = Path.Combine(dest, extension.FileName);
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
|
||||||
|
if (Path.GetExtension(filedest) == ExtensionManager.BTCPayExtensionSuffix)
|
||||||
|
{
|
||||||
|
await using var stream = new FileStream(filedest, FileMode.Create);
|
||||||
|
await extension.CopyToAsync(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UninstallExtension(string extension)
|
||||||
|
{
|
||||||
|
var dest = _btcPayServerOptions.ExtensionDir;
|
||||||
|
ExtensionManager.QueueCommands(dest, ("delete", extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AvailableExtension : IBTCPayServerExtension
|
||||||
|
{
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public Version Version { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public void Execute(IApplicationBuilder applicationBuilder,
|
||||||
|
IServiceProvider applicationBuilderApplicationServices)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(IServiceCollection applicationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GithubFile
|
||||||
|
{
|
||||||
|
[JsonProperty("name")] public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("sha")] public string Sha { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("download_url")] public string DownloadUrl { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public (string command, string extension)[] GetPendingCommands()
|
||||||
|
{
|
||||||
|
return ExtensionManager.GetPendingCommands(_btcPayServerOptions.ExtensionDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelCommands(string extension)
|
||||||
|
{
|
||||||
|
ExtensionManager.CancelCommands(_btcPayServerOptions.ExtensionDir, extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,11 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
|
|
||||||
|
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
||||||
<Compile Remove="Build\**" />
|
<Compile Remove="Build\**" />
|
||||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||||
@@ -52,6 +55,7 @@
|
|||||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
|
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
|
||||||
|
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
@@ -141,6 +145,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />
|
||||||
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
|
||||||
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
|
<ProjectReference Include="..\BTCPayServer.Data\BTCPayServer.Data.csproj" />
|
||||||
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
|
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
using BTCPayServer.Models.NotificationViewModels;
|
||||||
|
|
||||||
namespace BTCPayServer.Components.NotificationsDropdown
|
namespace BTCPayServer.Components.NotificationsDropdown
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ namespace BTCPayServer.Configuration
|
|||||||
{
|
{
|
||||||
NetworkType = DefaultConfiguration.GetNetworkType(conf);
|
NetworkType = DefaultConfiguration.GetNetworkType(conf);
|
||||||
DataDir = conf.GetDataDir(NetworkType);
|
DataDir = conf.GetDataDir(NetworkType);
|
||||||
|
ExtensionDir = conf.GetExtensionDir(NetworkType);
|
||||||
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
|
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
|
||||||
|
|
||||||
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != NetworkType.Regtest)
|
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != NetworkType.Regtest)
|
||||||
@@ -166,6 +167,7 @@ namespace BTCPayServer.Configuration
|
|||||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||||
MySQLConnectionString = conf.GetOrDefault<string>("mysql", null);
|
MySQLConnectionString = conf.GetOrDefault<string>("mysql", null);
|
||||||
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
BundleJsCss = conf.GetOrDefault<bool>("bundlejscss", true);
|
||||||
|
DockerDeployment = conf.GetOrDefault<bool>("dockerdeployment", true);
|
||||||
AllowAdminRegistration = conf.GetOrDefault<bool>("allow-admin-registration", false);
|
AllowAdminRegistration = conf.GetOrDefault<bool>("allow-admin-registration", false);
|
||||||
TorrcFile = conf.GetOrDefault<string>("torrcfile", null);
|
TorrcFile = conf.GetOrDefault<string>("torrcfile", null);
|
||||||
|
|
||||||
@@ -239,6 +241,8 @@ namespace BTCPayServer.Configuration
|
|||||||
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
|
DisableRegistration = conf.GetOrDefault<bool>("disable-registration", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ExtensionDir { get; set; }
|
||||||
|
|
||||||
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
|
private SSHSettings ParseSSHConfiguration(IConfiguration conf)
|
||||||
{
|
{
|
||||||
var settings = new SSHSettings();
|
var settings = new SSHSettings();
|
||||||
@@ -281,6 +285,7 @@ namespace BTCPayServer.Configuration
|
|||||||
public ExternalServices ExternalServices { get; set; } = new ExternalServices();
|
public ExternalServices ExternalServices { get; set; } = new ExternalServices();
|
||||||
|
|
||||||
public BTCPayNetworkProvider NetworkProvider { get; set; }
|
public BTCPayNetworkProvider NetworkProvider { get; set; }
|
||||||
|
public bool DockerDeployment { get; set; }
|
||||||
public string PostgresConnectionString
|
public string PostgresConnectionString
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|||||||
@@ -67,5 +67,11 @@ namespace BTCPayServer.Configuration
|
|||||||
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
|
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
|
||||||
return configuration.GetOrDefault("datadir", defaultSettings.DefaultDataDirectory);
|
return configuration.GetOrDefault("datadir", defaultSettings.DefaultDataDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetExtensionDir(this IConfiguration configuration, NetworkType networkType)
|
||||||
|
{
|
||||||
|
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
|
||||||
|
return configuration.GetOrDefault("extensiondir", defaultSettings.DefaultExtensionDirectory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace BTCPayServer.Contracts
|
|
||||||
{
|
|
||||||
public interface IStoreNavExtension
|
|
||||||
{
|
|
||||||
string Partial { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
109
BTCPayServer/Controllers/ServerController.Extensions.cs
Normal file
109
BTCPayServer/Controllers/ServerController.Extensions.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Configuration;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
|
using BTCPayServer.Models;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Controllers
|
||||||
|
{
|
||||||
|
public partial class ServerController
|
||||||
|
{
|
||||||
|
[HttpGet("server/extensions")]
|
||||||
|
public async Task<IActionResult> ListExtensions(
|
||||||
|
[FromServices] ExtensionService extensionService,
|
||||||
|
[FromServices] BTCPayServerOptions btcPayServerOptions,
|
||||||
|
string remote = "kukks/btcpayserver-extensions")
|
||||||
|
{
|
||||||
|
var res = new ListExtensionsViewModel()
|
||||||
|
{
|
||||||
|
Installed = extensionService.LoadedExtensions,
|
||||||
|
Available = await extensionService.GetRemoteExtensions(remote),
|
||||||
|
Remote = remote,
|
||||||
|
Commands = extensionService.GetPendingCommands(),
|
||||||
|
CanShowRestart = btcPayServerOptions.DockerDeployment
|
||||||
|
};
|
||||||
|
return View(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ListExtensionsViewModel
|
||||||
|
{
|
||||||
|
public string Remote { get; set; }
|
||||||
|
public IEnumerable<IBTCPayServerExtension> Installed { get; set; }
|
||||||
|
public IEnumerable<ExtensionService.AvailableExtension> Available { get; set; }
|
||||||
|
public (string command, string extension)[] Commands { get; set; }
|
||||||
|
public bool CanShowRestart { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("server/extensions/uninstall")]
|
||||||
|
public IActionResult UnInstallExtension(
|
||||||
|
[FromServices] ExtensionService extensionService, string extension)
|
||||||
|
{
|
||||||
|
extensionService.UninstallExtension(extension);
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
|
{
|
||||||
|
Message = "Extension scheduled to be uninstalled",
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Success
|
||||||
|
});
|
||||||
|
|
||||||
|
return RedirectToAction("ListExtensions");
|
||||||
|
}
|
||||||
|
[HttpPost("server/extensions/cancel")]
|
||||||
|
public IActionResult CancelExtensionCommands(
|
||||||
|
[FromServices] ExtensionService extensionService, string extension)
|
||||||
|
{
|
||||||
|
extensionService.CancelCommands(extension);
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
|
{
|
||||||
|
Message = "Updated",
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Success
|
||||||
|
});
|
||||||
|
|
||||||
|
return RedirectToAction("ListExtensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("server/extensions/install")]
|
||||||
|
public async Task<IActionResult> InstallExtension(
|
||||||
|
[FromServices] ExtensionService extensionService, string remote, string extension)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await extensionService.DownloadRemoteExtension(remote, extension);
|
||||||
|
extensionService.InstallExtension(extension);
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
|
{
|
||||||
|
Message = "Extension scheduled to be installed.",
|
||||||
|
Severity = StatusMessageModel.StatusSeverity.Success
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||||
|
{
|
||||||
|
Message = e.Message, Severity = StatusMessageModel.StatusSeverity.Error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction("ListExtensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("server/extensions/upload")]
|
||||||
|
public async Task<IActionResult> UploadExtension([FromServices] ExtensionService extensionService,
|
||||||
|
List<IFormFile> files)
|
||||||
|
{
|
||||||
|
foreach (var formFile in files.Where(file => file.Length > 0))
|
||||||
|
{
|
||||||
|
await extensionService.UploadExtension(formFile);
|
||||||
|
extensionService.InstallExtension(formFile.FileName.TrimEnd(ExtensionManager.BTCPayExtensionSuffix,
|
||||||
|
StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction("ListExtensions",
|
||||||
|
new {StatusMessage = "Files uploaded, restart server to load extensions"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,6 +105,12 @@ namespace BTCPayServer.Controllers
|
|||||||
public async Task<IActionResult> Maintenance(MaintenanceViewModel vm, string command)
|
public async Task<IActionResult> Maintenance(MaintenanceViewModel vm, string command)
|
||||||
{
|
{
|
||||||
vm.CanUseSSH = _sshState.CanUseSSH;
|
vm.CanUseSSH = _sshState.CanUseSSH;
|
||||||
|
|
||||||
|
if (!vm.CanUseSSH)
|
||||||
|
{
|
||||||
|
TempData[WellKnownTempData.ErrorMessage] = "Maintenance feature requires access to SSH properly configured in BTCPayServer configuration.";
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return View(vm);
|
return View(vm);
|
||||||
if (command == "changedomain")
|
if (command == "changedomain")
|
||||||
@@ -182,6 +188,13 @@ namespace BTCPayServer.Controllers
|
|||||||
return error;
|
return error;
|
||||||
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon...";
|
TempData[WellKnownTempData.SuccessMessage] = $"The old docker images will be cleaned soon...";
|
||||||
}
|
}
|
||||||
|
else if (command == "restart")
|
||||||
|
{
|
||||||
|
var error = await RunSSH(vm, $"btcpay-restart.sh");
|
||||||
|
if (error != null)
|
||||||
|
return error;
|
||||||
|
TempData[WellKnownTempData.SuccessMessage] = $"BTCPay will restart momentarily.";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|||||||
18
BTCPayServer/Extensions/StringExtensions.cs
Normal file
18
BTCPayServer/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string TrimEnd(this string input, string suffixToRemove,
|
||||||
|
StringComparison comparisonType)
|
||||||
|
{
|
||||||
|
if (input != null && suffixToRemove != null
|
||||||
|
&& input.EndsWith(suffixToRemove, comparisonType))
|
||||||
|
{
|
||||||
|
return input.Substring(0, input.Length - suffixToRemove.Length);
|
||||||
|
}
|
||||||
|
else return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -139,6 +139,7 @@ namespace BTCPayServer.Hosting
|
|||||||
});
|
});
|
||||||
|
|
||||||
services.TryAddSingleton<AppService>();
|
services.TryAddSingleton<AppService>();
|
||||||
|
services.AddSingleton<ExtensionService>();
|
||||||
services.TryAddTransient<Safe>();
|
services.TryAddTransient<Safe>();
|
||||||
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
|
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ namespace BTCPayServer.Hosting
|
|||||||
services.AddProviderStorage();
|
services.AddProviderStorage();
|
||||||
services.AddSession();
|
services.AddSession();
|
||||||
services.AddSignalR();
|
services.AddSignalR();
|
||||||
services.AddMvc(o =>
|
var mvcBuilder= services.AddMvc(o =>
|
||||||
{
|
{
|
||||||
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
o.Filters.Add(new XFrameOptionsAttribute("DENY"));
|
||||||
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
|
o.Filters.Add(new XContentTypeOptionsAttribute("nosniff"));
|
||||||
@@ -91,7 +91,11 @@ namespace BTCPayServer.Hosting
|
|||||||
#if RAZOR_RUNTIME_COMPILE
|
#if RAZOR_RUNTIME_COMPILE
|
||||||
.AddRazorRuntimeCompilation()
|
.AddRazorRuntimeCompilation()
|
||||||
#endif
|
#endif
|
||||||
|
.AddExtensions(services, Configuration, LoggerFactory)
|
||||||
.AddControllersAsServices();
|
.AddControllersAsServices();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
services.TryAddScoped<ContentSecurityPolicies>();
|
services.TryAddScoped<ContentSecurityPolicies>();
|
||||||
services.Configure<IdentityOptions>(options =>
|
services.Configure<IdentityOptions>(options =>
|
||||||
{
|
{
|
||||||
@@ -174,6 +178,7 @@ namespace BTCPayServer.Hosting
|
|||||||
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
|
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, BTCPayServerOptions options)
|
||||||
{
|
{
|
||||||
Logs.Configure(loggerFactory);
|
Logs.Configure(loggerFactory);
|
||||||
|
app.UseExtensions();
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
|
|
||||||
namespace BTCPayServer.Models.NotificationViewModels
|
namespace BTCPayServer.Models.NotificationViewModels
|
||||||
{
|
{
|
||||||
@@ -11,12 +11,5 @@ namespace BTCPayServer.Models.NotificationViewModels
|
|||||||
public List<NotificationViewModel> Items { get; set; }
|
public List<NotificationViewModel> Items { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NotificationViewModel
|
|
||||||
{
|
|
||||||
public string Id { get; set; }
|
|
||||||
public DateTimeOffset Created { get; set; }
|
|
||||||
public string Body { get; set; }
|
|
||||||
public string ActionLink { get; set; }
|
|
||||||
public bool Seen { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,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_UPDATEURL": ""
|
"BTCPAY_UPDATEURL": "",
|
||||||
|
"BTCPAY_DOCKERDEPLOYMENT": "true"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://127.0.0.1:14142/"
|
"applicationUrl": "http://127.0.0.1:14142/"
|
||||||
},
|
},
|
||||||
@@ -51,7 +52,8 @@
|
|||||||
"BTCPAY_SSHPASSWORD": "opD3i2282D",
|
"BTCPAY_SSHPASSWORD": "opD3i2282D",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"applicationUrl": "https://localhost:14142/"
|
"applicationUrl": "https://localhost:14142/"
|
||||||
},
|
},
|
||||||
@@ -84,7 +86,8 @@
|
|||||||
"BTCPAY_SSHPASSWORD": "opD3i2282D",
|
"BTCPAY_SSHPASSWORD": "opD3i2282D",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"applicationUrl": "https://localhost:14142/"
|
"applicationUrl": "https://localhost:14142/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace BTCPayServer.Services.Altcoins.Ethereum
|
|||||||
serviceCollection.AddSingleton<IHostedService, EthereumService>(provider => provider.GetService<EthereumService>());
|
serviceCollection.AddSingleton<IHostedService, EthereumService>(provider => provider.GetService<EthereumService>());
|
||||||
serviceCollection.AddSingleton<EthereumLikePaymentMethodHandler>();
|
serviceCollection.AddSingleton<EthereumLikePaymentMethodHandler>();
|
||||||
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<EthereumLikePaymentMethodHandler>());
|
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<EthereumLikePaymentMethodHandler>());
|
||||||
serviceCollection.AddSingleton<IStoreNavExtension, EthereumStoreNavExtension>();
|
serviceCollection.AddSingleton<INavExtension, EthereumNavExtension>();
|
||||||
serviceCollection.AddTransient<NoRedirectHttpClientHandler>();
|
serviceCollection.AddTransient<NoRedirectHttpClientHandler>();
|
||||||
serviceCollection.AddSingleton<ISyncSummaryProvider, EthereumSyncSummaryProvider>();
|
serviceCollection.AddSingleton<ISyncSummaryProvider, EthereumSyncSummaryProvider>();
|
||||||
serviceCollection.AddHttpClient(EthereumInvoiceCreateHttpClient)
|
serviceCollection.AddHttpClient(EthereumInvoiceCreateHttpClient)
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ using BTCPayServer.Contracts;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Altcoins.Ethereum
|
namespace BTCPayServer.Services.Altcoins.Ethereum
|
||||||
{
|
{
|
||||||
public class EthereumStoreNavExtension: IStoreNavExtension
|
public class EthereumNavExtension: INavExtension
|
||||||
{
|
{
|
||||||
public string Partial { get; } = "Ethereum/StoreNavEthereumExtension";
|
public string Partial { get; } = "Ethereum/StoreNavEthereumExtension";
|
||||||
|
public string Location { get; } = "store";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace BTCPayServer.Services.Altcoins.Monero
|
|||||||
serviceCollection.AddHostedService<MoneroListener>();
|
serviceCollection.AddHostedService<MoneroListener>();
|
||||||
serviceCollection.AddSingleton<MoneroLikePaymentMethodHandler>();
|
serviceCollection.AddSingleton<MoneroLikePaymentMethodHandler>();
|
||||||
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<MoneroLikePaymentMethodHandler>());
|
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<MoneroLikePaymentMethodHandler>());
|
||||||
serviceCollection.AddSingleton<IStoreNavExtension, MoneroStoreNavExtension>();
|
serviceCollection.AddSingleton<INavExtension, MoneroNavExtension>();
|
||||||
serviceCollection.AddSingleton<ISyncSummaryProvider, MoneroSyncSummaryProvider>();
|
serviceCollection.AddSingleton<ISyncSummaryProvider, MoneroSyncSummaryProvider>();
|
||||||
|
|
||||||
return serviceCollection;
|
return serviceCollection;
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ using BTCPayServer.Contracts;
|
|||||||
|
|
||||||
namespace BTCPayServer.Services.Altcoins.Monero
|
namespace BTCPayServer.Services.Altcoins.Monero
|
||||||
{
|
{
|
||||||
public class MoneroStoreNavExtension : IStoreNavExtension
|
public class MoneroNavExtension : INavExtension
|
||||||
{
|
{
|
||||||
public string Partial { get; } = "Monero/StoreNavMoneroExtension";
|
public string Partial { get; } = "Monero/StoreNavMoneroExtension";
|
||||||
|
public string Location { get; } = "store";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using BTCPayServer.Configuration;
|
using BTCPayServer.Configuration;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
using BTCPayServer.Models.NotificationViewModels;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
using BTCPayServer.Contracts;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Notifications.Blobs
|
namespace BTCPayServer.Services.Notifications.Blobs
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using BTCPayServer.Contracts;
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
using BTCPayServer.Models.NotificationViewModels;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Notifications.Blobs
|
namespace BTCPayServer.Services.Notifications.Blobs
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using BTCPayServer.Configuration;
|
using BTCPayServer.Configuration;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
using BTCPayServer.Models.NotificationViewModels;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
using BTCPayServer.Models.NotificationViewModels;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Notifications
|
namespace BTCPayServer.Services.Notifications
|
||||||
{
|
{
|
||||||
public interface INotificationHandler
|
|
||||||
{
|
|
||||||
string NotificationType { get; }
|
|
||||||
Type NotificationBlobType { get; }
|
|
||||||
void FillViewModel(object notification, NotificationViewModel vm);
|
|
||||||
}
|
|
||||||
public abstract class NotificationHandler<TNotification> : INotificationHandler
|
public abstract class NotificationHandler<TNotification> : INotificationHandler
|
||||||
{
|
{
|
||||||
public abstract string NotificationType { get; }
|
public abstract string NotificationType { get; }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Components.NotificationsDropdown;
|
using BTCPayServer.Components.NotificationsDropdown;
|
||||||
|
using BTCPayServer.Contracts;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
using BTCPayServer.Models.NotificationViewModels;
|
using BTCPayServer.Models.NotificationViewModels;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|||||||
153
BTCPayServer/Views/Server/ListExtensions.cshtml
Normal file
153
BTCPayServer/Views/Server/ListExtensions.cshtml
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
@model BTCPayServer.Controllers.ServerController.ListExtensionsViewModel
|
||||||
|
@{
|
||||||
|
ViewData.SetActivePageAndTitle(ServerNavPages.Extensions);
|
||||||
|
var installed = Model.Installed.Select(extension => extension.Identifier);
|
||||||
|
var availableAndNotInstalled = Model.Available.Where(extension => !installed.Contains(extension.Identifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (Model.Commands.Any())
|
||||||
|
{
|
||||||
|
<div class="alert alert-info">
|
||||||
|
You need to restart BTCPay Server in order to update your active extensions.
|
||||||
|
@if (Model.CanShowRestart)
|
||||||
|
{
|
||||||
|
<form method="post" asp-action="Maintenance">
|
||||||
|
<button type="submit" name="command" value="restart" class="btn btn-outline-info alert-link" asp-action="Maintenance">Restart now</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<partial name="_StatusMessage"/>
|
||||||
|
|
||||||
|
|
||||||
|
@if (Model.Installed.Any())
|
||||||
|
{
|
||||||
|
<h4>Installed Extensions</h4>
|
||||||
|
<div class="card-columns">
|
||||||
|
|
||||||
|
@foreach (var extension in Model.Installed)
|
||||||
|
{
|
||||||
|
var matchedAvailable = Model.Available.SingleOrDefault(availableExtension => availableExtension.Identifier == extension.Identifier);
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">
|
||||||
|
@extension.Name <span class="badge badge-secondary">@extension.Version</span>
|
||||||
|
</h3>
|
||||||
|
<p class="card-text">@extension.Description</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
@if (matchedAvailable != null)
|
||||||
|
{
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Current version</span>
|
||||||
|
<span>@extension.Version</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Remote version</span>
|
||||||
|
<span>@matchedAvailable.Version</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="card-footer">
|
||||||
|
@if (Model.Commands.Any(tuple => tuple.extension.Equals(extension.Identifier, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
|
{
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="badge badge-info">pending action</div></div>
|
||||||
|
<form asp-action="CancelExtensionCommands" asp-route-extension="@extension.Identifier">
|
||||||
|
<button type="submit" class="btn btn-link pt-0">Cancel</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<form asp-action="UnInstallExtension" asp-route-extension="@extension.Identifier">
|
||||||
|
<button type="submit" class="btn btn-link">Uninstall</button>
|
||||||
|
</form>
|
||||||
|
@if (extension.Version < matchedAvailable.Version)
|
||||||
|
{
|
||||||
|
<form asp-action="InstallExtension" asp-route-extension="@extension.Identifier" asp-route-remote="@Model.Remote">
|
||||||
|
<button type="submit" class="btn btn-link">Update</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (availableAndNotInstalled.Any())
|
||||||
|
{
|
||||||
|
<h4>Available Extensions</h4>
|
||||||
|
|
||||||
|
<div class="card-columns">
|
||||||
|
@foreach (var extension in availableAndNotInstalled)
|
||||||
|
{
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">
|
||||||
|
@extension.Name <span class="badge badge-secondary">@extension.Version</span>
|
||||||
|
</h3>
|
||||||
|
<p class="card-text">@extension.Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer py-0 text-right">
|
||||||
|
@if (Model.Commands.Any(tuple => tuple.extension.Equals(extension.Identifier, StringComparison.InvariantCultureIgnoreCase)))
|
||||||
|
{
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="badge badge-info">pending action</div></div>
|
||||||
|
<form asp-action="CancelExtensionCommands" asp-route-extension="@extension.Identifier">
|
||||||
|
<button type="submit" class="btn btn-link pt-0">Cancel</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<form asp-action="InstallExtension" asp-route-extension="@extension.Identifier" asp-route-remote="@Model.Remote">
|
||||||
|
<button type="submit" class="btn btn-link">Install</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<button class="btn btn-link mt-4" type="button" data-toggle="collapse" data-target="#manual-upload">
|
||||||
|
Upload extension
|
||||||
|
</button>
|
||||||
|
<div class="collapse" id="manual-upload">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">Add extension manually</h3>
|
||||||
|
<div class="alert alert-warning">This is an extremely dangerous operation. Do not upload extensions from someone that you do not trust.</div>
|
||||||
|
<form method="post" enctype="multipart/form-data" asp-action="UploadExtension">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="file" name="files" accept=".btcpay" id="files"/>
|
||||||
|
<button class="btn btn-primary" type="submit">Upload</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
$('.custom-file-input').on('change',
|
||||||
|
function () {
|
||||||
|
var label = $(this).next('label');
|
||||||
|
if (document.getElementById("file").files.length > 0) {
|
||||||
|
var fileName = document.getElementById("file").files[0].name;
|
||||||
|
label.addClass("selected").html(fileName);
|
||||||
|
} else {
|
||||||
|
label.removeClass("selected").html("Choose file");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@@ -33,6 +33,16 @@
|
|||||||
<button name="command" type="submit" class="btn btn-primary" value="update" disabled="@(Model.CanUseSSH ? null : "disabled")">Update</button>
|
<button name="command" type="submit" class="btn btn-primary" value="update" disabled="@(Model.CanUseSSH ? null : "disabled")">Update</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h5>Restart</h5>
|
||||||
|
<p class="text-secondary mb-2">Restart BTCPay server and related services.</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-group">
|
||||||
|
<button name="command" type="submit" class="btn btn-primary" value="restart" disabled="@(Model.CanUseSSH ? null : "disabled")">Restart</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h5 class="mt-5">Clean</h5>
|
<h5 class="mt-5">Clean</h5>
|
||||||
<p class="text-secondary mb-2">Delete unused docker images present on your system.</p>
|
<p class="text-secondary mb-2">Delete unused docker images present on your system.</p>
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ namespace BTCPayServer.Views.Server
|
|||||||
{
|
{
|
||||||
public enum ServerNavPages
|
public enum ServerNavPages
|
||||||
{
|
{
|
||||||
Index, Users, Emails, Policies, Theme, Services, Maintenance, Logs, Files
|
Index, Users, Emails, Policies, Theme, Services, Maintenance, Logs, Files, Extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<div class="nav flex-column nav-pills mb-4">
|
@using BTCPayServer.Configuration
|
||||||
|
@inject BTCPayServerOptions BTCPayServerOptions
|
||||||
|
<div class="nav flex-column nav-pills mb-4">
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Users" class="nav-link @ViewData.IsActivePage(ServerNavPages.Users)" asp-action="ListUsers">Users</a>
|
<a asp-controller="Server" id="Server-@ServerNavPages.Users" class="nav-link @ViewData.IsActivePage(ServerNavPages.Users)" asp-action="ListUsers">Users</a>
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Emails" class="nav-link @ViewData.IsActivePage(ServerNavPages.Emails)" asp-action="Emails">Email server</a>
|
<a asp-controller="Server" id="Server-@ServerNavPages.Emails" class="nav-link @ViewData.IsActivePage(ServerNavPages.Emails)" asp-action="Emails">Email server</a>
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Policies" class="nav-link @ViewData.IsActivePage(ServerNavPages.Policies)" asp-action="Policies">Policies</a>
|
<a asp-controller="Server" id="Server-@ServerNavPages.Policies" class="nav-link @ViewData.IsActivePage(ServerNavPages.Policies)" asp-action="Policies">Policies</a>
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Services" class="nav-link @ViewData.IsActivePage(ServerNavPages.Services)" asp-action="Services">Services</a>
|
<a asp-controller="Server" id="Server-@ServerNavPages.Services" class="nav-link @ViewData.IsActivePage(ServerNavPages.Services)" asp-action="Services">Services</a>
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Theme" class="nav-link @ViewData.IsActivePage(ServerNavPages.Theme)" asp-action="Theme">Theme</a>
|
<a asp-controller="Server" id="Server-@ServerNavPages.Theme" class="nav-link @ViewData.IsActivePage(ServerNavPages.Theme)" asp-action="Theme">Theme</a>
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Maintenance" class="nav-link @ViewData.IsActivePage(ServerNavPages.Maintenance)" asp-action="Maintenance">Maintenance</a>
|
@if (BTCPayServerOptions.DockerDeployment)
|
||||||
|
{
|
||||||
|
<a asp-controller="Server" id="Server-@ServerNavPages.Maintenance" class="nav-link @ViewData.IsActivePage(ServerNavPages.Maintenance)" asp-action="Maintenance">Maintenance</a>
|
||||||
|
}
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Logs" class="nav-link @ViewData.IsActivePage(ServerNavPages.Logs)" asp-action="LogsView">Logs</a>
|
<a asp-controller="Server" id="Server-@ServerNavPages.Logs" class="nav-link @ViewData.IsActivePage(ServerNavPages.Logs)" asp-action="LogsView">Logs</a>
|
||||||
<a asp-controller="Server" id="Server-@ServerNavPages.Files" class="nav-link @ViewData.IsActivePage(ServerNavPages.Files)" asp-action="Files">Files</a>
|
<a asp-controller="Server" id="Server-@ServerNavPages.Files" class="nav-link @ViewData.IsActivePage(ServerNavPages.Files)" asp-action="Files">Files</a>
|
||||||
|
<a asp-controller="Server" id="Server-@ServerNavPages.Extensions" class="nav-link @ViewData.IsActivePage(ServerNavPages.Extensions)" asp-action="ListExtensions">Extensions (experimental)</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,6 +40,12 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav ml-auto">
|
||||||
|
@inject IEnumerable<BTCPayServer.Contracts.INavExtension> Extensions;
|
||||||
|
@foreach (var extension in Extensions.Where(extension => extension.Location == "header-nav"))
|
||||||
|
{
|
||||||
|
<partial name="@extension.Partial"/>
|
||||||
|
}
|
||||||
|
|
||||||
@if (SignInManager.IsSignedIn(User))
|
@if (SignInManager.IsSignedIn(User))
|
||||||
{
|
{
|
||||||
if (User.IsInRole(Roles.ServerAdmin))
|
if (User.IsInRole(Roles.ServerAdmin))
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<a id="@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="Stores" asp-action="StoreUsers" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Users</a>
|
<a id="@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="Stores" asp-action="StoreUsers" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Users</a>
|
||||||
<a id="@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Pay Button</a>
|
<a id="@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Pay Button</a>
|
||||||
<a id="@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Integrations</a>
|
<a id="@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@this.Context.GetRouteValue("storeId")">Integrations</a>
|
||||||
@inject IEnumerable<BTCPayServer.Contracts.IStoreNavExtension> Extensions;
|
@inject IEnumerable<BTCPayServer.Contracts.INavExtension> Extensions;
|
||||||
@foreach (var extension in Extensions)
|
@foreach (var extension in Extensions.Where(extension => extension.Location == "store"))
|
||||||
{
|
{
|
||||||
<partial name="@extension.Partial" />
|
<partial name="@extension.Partial" />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Data", "BTCPay
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Client", "BTCPayServer.Client\BTCPayServer.Client.csproj", "{21A13304-7168-49A0-86C2-0A1A9453E9C7}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Client", "BTCPayServer.Client\BTCPayServer.Client.csproj", "{21A13304-7168-49A0-86C2-0A1A9453E9C7}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Abstractions", "BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj", "{A0D50BB6-FE2C-4671-8693-F7582B66178F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Test", "BTCPayServer.Test\BTCPayServer.Test.csproj", "{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Altcoins-Debug|Any CPU = Altcoins-Debug|Any CPU
|
Altcoins-Debug|Any CPU = Altcoins-Debug|Any CPU
|
||||||
@@ -188,6 +192,54 @@ Global
|
|||||||
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x64.Build.0 = Release|Any CPU
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x86.ActiveCfg = Release|Any CPU
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x86.Build.0 = Release|Any CPU
|
{21A13304-7168-49A0-86C2-0A1A9453E9C7}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Release|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Release|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Release|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Altcoins-Release|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A0D50BB6-FE2C-4671-8693-F7582B66178F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Release|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Release|x64.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Release|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Altcoins-Release|x86.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user