init repo!

This commit is contained in:
Kukks 2019-11-15 14:26:43 +01:00
parent e8c687f92f
commit 563f170661
108 changed files with 42090 additions and 0 deletions

104
.circleci/config.yml Normal file
View File

@ -0,0 +1,104 @@
version: 2
jobs:
build:
machine:
docker_layer_caching: false
steps:
- checkout
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
publish_docker_linuxamd64:
machine:
docker_layer_caching: false
steps:
- checkout
- run:
command: |
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f Dockerfiles/Dockerfile.linuxamd64 .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
publish_docker_linuxarm32:
machine:
docker_layer_caching: false
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f Dockerfiles/Dockerfile.linuxarm32v7 .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
publish_docker_linuxarm64:
machine:
docker_layer_caching: false
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f Dockerfiles/Dockerfile.linuxarm64v8 .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
publish_docker_multiarch:
machine:
enabled: true
image: circleci/classic:201808-01
steps:
- run:
command: |
# Turn on Experimental features
sudo mkdir $HOME/.docker
sudo sh -c 'echo "{ \"experimental\": \"enabled\" }" >> $HOME/.docker/config.json'
#
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
#
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 --os linux --arch amd64
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
workflows:
version: 2
build_and_test:
jobs:
- test
publish:
jobs:
- publish_docker_linuxamd64:
filters:
# ignore any commit on any branch by default
branches:
ignore: /.*/
# only act on version tags
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_linuxarm32:
filters:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_linuxarm64:
filters:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_multiarch:
requires:
- publish_docker_linuxamd64
- publish_docker_linuxarm32
- publish_docker_linuxarm64
filters:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*/

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
bin/
obj/
/packages/
.idea

View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServerDockerConfigurator", "BTCPayServerDockerConfigurator\BTCPayServerDockerConfigurator.csproj", "{3A44286B-FFC4-4A97-AAB8-6DBFB9269A4A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3A44286B-FFC4-4A97-AAB8-6DBFB9269A4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A44286B-FFC4-4A97-AAB8-6DBFB9269A4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A44286B-FFC4-4A97-AAB8-6DBFB9269A4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A44286B-FFC4-4A97-AAB8-6DBFB9269A4A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.0" />
<PackageReference Include="Neon.SSH.NET" Version="0.7.1" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,88 @@
using System;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpGet("additional")]
public IActionResult AdditionalServices()
{
var model = GetConfiguratorSettings();
return View(new UpdateSettings<AdditionalServices, AdditionalDataStub>()
{
Json = model.ToString(),
Settings = model.AdditionalServices
});
}
[HttpPost("additional")]
public async Task<IActionResult> AdditionalServices(
UpdateSettings<AdditionalServices, AdditionalDataStub> updateSettings)
{
var configuratorSettings = string.IsNullOrEmpty(updateSettings.Json)
? new ConfiguratorSettings()
: JsonSerializer.Deserialize<ConfiguratorSettings>(updateSettings.Json);
if (ModelState.IsValid)
{
if (updateSettings.Settings.BTCTransmuterSettings.Enabled)
{
var error = await CheckHost(updateSettings.Settings.BTCTransmuterSettings.Host,
configuratorSettings);
if (!string.IsNullOrEmpty(error))
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." +
nameof(updateSettings.Settings.BTCTransmuterSettings) + "." +
nameof(updateSettings.Settings.BTCTransmuterSettings.Host),
error);
}
}
if (updateSettings.Settings.LibrePatronSettings.Enabled)
{
var error = await CheckHost(updateSettings.Settings.LibrePatronSettings.Host,
configuratorSettings);
if (!string.IsNullOrEmpty(error))
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." +
nameof(updateSettings.Settings.LibrePatronSettings) + "." +
nameof(updateSettings.Settings.LibrePatronSettings.Host),
error);
}
}
if (updateSettings.Settings.WooCommerceSettings.Enabled)
{
var error = await CheckHost(updateSettings.Settings.WooCommerceSettings.Host,
configuratorSettings);
if (!string.IsNullOrEmpty(error))
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." +
nameof(updateSettings.Settings.WooCommerceSettings) + "." +
nameof(updateSettings.Settings.WooCommerceSettings.Host),
error);
}
}
}
if (!ModelState.IsValid)
{
return View(updateSettings);
}
configuratorSettings.AdditionalServices = updateSettings.Settings;
SetConfiguratorSettings(configuratorSettings);
return RedirectToAction(nameof(AdvancedSettings));
}
}
}

View File

@ -0,0 +1,95 @@
using System;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpGet("advanced")]
public IActionResult AdvancedSettings()
{
var model = GetConfiguratorSettings();
return View(new UpdateSettings<AdvancedSettings, AdvancedSettingsAdditionalData>()
{
Json = model.ToString(),
Settings = model.AdvancedSettings,
Additional = new AdvancedSettingsAdditionalData()
{
ShowSettings = model.AdvancedSettings.AnythingSet()
}
});
}
[HttpPost("advanced")]
public async Task<IActionResult> AdvancedSettings(UpdateSettings<AdvancedSettings, AdvancedSettingsAdditionalData> updateSettings,
string command = null)
{
switch (command)
{
case "show":
updateSettings.Additional = new AdvancedSettingsAdditionalData()
{
ShowSettings = true
};
return View(updateSettings);
case "add-additional":
updateSettings.Additional = new AdvancedSettingsAdditionalData()
{
ShowSettings = true
};
updateSettings.Settings.AdditionalFragments.Add("");
return View(updateSettings);
case string commandx
when commandx.StartsWith("remove-additional", StringComparison.InvariantCultureIgnoreCase):
{
updateSettings.Additional = new AdvancedSettingsAdditionalData()
{
ShowSettings = true
};
var index = int.Parse(commandx.Substring(commandx.IndexOf(":", StringComparison.Ordinal) + 1));
updateSettings.Settings.AdditionalFragments.RemoveAt(index);
return View(updateSettings);
}
case "add-excluded":
updateSettings.Additional = new AdvancedSettingsAdditionalData()
{
ShowSettings = true
};
updateSettings.Settings.ExcludedFragments.Add("");
return View(updateSettings);
case string commandx
when commandx.StartsWith("remove-excluded", StringComparison.InvariantCultureIgnoreCase):
{
updateSettings.Additional = new AdvancedSettingsAdditionalData()
{
ShowSettings = true
};
var index = int.Parse(commandx.Substring(commandx.IndexOf(":", StringComparison.Ordinal) + 1));
updateSettings.Settings.ExcludedFragments.RemoveAt(index);
return View(updateSettings);
}
}
if (!ModelState.IsValid)
{
updateSettings.Additional = new AdvancedSettingsAdditionalData()
{
ShowSettings = true
};
return View(updateSettings);
}
var configuratorSettings = string.IsNullOrEmpty(updateSettings.Json)
? new ConfiguratorSettings()
: JsonSerializer.Deserialize<ConfiguratorSettings>(updateSettings.Json);
configuratorSettings.AdvancedSettings = updateSettings.Settings;
SetConfiguratorSettings(configuratorSettings);
return RedirectToAction(nameof(Summary));
}
}
}

View File

@ -0,0 +1,65 @@
using System;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpGet("chain")]
public IActionResult ChainSettings()
{
var model = GetConfiguratorSettings();
return View(new UpdateSettings<ChainSettings, AdditionalDataStub>()
{
Json = model.ToString(),
Settings = model.ChainSettings,
});
}
[HttpPost("chain")]
public async Task<IActionResult> ChainSettings(UpdateSettings<ChainSettings, AdditionalDataStub> updateSettings,
string command = null)
{
switch (command)
{
case "add-chain":
updateSettings.Settings.AltChains.Add("");
return View(updateSettings);
case string commandx
when commandx.StartsWith("remove-chain", StringComparison.InvariantCultureIgnoreCase):
{
var index = int.Parse(commandx.Substring(commandx.IndexOf(":", StringComparison.Ordinal) + 1));
updateSettings.Settings.AltChains.RemoveAt(index);
return View(updateSettings);
}
}
updateSettings.Settings.AltChains =
updateSettings.Settings.AltChains.Where(s => !string.IsNullOrEmpty(s)).ToList();
if (!updateSettings.Settings.Bitcoin && !updateSettings.Settings.AltChains.Any())
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.Bitcoin),
"You need to set up at least one chain");
}
if (!ModelState.IsValid)
{
return View(updateSettings);
}
var configuratorSettings = string.IsNullOrEmpty(updateSettings.Json)
? new ConfiguratorSettings()
: JsonSerializer.Deserialize<ConfiguratorSettings>(updateSettings.Json);
configuratorSettings.ChainSettings = updateSettings.Settings;
SetConfiguratorSettings(configuratorSettings);
return RedirectToAction(nameof(LightningSettings));
}
}
}

View File

@ -0,0 +1,80 @@
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpPost("deploy")]
public async Task<IActionResult> Deploy()
{
var model = GetConfiguratorSettings();
var bash = model.ConstructBashFile(null);
var oneliner = bash
.Replace("\r\n", "\n")
.Replace("\n", " &&\n");
if (model.DeploymentSettings.DeploymentType == DeploymentType.Manual)
{
return View(new UpdateSettings<ConfiguratorSettings, DeployAdditionalData>()
{
Additional = new DeployAdditionalData()
{
Bash = oneliner
},
Json = model.ToString(),
Settings = model
});
}
SSHSettings ssh = null;;
switch (model.DeploymentSettings.DeploymentType)
{
case DeploymentType.RemoteMachine when ModelState.IsValid:
{
ssh = new SSHSettings()
{
Password = model.DeploymentSettings.Password,
Server = model.DeploymentSettings.Host,
Username = model.DeploymentSettings.Username
};
break;
}
case DeploymentType.ThisMachine when ModelState.IsValid:
{
ssh = _options.Value.ParseSSHConfiguration();
break;
}
}
if (ssh == null)
{
throw new Exception("lolita bonita");
}
var connection = await ssh.ConnectAsync();
var result = await connection.RunBash(oneliner);
return View(new UpdateSettings<ConfiguratorSettings, DeployAdditionalData>()
{
Additional = new DeployAdditionalData()
{
Bash = oneliner,
Error = result.Error,
Output = result.Output,
ExitStatus = result.ExitStatus
},
Json = model.ToString(),
Settings = model
});
}
}
public class DeployAdditionalData: SSHClientExtensions.SSHCommandResult
{
public string Bash { get; set; }
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpGet("destination")]
[HttpGet("")]
public IActionResult DeploymentDestination()
{
var model = GetConfiguratorSettings();
return View(new UpdateSettings<DeploymentSettings, DeploymentAdditionalData>()
{
Json = JsonSerializer.Serialize(model),
Settings = model.DeploymentSettings,
Additional = ConstructDeploymentAdditionalData()
});
}
[HttpPost("destination")]
public async Task<IActionResult> DeploymentDestination(
UpdateSettings<DeploymentSettings, DeploymentAdditionalData> updateSettings)
{
updateSettings.Additional = ConstructDeploymentAdditionalData();
switch (updateSettings.Settings.DeploymentType)
{
case DeploymentType.RemoteMachine when ModelState.IsValid:
{
var ssh = new SSHSettings()
{
Password = updateSettings.Settings.Password,
Server = updateSettings.Settings.Host,
Username = updateSettings.Settings.Username
};
if (!await TestSSH(ssh))
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.Host),
"Could not connect with specified SSH details");
}
break;
}
case DeploymentType.ThisMachine when ModelState.IsValid:
{
var ssh = _options.Value.ParseSSHConfiguration();
if (!await TestSSH(ssh))
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.DeploymentType),
"Couldn't SSH into the host. That's bad fyi.'");
}
break;
}
}
if (!ModelState.IsValid)
{
return View(updateSettings);
}
var configuratorSettings = string.IsNullOrEmpty(updateSettings.Json)
? new ConfiguratorSettings()
: JsonSerializer.Deserialize<ConfiguratorSettings>(updateSettings.Json);
configuratorSettings.DeploymentSettings = updateSettings.Settings;
SetConfiguratorSettings(configuratorSettings);
return RedirectToAction(nameof(DomainSettings));
}
private async Task<bool> TestSSH(SSHSettings ssh)
{
try
{
var test = await ssh.ConnectAsync();
if (test.IsConnected)
{
await test.DisconnectAsync();
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
return false;
}
}
private DeploymentAdditionalData ConstructDeploymentAdditionalData()
{
var additionalData = new DeploymentAdditionalData();
if (!string.IsNullOrEmpty(_options.Value.SSHConnection))
{
additionalData.AvailableDeploymentTypes.Add(DeploymentType.ThisMachine);
}
additionalData.AvailableDeploymentTypes.Add(DeploymentType.RemoteMachine);
additionalData.AvailableDeploymentTypes.Add(DeploymentType.Manual);
return additionalData;
}
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpGet("domain")]
public IActionResult DomainSettings()
{
var model = GetConfiguratorSettings() ?? new ConfiguratorSettings();
return View(new UpdateSettings<DomainSettings, AdditionalDataStub>()
{
Json = JsonSerializer.Serialize(model),
Settings = model.DomainSettings,
});
}
[HttpPost("domain")]
public async Task<IActionResult> DomainSettings(
UpdateSettings<DomainSettings, AdditionalDataStub> updateSettings,
string command = null)
{
switch (command)
{
case "add-domain":
if (!string.IsNullOrEmpty(updateSettings.Settings.Domain))
{
updateSettings.Settings.AdditionalDomains.Add("");
}
return View(updateSettings);
case string commandx
when commandx.StartsWith("remove-domain", StringComparison.InvariantCultureIgnoreCase):
{
var index = int.Parse(commandx.Substring(commandx.IndexOf(":", StringComparison.Ordinal) + 1));
updateSettings.Settings.AdditionalDomains.RemoveAt(index);
return View(updateSettings);
}
}
if (updateSettings.Settings.AdditionalDomains.Any() &&
string.IsNullOrEmpty(updateSettings.Settings.Domain))
{
ModelState.AddModelError(nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.Domain),
"You cannot set additional domains when there is no primary domain set!");
}
var configuratorSettings = string.IsNullOrEmpty(updateSettings.Json)
? new ConfiguratorSettings()
: JsonSerializer.Deserialize<ConfiguratorSettings>(updateSettings.Json);
var error = await CheckHost(updateSettings.Settings.Domain, configuratorSettings);
if (!string.IsNullOrEmpty(error))
{
ModelState.AddModelError(nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.Domain),
error);
}
error = null;
for (var index = 0; index < updateSettings.Settings.AdditionalDomains.Count; index++)
{
var additionalDomain = updateSettings.Settings.AdditionalDomains[index];
error = await CheckHost(additionalDomain, configuratorSettings);
if (!string.IsNullOrEmpty(error))
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.Domain) + $"[{index}]",
error);
}
}
if (!ModelState.IsValid)
{
return View(updateSettings);
}
configuratorSettings.DomainSettings = updateSettings.Settings;
SetConfiguratorSettings(configuratorSettings);
return RedirectToAction(nameof(ChainSettings));
}
}
}

View File

@ -0,0 +1,54 @@
using System.Text.Json;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpGet("lightning")]
public IActionResult LightningSettings()
{
var model = GetConfiguratorSettings();
return View(new UpdateSettings<LightningSettings, AdditionalDataStub>()
{
Json = model.ToString(),
Settings = model.LightningSettings,
});
}
[HttpPost("lightning")]
public async Task<IActionResult> LightningSettings(
UpdateSettings<LightningSettings, AdditionalDataStub> updateSettings)
{
var configuratorSettings = string.IsNullOrEmpty(updateSettings.Json)
? new ConfiguratorSettings()
: JsonSerializer.Deserialize<ConfiguratorSettings>(updateSettings.Json);
if (configuratorSettings.ChainSettings.PruneMode != PruneMode.NoPruning &&
updateSettings.Settings.Implementation == "eclair")
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.Implementation),
"You cannot use Eclair when you have pruning enabled.");
}else if (configuratorSettings.ChainSettings.PruneMode == PruneMode.ExtraExtraSmall &&
!string.IsNullOrEmpty(updateSettings.Settings.Implementation))
{
ModelState.AddModelError(
nameof(updateSettings.Settings) + "." + nameof(updateSettings.Settings.Implementation),
"You cannot use lightning when you have your level of pruning set.");
}
if (!ModelState.IsValid)
{
return View(updateSettings);
}
configuratorSettings.LightningSettings = updateSettings.Settings;
SetConfiguratorSettings(configuratorSettings);
return RedirectToAction(nameof(AdditionalServices));
}
}
}

View File

@ -0,0 +1,19 @@
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServerDockerConfigurator.Controllers
{
public partial class ConfiguratorController
{
[HttpGet("summary")]
public IActionResult Summary()
{
var model = GetConfiguratorSettings();
return View(new UpdateSettings<ConfiguratorSettings, AdditionalDataStub>()
{
Settings = model
});
}
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using Renci.SshNet;
using Options = BTCPayServerDockerConfigurator.Models.Options;
namespace BTCPayServerDockerConfigurator.Controllers
{
[Route("")]
public partial class ConfiguratorController : Controller
{
private readonly IOptions<Options> _options;
public ConfiguratorController(IOptions<Options> options)
{
_options = options;
}
private ConfiguratorSettings GetConfiguratorSettings()
{
var rawResult = TempData.Peek(nameof(ConfiguratorSettings))?.ToString();
return string.IsNullOrEmpty(rawResult)
? new ConfiguratorSettings()
: JsonSerializer.Deserialize<ConfiguratorSettings>(rawResult);
}
private void SetConfiguratorSettings(ConfiguratorSettings settings)
{
if (TempData.ContainsKey(nameof(ConfiguratorSettings)))
TempData.Remove(nameof(ConfiguratorSettings));
TempData.Add(nameof(ConfiguratorSettings), JsonSerializer.Serialize(settings));
}
private async Task<string> CheckHost(string host, ConfiguratorSettings configuratorSettings)
{
string hostToCheckAgainst = null;
switch (configuratorSettings.DeploymentSettings.DeploymentType)
{
case DeploymentType.Manual:
break;
case DeploymentType.ThisMachine:
hostToCheckAgainst = new WebClient().DownloadString("http://icanhazip.com");
break;
case DeploymentType.RemoteMachine:
hostToCheckAgainst = configuratorSettings.DeploymentSettings.Host;
break;
default:
throw new ArgumentOutOfRangeException();
}
var errorMessage = string.IsNullOrEmpty(hostToCheckAgainst)
? "The domain was invalid."
: $"The domain was either invalid or the DNS record did not seem to point to {hostToCheckAgainst}";
if (!string.IsNullOrEmpty(host))
{
if (!await CheckHostDNSIsCorrect(host, hostToCheckAgainst))
{
return errorMessage;
}
}
return null;
}
private async Task<bool> CheckHostDNSIsCorrect(string host, string hostToCheckAgainst = null)
{
var basicCheck = Uri.CheckHostName(host);
if (basicCheck != UriHostNameType.Dns && basicCheck != UriHostNameType.Unknown)
{
return false;
}
if (hostToCheckAgainst == null || host.EndsWith(".local", StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
var vpsType = Uri.CheckHostName(hostToCheckAgainst);
if (vpsType == UriHostNameType.Dns)
{
var vpsIps = Dns.GetHostAddresses(hostToCheckAgainst);
return Dns.GetHostAddresses(host).ToList().Any(address =>
vpsIps.Any(ipAddress =>
ipAddress.Equals(address)));
}
return Dns.GetHostAddresses(host).ToList().Any(address =>
address.ToString().Equals(hostToCheckAgainst, StringComparison.InvariantCultureIgnoreCase));
}
}
}

View File

@ -0,0 +1,7 @@
namespace BTCPayServerDockerConfigurator.Models
{
public class AdditionalDataStub
{
}
}

View File

@ -0,0 +1,10 @@
namespace BTCPayServerDockerConfigurator.Models
{
public class AdditionalServices
{
public LibrePatronSettings LibrePatronSettings { get; set; }
public BTCTransmuterSettings BTCTransmuterSettings { get; set; }
public WooCommerceSettings WooCommerceSettings { get; set; }
public TorRelaySettings TorRelaySettings { get; set; }
}
}

View File

@ -0,0 +1,24 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace BTCPayServerDockerConfigurator.Models
{
public class AdvancedSettings
{
[Display(Name = "Custom BTCPay Docker image")]
public string CustomBTCPayImage { get; set; } = "";
[Display(Name = "Custom btcpayserver-docker repository")]
public string BTCPayDockerRepository { get; set; }
[Display(Name = "Custom btcpayserver-docker branch")]
public string BTCPayDockerBranch { get; set; }
public List<string> AdditionalFragments { get; set; } = new List<string>();
public List<string> ExcludedFragments { get; set; } = new List<string>();
public bool AnythingSet()
{
return !string.IsNullOrEmpty(CustomBTCPayImage) || !string.IsNullOrEmpty(BTCPayDockerRepository) ||
!string.IsNullOrEmpty(BTCPayDockerBranch) || AdditionalFragments.Any() || ExcludedFragments.Any();
}
}
}

View File

@ -0,0 +1,7 @@
namespace BTCPayServerDockerConfigurator.Models
{
public class AdvancedSettingsAdditionalData
{
public bool ShowSettings { get; set; } = false;
}
}

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
using BTCPayServerDockerConfigurator.Validation;
namespace BTCPayServerDockerConfigurator.Models
{
public class BTCTransmuterSettings
{
[RequiredIf(nameof(Enabled), "true", "A host must be set when enabled")]
[Display(Name = "Hostname of your BTC Transmuter website")]
public string Host { get; set; }
public bool Enabled { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace BTCPayServerDockerConfigurator.Models
{
public class ChainSettings
{
public bool Bitcoin { get; set; } = true;
[Display(Name = "")] public List<string> AltChains { get; set; } = new List<string>();
[Display(Name = "Pruning mode")] public PruneMode PruneMode { get; set; } = PruneMode.Small;
public NetworkType Network { get; set; } = NetworkType.Mainnet;
}
}

View File

@ -0,0 +1,130 @@
using System.Linq;
using System.Text;
using System.Text.Json;
namespace BTCPayServerDockerConfigurator.Models
{
public class ConfiguratorSettings
{
public DeploymentSettings DeploymentSettings { get; set; } = new DeploymentSettings();
public DomainSettings DomainSettings { get; set; } = new DomainSettings();
public LightningSettings LightningSettings { get; set; } = new LightningSettings();
public AdvancedSettings AdvancedSettings { get; set; } = new AdvancedSettings();
public ChainSettings ChainSettings { get; set; } = new ChainSettings();
public AdditionalServices AdditionalServices { get; set; } = new AdditionalServices();
public override string ToString()
{
return JsonSerializer.Serialize(this);
}
public string ConstructBashFile(string downloadLink)
{
var result = new StringBuilder();
result.AppendLine(GetAbstractedPackageManager());
result.AppendLine(InstallPackage("git wget"));
DownloadFile(downloadLink);
var gitRepo = string.IsNullOrEmpty(AdvancedSettings.BTCPayDockerRepository)
? "https://github.com/btcpayserver/btcpayserver-docker" : AdvancedSettings.BTCPayDockerRepository;
var gitBranch = string.IsNullOrEmpty(AdvancedSettings.BTCPayDockerBranch)
? "master" : AdvancedSettings.BTCPayDockerBranch;
result.AppendLine($"git clone -b {gitBranch} {gitRepo}");
result.AppendLine($"cd btcpayserver-docker");
if (!string.IsNullOrEmpty(AdvancedSettings.CustomBTCPayImage))
{
result.AppendLine($"export BTCPAY_IMAGE=\"{AdvancedSettings.CustomBTCPayImage}\"");
}
if (gitBranch != "master" || gitRepo != "https://github.com/btcpayserver/btcpayserver-docker")
{
result.AppendLine($"export BTCPAYGEN_DOCKER_IMAGE=\"btcpayserver/docker-compose-generator:local\"");
}
var additionalFragments = AdvancedSettings.AdditionalFragments;
var excludedFragments = AdvancedSettings.ExcludedFragments;
result.AppendLine($"export BTCPAY_IMAGE=\"{AdvancedSettings.CustomBTCPayImage}\"");
var domain = string.IsNullOrEmpty(DomainSettings.Domain) ? "btcpay.local" : DomainSettings.Domain;
result.AppendLine($"export BTCPAY_HOST=\"{domain}\"");
if (DomainSettings.AdditionalDomains.Any())
{
result.AppendLine($"export BTCPAY_ADDITIONAL_HOSTS=\"{string.Join(',', DomainSettings.AdditionalDomains)}\"");
}
result.AppendLine($"export NBITCOIN_NETWORK=\"{ChainSettings.Network.ToString().ToLower()}\"");
result.AppendLine($"export LIGHTNING_ALIAS=\"{LightningSettings.Alias}\"");
result.AppendLine($"export BTCPAYGEN_LIGHTNING=\"{LightningSettings.Implementation}\"");
var index = 1;
if (ChainSettings.Bitcoin)
{
result.AppendLine($"export BTCPAYGEN_CRYPTO1=\"btc\"");
index++;
}
foreach (var chainSettingsAltChain in ChainSettings.AltChains)
{
result.AppendLine($"export BTCPAYGEN_CRYPTO{index}=\"{chainSettingsAltChain}\"");
index++;
}
result.AppendLine($"export BTCPAY_ENABLE_SSH=true");
if (AdditionalServices.LibrePatronSettings.Enabled)
{
additionalFragments.Add("opt-add-librepatron");
result.AppendLine($"export LIBREPATRON_HOST=\"{AdditionalServices.LibrePatronSettings.Host}\"");
}
if (AdditionalServices.TorRelaySettings.Enabled)
{
additionalFragments.Add("opt-add-tor-relay");
result.AppendLine($"export TOR_RELAY_NICKNAME=\"{AdditionalServices.TorRelaySettings.Nickname}\"");
result.AppendLine($"export TOR_RELAY_EMAIL=\"{AdditionalServices.TorRelaySettings.Email}\"");
}
if (AdditionalServices.WooCommerceSettings.Enabled)
{
additionalFragments.Add("opt-add-woocommerce");
result.AppendLine($"export WOOCOMMERCE_HOST=\"{AdditionalServices.WooCommerceSettings.Host}\"");
}
if (AdditionalServices.BTCTransmuterSettings.Enabled)
{
additionalFragments.Add("opt-add-btctransmuter");
result.AppendLine($"export BTCTRANSMUTER_HOST=\"{AdditionalServices.BTCTransmuterSettings.Host}\"");
}
if (additionalFragments.Any())
{
result.AppendLine($"export BTCPAYGEN_ADDITIONAL_FRAGMENTS=\"{string.Join(';', additionalFragments)}\"");
}
if (excludedFragments.Any())
{
result.AppendLine($"export BTCPAYGEN_EXCLUDE_FRAGMENTS=\"{string.Join(';', excludedFragments)}\"");
}
result.AppendLine(". ./btcpay.setup -i");
return result.ToString();
}
private string GetAbstractedPackageManager()
{
return "#!/bin/bash\ndeclare -A osInfo;\nosInfo[/etc/debian_version]=\"apt-get install -y\"\nosInfo[/etc/alpine-release]=\"apk --update add\"\nosInfo[/etc/centos-release]=\"yum install -y\"\nosInfo[/etc/fedora-release]=\"dnf install -y\"\n\nfor f in ${!osInfo[@]}\ndo\n if [[ -f $f ]];then\n package_manager=${osInfo[$f]}\n fi\ndone";
}
private string InstallPackage(string package)
{
return "${package_manager} "+ package;
}
private string DownloadFile(string url)
{
if (string.IsNullOrEmpty(url))
{
return string.Empty;
}
return $"wget {url} 2>/dev/null || curl -O {url}";
}
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace BTCPayServerDockerConfigurator.Models
{
public class DeploymentAdditionalData
{
public List<DeploymentType> AvailableDeploymentTypes { get; set; } = new List<DeploymentType>();
}
}

View File

@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using BTCPayServerDockerConfigurator.Validation;
namespace BTCPayServerDockerConfigurator.Models
{
public class DeploymentSettings
{
[Required] public DeploymentType DeploymentType { get; set; }
[RequiredIf(nameof(DeploymentType), nameof(DeploymentType.RemoteMachine),
"Please enter the host/ip of the remote server")]
public string Host { get; set; }
[RequiredIf(nameof(DeploymentType), nameof(DeploymentType.RemoteMachine),
"Please enter the username of the remote server")]
public string Username { get; set; }
[RequiredIf(nameof(DeploymentType), nameof(DeploymentType.RemoteMachine),
"Please enter the password of the remote server")]
public string Password { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace BTCPayServerDockerConfigurator.Models
{
public enum DeploymentType
{
ThisMachine,
RemoteMachine,
Manual
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace BTCPayServerDockerConfigurator.Models
{
public class DomainSettings
{
public string Domain { get; set; }
public List<string> AdditionalDomains { get; set; } = new List<string>();
}
}

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace BTCPayServerDockerConfigurator.Models
{
public static class EnumTextHelper
{
public static Dictionary<PruneMode, string> PruneMode = new Dictionary<PruneMode, string>()
{
{Models.PruneMode.NoPruning, "No pruning"},
{Models.PruneMode.Minimal, "100GB ~1 year worth of blocks"},
{Models.PruneMode.Small, "50GB ~6 months worth of blocks"},
{Models.PruneMode.ExtraSmall, "25GB ~3 months worth of blocks"},
{Models.PruneMode.ExtraExtraSmall, "5GB ~2 weeks worth of blocks"},
};
}
}

View File

@ -0,0 +1,12 @@
using BTCPayServerDockerConfigurator.Controllers;
using Microsoft.Extensions.Configuration;
namespace BTCPayServerDockerConfigurator.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
using BTCPayServerDockerConfigurator.Validation;
namespace BTCPayServerDockerConfigurator.Models
{
public class LibrePatronSettings
{
[RequiredIf(nameof(Enabled), "true", "A host must be set when enabled")]
[Display(Name = "Hostname of your Libre Patron website")]
public string Host { get; set; }
public bool Enabled { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace BTCPayServerDockerConfigurator.Models
{
public class LightningSettings
{
public string Implementation { get; set; } = "";
public string Alias { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace BTCPayServerDockerConfigurator.Models
{
public enum NetworkType
{
Mainnet,
Testnet,
Regtest
}
}

View File

@ -0,0 +1,48 @@
namespace BTCPayServerDockerConfigurator.Models
{
public class Options
{
public string SSHConnection { get; set; } = null;
public string SSHPassword { get; set; } = "";
public string SSHKeyFile { get; set; }= "";
public string SSHAuthorizedKeys { get; set; }= "";
public string SSHKeyFilePassword { get; set; }= "";
public SSHSettings ParseSSHConfiguration()
{
var settings = new SSHSettings()
{
Password = SSHPassword,
KeyFile = SSHKeyFile,
AuthorizedKeysFile = SSHAuthorizedKeys,
KeyFilePassword = SSHKeyFilePassword,
Server = SSHConnection
};
if (settings.Server != null)
{
var parts = settings.Server.Split(':');
if (parts.Length == 2 && int.TryParse(parts[1], out int port))
{
settings.Port = port;
settings.Server = parts[0];
}
else
{
settings.Port = 22;
}
parts = settings.Server.Split('@');
if (parts.Length == 2)
{
settings.Username = parts[0];
settings.Server = parts[1];
}
else
{
settings.Username = "root";
}
}
return settings;
}
}
}

View File

@ -0,0 +1,12 @@
namespace BTCPayServerDockerConfigurator.Models
{
public enum PruneMode
{
NoPruning = 0,
Minimal = 100,
Small = 50,
ExtraSmall = 25,
ExtraExtraSmall = 5,
}
}

View File

@ -0,0 +1,127 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Renci.SshNet;
namespace BTCPayServerDockerConfigurator.Models
{
public static class SSHClientExtensions
{
public static async Task<SshClient> ConnectAsync(this SSHSettings sshSettings, CancellationToken cancellationToken = default)
{
if (sshSettings == null)
throw new ArgumentNullException(nameof(sshSettings));
TaskCompletionSource<SshClient> tcs = new TaskCompletionSource<SshClient>(TaskCreationOptions.RunContinuationsAsynchronously);
new Thread(() =>
{
SshClient sshClient = null;
try
{
sshClient = new SshClient(sshSettings.CreateConnectionInfo());
sshClient.HostKeyReceived += (object sender, Renci.SshNet.Common.HostKeyEventArgs e) =>
{
e.CanTrust = true;
};
sshClient.Connect();
tcs.TrySetResult(sshClient);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
try
{
sshClient?.Dispose();
}
catch { }
}
})
{ IsBackground = true }.Start();
using (cancellationToken.Register(() => { tcs.TrySetCanceled(); }))
{
return await tcs.Task;
}
}
public static string EscapeSingleQuotes(this string command)
{
return command.Replace("'", "'\"'\"'", StringComparison.OrdinalIgnoreCase);
}
public static Task<SSHCommandResult> RunBash(this SshClient sshClient, string command, TimeSpan? timeout = null)
{
if (sshClient == null)
throw new ArgumentNullException(nameof(sshClient));
if (command == null)
throw new ArgumentNullException(nameof(command));
command = $"bash -c '{command.EscapeSingleQuotes()}'";
var sshCommand = sshClient.CreateCommand(command);
if (timeout is TimeSpan v)
sshCommand.CommandTimeout = v;
var tcs = new TaskCompletionSource<SSHCommandResult>(TaskCreationOptions.RunContinuationsAsynchronously);
new Thread(() =>
{
sshCommand.BeginExecute(ar =>
{
try
{
sshCommand.EndExecute(ar);
tcs.TrySetResult(CreateSSHCommandResult(sshCommand));
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
finally
{
sshCommand.Dispose();
}
});
})
{ IsBackground = true }.Start();
return tcs.Task;
}
private static SSHCommandResult CreateSSHCommandResult(SshCommand sshCommand)
{
return new SSHCommandResult()
{
Output = sshCommand.Result,
Error = sshCommand.Error,
ExitStatus = sshCommand.ExitStatus
};
}
public static async Task DisconnectAsync(this SshClient sshClient, CancellationToken cancellationToken = default)
{
if (sshClient == null)
throw new ArgumentNullException(nameof(sshClient));
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
new Thread(() =>
{
try
{
sshClient.Disconnect();
tcs.TrySetResult(true);
}
catch
{
tcs.TrySetResult(true); // We don't care about exception
}
})
{ IsBackground = true }.Start();
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
await tcs.Task;
}
}
public class SSHCommandResult
{
public int ExitStatus { get; internal set; }
public string Output { get; internal set; }
public string Error { get; internal set; }
}
}
}

View File

@ -0,0 +1,27 @@
using Renci.SshNet;
namespace BTCPayServerDockerConfigurator.Models
{
public class SSHSettings
{
public string Server { get; set; }
public int Port { get; set; } = 22;
public string KeyFile { get; set; }
public string KeyFilePassword { get; set; }
public string AuthorizedKeysFile { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public ConnectionInfo CreateConnectionInfo()
{
if (!string.IsNullOrEmpty(KeyFile))
{
return new ConnectionInfo(Server, Port, Username, new[] { new PrivateKeyAuthenticationMethod(Username, new PrivateKeyFile(KeyFile, KeyFilePassword)) });
}
else
{
return new ConnectionInfo(Server, Port, Username, new[] { new PasswordAuthenticationMethod(Username, Password) });
}
}
}
}

View File

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using BTCPayServerDockerConfigurator.Validation;
namespace BTCPayServerDockerConfigurator.Models
{
public class TorRelaySettings
{
[RequiredIf(nameof(Enabled), "true", "Required")]
[Display(Name = "Relay nickname")]
public string Nickname { get; set; }
[RequiredIf(nameof(Enabled), "true", "Required")]
[EmailAddress]
[Display(Name = "Tor contact email")]
public string Email { get; set; }
public bool Enabled { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace BTCPayServerDockerConfigurator.Models
{
public class UpdateSettings<T,TAdditionalData>
{
public T Settings { get; set; }
public TAdditionalData Additional { get; set; }
public string Json { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
using BTCPayServerDockerConfigurator.Validation;
namespace BTCPayServerDockerConfigurator.Models
{
public class WooCommerceSettings
{
[RequiredIf(nameof(Enabled), "true", "A host must be set when enabled")]
[Display(Name = "Hostname of your WooCommerce website")]
public string Host { get; set; }
public bool Enabled { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace BTCPayServerDockerConfigurator
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}

View File

@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:30600",
"sslPort": 44345
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"BTCPayServerDockerConfigurator": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServerDockerConfigurator.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BTCPayServerDockerConfigurator
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<Options>(Configuration);
services.AddControllersWithViews().AddRazorRuntimeCompilation();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace BTCPayServerDockerConfigurator.Validation
{
//from https://stackoverflow.com/questions/52321148/conditional-validation-in-mvc-net-core-requiredif
public class RequiredIfAttribute : ValidationAttribute
{
private String PropertyName { get; set; }
private String ErrorMessage { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue, String errormessage)
{
this.PropertyName = propertyName;
this.DesiredValue = desiredvalue;
this.ErrorMessage = errormessage;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString() && value == null)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
}

View File

@ -0,0 +1,27 @@
@model BTCTransmuterSettings
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<label class="d-flex justify-content-between px-2 pb-0 mb-0 styled-checkbox-label">
<h3 class="card-title text-center pt-2 pb-0 mb-0">BTC Transmuter</h3>
<input class="styled-checkbox" type="checkbox" asp-for="Enabled"/>
</label>
<div class="card-body">
<p class="card-text">
BTC Transmuter is an opt-in plugin for BTCPay that allows you to automate workflows around your bitcoin. You'll be able to automate trades on exchanges, send customized emails, set up hot wallets for transaction automization and even handle lighrning channels.
</p>
<div id="transmuter-body" style="display: none">
<hr>
<div class="form-group">
<label asp-for="Host"></label>
<input class="form-control" asp-for="Host">
<span asp-validation-for="Host" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,28 @@
@model LibrePatronSettings
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<label class="d-flex justify-content-between px-2 pb-0 mb-0 styled-checkbox-label">
<h3 class="card-title text-center pt-2">LibrePatron</h3>
<input class="styled-checkbox" type="checkbox" asp-for="Enabled"/>
</label>
<div class="card-body">
<p class="card-text">
LibrePatron is A self-hosted Patreon alternative backed by BTCPay created by Jeff Vandrew Jr.
</p>
<div id="librepatron-body" style="display: none">
<hr>
<div class="form-group">
<label asp-for="Host"></label>
<input class="form-control" asp-for="Host">
<span asp-validation-for="Host" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,32 @@
@model TorRelaySettings
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<label class="d-flex justify-content-between px-2 pb-0 mb-0 styled-checkbox-label">
<h3 class="card-title text-center pt-2">Tor Relay </h3>
<input class="styled-checkbox" type="checkbox" asp-for="Enabled"/>
</label>
<div class="card-body">
<p class="card-text">
Tor relays are also referred to as "routers" or "nodes." They receive traffic on the Tor network and pass it along. This sets up a non-exit tor relay alongside your BTCPay. <br/><a href="https://www.eff.org/torchallenge/faq.html" target="_parent">Please read the legal implications of running a tor relay</a> and <a href="https://trac.torproject.org/projects/tor/wiki/TorRelayGuide#RelayRequirements" target="_blank">what resources are used to operate the relay.</a>
</p>
<div id="tor-relay-body" style="display: none">
<hr>
<div class="form-group">
<label asp-for="Email"></label>
<input class="form-control" asp-for="Email">
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Nickname"></label>
<input class="form-control" asp-for="Nickname">
<span asp-validation-for="Nickname" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,27 @@
@model WooCommerceSettings
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<label class="d-flex justify-content-between px-2 pb-0 mb-0 styled-checkbox-label">
<h3 class="card-title text-center pt-2">WooCommerce</h3>
<input class="styled-checkbox" type="checkbox" asp-for="Enabled"/>
</label>
<div class="card-body">
<p class="card-text">
WooCommerce is an open-source e-commerce plugin for WordPress. This will install Wordpress + Woocommerce plugin + BTCPay plugin.
</p>
<div id="woocommerce-body" style="display: none">
<hr>
<div class="form-group">
<label asp-for="Host"></label>
<input class="form-control" asp-for="Host">
<span asp-validation-for="Host" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,57 @@
@{
ViewData["Title"] = "How about some extra icing on top?";
}
@model UpdateSettings<AdditionalServices, AdditionalDataStub>;
<form class="container" method="post" asp-action="AdditionalServices">
@* <div asp-validation-summary="All" class="text-danger"></div> *@
<input type="hidden" asp-for="Json"/>
<div class="row mb-md-4">
<h2 class=" text-center w-100">@ViewData["Title"]</h2>
</div>
<partial for="Settings.TorRelaySettings" name="Additional/TorRelaySettings"/>
<partial for="Settings.BTCTransmuterSettings" name="Additional/BTCTransmuterSettings"/>
<partial for="Settings.LibrePatronSettings" name="Additional/LibrePatronSettings"/>
<partial for="Settings.WooCommerceSettings" name="Additional/WooCommerceSettings"/>
<div class="row w-100">
<button type="submit" class="m-auto btn btn-primary btn-lg">Continue</button>
</div>
</form>
@section Scripts
{
<script>
$(document).ready(function(){
var torBody = $("#tor-relay-body");
var torInput = $("#Settings_TorRelaySettings_Enabled");
toggle(torInput, torBody);
torInput.on("change input", function(){ toggle(torInput, torBody)});
var transmuterBody = $("#transmuter-body");
var transmuterInput = $("#Settings_BTCTransmuterSettings_Enabled");
toggle(transmuterInput, transmuterBody);
transmuterInput.on("change input", function(){ toggle(transmuterInput, transmuterBody)});
var librepatronBody = $("#librepatron-body");
var librepatronInput = $("#Settings_LibrePatronSettings_Enabled");
toggle(librepatronInput, librepatronBody);
librepatronInput.on("change input", function(){ toggle(librepatronInput, librepatronBody)});
var woocommerceBody = $("#woocommerce-body");
var woocommerceInput = $("#Settings_WooCommerceSettings_Enabled");
toggle(woocommerceInput, woocommerceBody);
woocommerceInput.on("change input", function(){ toggle(woocommerceInput, woocommerceBody)});
});
function toggle(input, el){
if(input.prop('checked')){
el.show();
}else{
el.hide();
}
}
</script>
}

View File

@ -0,0 +1,109 @@
@{
ViewData["Title"] = "Geeky settings?";
}
@model UpdateSettings<AdvancedSettings, AdvancedSettingsAdditionalData>;
<form class="container" method="post" asp-action="AdvancedSettings">
@* <div asp-validation-summary="All" class="text-danger"></div> *@
<input type="hidden" asp-for="Json"/>
<div class="row mb-md-4">
<h2 class=" text-center w-100">@ViewData["Title"]</h2>
</div>
@if (!Model.Additional.ShowSettings)
{
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 card shadow-sm">
<div class="card-body w-100 text-center">
<button type="submit" class="btn btn-primary ">
Continue
</button>
<br/>
<br/>
<button type="submit" name="command" value="show" class="btn btn-link h2 w-100 text-center">Show me the advanced settings.</button>
</div>
</div>
</div>
}
<div class="row mb-4 @(Model.Additional.ShowSettings ? string.Empty : "invisible")">
<div class="col-md-6 col-sm-12 offset-md-3 card shadow-sm pt-4">
<h3 class="card-title">Additional fragments</h3>
<ul class="list-group list-group-flush" style="list-style-type: none">
@for (var index = 0; index < Model.Settings.AdditionalFragments.Count; index++)
{
<li class="list-group-item justify-content-between align-items-center">
<div class="form-group">
<div class="input-group mb-3">
<input class="form-control" asp-for="Settings.AdditionalFragments[index]" placeholder="Fragment">
<div class="input-group-append">
<button type="submit" name="command" value="@($"remove-additional:{index}")" class="btn btn-outline-secondary">Remove</button>
</div>
</div>
<span asp-validation-for="Settings.AdditionalFragments[index]" class="text-danger"></span>
</div>
</li>
}
<li class="list-group-item-action text-center">
<button name="command" value="add-additional" class="btn btn-link text-black-50">Add additional fragment</button>
</li>
</ul>
</div>
</div>
<div class="row mb-4 @(Model.Additional.ShowSettings ? string.Empty : "invisible")">
<div class="col-md-6 col-sm-12 offset-md-3 card shadow-sm pt-4">
<h3 class="card-title">Excluded fragments</h3>
<ul class="list-group list-group-flush" style="list-style-type: none">
@for (var index = 0; index < Model.Settings.ExcludedFragments.Count; index++)
{
<li class="list-group-item justify-content-between align-items-center">
<div class="form-group">
<div class="input-group mb-3">
<input class="form-control" asp-for="Settings.ExcludedFragments[index]" placeholder="Fragment">
<div class="input-group-append">
<button type="submit" name="command" value="@($"remove-excluded:{index}")" class="btn btn-outline-secondary">Remove</button>
</div>
</div>
<span asp-validation-for="Settings.ExcludedFragments[index]" class="text-danger"></span>
</div>
</li>
}
<li class="list-group-item-action text-center">
<button name="command" value="add-excluded" class="btn btn-link text-black-50">Add excluded fragment</button>
</li>
</ul>
</div>
</div>
<div class="row mb-4 @(Model.Additional.ShowSettings ? string.Empty : "invisible")">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<h3 class="card-title text-center pt-2">Fork settings</h3>
<div class="card-body">
<div class="form-group">
<label asp-for="Settings.BTCPayDockerRepository"></label>
<input class="form-control" asp-for="Settings.BTCPayDockerRepository">
<span asp-validation-for="Settings.BTCPayDockerRepository" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Settings.BTCPayDockerBranch"></label>
<input class="form-control" asp-for="Settings.BTCPayDockerBranch">
<span asp-validation-for="Settings.BTCPayDockerBranch" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Settings.CustomBTCPayImage"></label>
<input class="form-control" asp-for="Settings.CustomBTCPayImage">
<span asp-validation-for="Settings.CustomBTCPayImage" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
<div class="row @(Model.Additional.ShowSettings ? string.Empty : "invisible")">
<div class="col-md-6 col-sm-12 offset-md-3 text-center">
<button type="submit" class="m-auto btn btn-primary btn-lg">Continue</button>
</div>
</div>
</form>

View File

@ -0,0 +1,117 @@
@{
ViewData["Title"] = "How do your want your nodes?";
}
@model UpdateSettings<ChainSettings, AdditionalDataStub>;
<form class="container" method="post" asp-action="ChainSettings">
@* <div asp-validation-summary="All" class="text-danger"></div> *@
<input type="hidden" asp-for="Json"/>
<div class="row mb-md-4">
<h2 class=" text-center w-100">@ViewData["Title"]</h2>
</div>
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3">
<label class="card-input-element-label">
<input type="checkbox" asp-for="Settings.Bitcoin" class="card-input-element d-none"/>
<div class="card shadow-sm w-100">
<div class="card-img-top bg-light" style="position:relative; background-image: url(assets/Bitcoin.svg); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">Bitcoin</h3>
<p class="card-text">A full Bitcoin node will de deployed alongside BTCPay in order to validate your incoming transactions without third parties.</p>
<span asp-validation-for="Settings.Bitcoin" class="text-danger"></span>
</div>
</div>
</label>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 card shadow-sm ">
<div class="card-body">
<div class="form-group">
<label asp-for="Settings.Network"></label>
<select class="form-control" asp-for="Settings.Network">
<option value="@NetworkType.Mainnet">Mainnet</option>
<option value="@NetworkType.Testnet">Testnet</option>
<option value="@NetworkType.Regtest">Regtest</option>
</select>
<span asp-validation-for="Settings.Network" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Settings.PruneMode"></label>
<select class="form-control" asp-for="Settings.PruneMode">
<option value="@PruneMode.NoPruning">@EnumTextHelper.PruneMode[PruneMode.NoPruning]</option>
<option value="@PruneMode.Minimal">@EnumTextHelper.PruneMode[PruneMode.Minimal]</option>
<option value="@PruneMode.Small">@EnumTextHelper.PruneMode[PruneMode.Small]</option>
<option value="@PruneMode.ExtraSmall">@EnumTextHelper.PruneMode[PruneMode.ExtraSmall]</option>
<option value="@PruneMode.ExtraExtraSmall">@EnumTextHelper.PruneMode[PruneMode.ExtraExtraSmall]</option>
</select>
<small class="text-muted">Bitcoin uses up quite a bit of data. Fortunately, there is pruning mode which lets you operate uyour full node while only keeping a certain amount of the latest blocks.</small>
<span asp-validation-for="Settings.PruneMode" class="text-danger"></span>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 @(Model.Settings.AltChains.Any() ? "card shadow-sm pt-4" : string.Empty)">
<ul class="list-group list-group-flush" style="list-style-type: none">
@for (var index = 0; index < Model.Settings.AltChains.Count; index++)
{
<li class="list-group-item justify-content-between align-items-center">
<div class="form-group">
<div class="input-group mb-3">
<input class="form-control" asp-for="Settings.AltChains[index]" placeholder="Crypto Code">
<div class="input-group-append">
<button type="submit" name="command" value="@($"remove-chain:{index}")" class="btn btn-outline-secondary">Remove</button>
</div>
</div>
<span asp-validation-for="Settings.AltChains[index]" class="text-danger"></span>
</div>
</li>
}
<li class="list-group-item-action text-center">
<button name="command" value="add-chain" class="btn btn-link text-black-50">Add an altcoin</button>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-md-6 col-sm-12 offset-md-3 text-center">
<button type="submit" class="m-auto btn btn-primary btn-lg">Continue</button>
</div>
</div>
</form>
<div class="modal fade" id="warningModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">⚠ Lightning is not supported with this level of pruning.</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@section Scripts
{
<script>
var warningToggled = false;
$(document).ready(function() {
var select = $("#Settings_PruneMode");
select.on("change input", function(){ toggleWarning(select)});
});
function toggleWarning(select){
var pruning = select.val();
if(pruning === "@PruneMode.ExtraExtraSmall"){
$("#warningModal").modal("show");
}
}
</script>
}

View File

@ -0,0 +1,7 @@
@model UpdateSettings<ConfiguratorSettings, BTCPayServerDockerConfigurator.Controllers.DeployAdditionalData>
<code>
@Json.Serialize(Model.Additional)
</code>

View File

@ -0,0 +1,102 @@
@{
ViewData["Title"] = "What BTCPay Server are you configuring?";
}
@model UpdateSettings<DeploymentSettings, DeploymentAdditionalData>;
<form class="container" method="post" asp-action="DeploymentDestination">
@* <div asp-validation-summary="All" class="text-danger"></div> *@
<input type="hidden" asp-for="Json"/>
<div class="row mb-md-4">
<h2 class=" text-center w-100">@ViewData["Title"]</h2>
</div>
<div class="row mb-4">
<div class="col-md-4">
<label class="card-input-element-label">
<input type="radio" asp-for="Settings.DeploymentType" value="@DeploymentType.ThisMachine" class="card-input-element d-none"/>
<div class="card shadow-sm w-100">
<div class="card-img-top bg-light" style="background-image: url(assets/machine.svg); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">This machine</h3>
<p class="card-text text-center">
We'll configure BTCPay on the current machine
<br/><br/><br/>
</p>
</div>
</div>
</label>
</div>
<div class="col-md-4">
<label class="card-input-element-label">
<input type="radio" asp-for="Settings.DeploymentType" value="@DeploymentType.RemoteMachine" class="card-input-element d-none"/>
<div class="card shadow-sm w-100">
<div class="card-img-top bg-light" style="position:relative; background-image: url(assets/remote.svg); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">A remote machine</h3>
<p class="card-text">A remote machine that you can provide the SSH connectivity details to so that the configurator automagically sets it up for you</p>
</div>
</div>
</label>
</div>
<div class="col-md-4">
<label class="card-input-element-label">
<input type="radio" asp-for="Settings.DeploymentType" value="@DeploymentType.Manual" class="card-input-element d-none"/>
<div class="card shadow-sm w-100 ">
<div class="card-img-top bg-light" style="position:relative; background-image: url(assets/shell.png); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">Manual</h3>
<p class="card-text">We'll generate a bash script that you run on the server without ever providing us access.<br/><br/></p>
</div>
</div>
</label>
</div>
<span asp-validation-for="Settings.DeploymentType" class="text-danger w-100 text-center"></span>
</div>
<div class="row mb-4" style="display: none" id="remote-machine-form">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<h3 class="card-title text-center pt-2">Remote Machine SSH Details</h3>
<div class="card-body">
<div class="form-group">
<label asp-for="Settings.Host"></label>
<input class="form-control" asp-for="Settings.Host">
<span asp-validation-for="Settings.Host" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Settings.Username"></label>
<input class="form-control" asp-for="Settings.Username">
<span asp-validation-for="Settings.Username" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Settings.Password">Password</label>
<input type="password" class="form-control" asp-for="Settings.Password" placeholder="Password">
<span asp-validation-for="Settings.Password" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
<div class="row w-100">
<button type="submit" class="m-auto btn btn-primary btn-lg">Continue</button>
</div>
</form>
@section Scripts
{
<script>
$(document).ready(function() {
toggleForm();
$("label").on("click", toggleForm);
});
function toggleForm(){
var selectedDeploymentType = $('input[name=Settings\\.DeploymentType]:checked').val();
if(selectedDeploymentType === "@nameof(DeploymentType.RemoteMachine)"){
$("#remote-machine-form").show();
}else{
$("#remote-machine-form").hide();
}
}
</script>
}

View File

@ -0,0 +1,72 @@
@{
ViewData["Title"] = "How will you be accessing your BTCPay Server?";
}
@model UpdateSettings<DomainSettings, AdditionalDataStub>;
<form class="container" method="post" asp-action="DomainSettings">
@* <div asp-validation-summary="All" class="text-danger"></div> *@
<input type="hidden" asp-for="Json"/>
<div class="row mb-md-4">
<h2 class=" text-center w-100">@ViewData["Title"]</h2>
</div>
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<div class="card-body">
<div class="form-group">
<label asp-for="Settings.Domain"></label>
<input class="form-control" asp-for="Settings.Domain">
<small class="text-muted">If you don't have a domain to use, you'll still be able to access BTCPay and other related services via TOR. <a href="https://docs.btcpayserver.org/features/dynamicdns" target="_blank">There's also built-in support for dynamic DNS providers.</a></small>
<span asp-validation-for="Settings.Domain" class="text-danger"></span>
</div>
<ul class="list-group list-group-flush" style="list-style-type: none">
@for (var index = 0; index < Model.Settings.AdditionalDomains.Count; index++)
{
<li class="list-group-item justify-content-between align-items-center">
<div class="form-group">
<label asp-for="Settings.AdditionalDomains[index]"></label>
<div class="input-group mb-3">
<input class="form-control" asp-for="Settings.AdditionalDomains[index]">
<div class="input-group-append">
<button type="submit" name="command" value="@($"remove-domain:{index}")" class="btn btn-outline-secondary">Remove</button>
</div>
</div>
<span asp-validation-for="Settings.AdditionalDomains[index]" class="text-danger"></span>
</div>
</li>
}
<li class="list-group-item-action text-center" style="visibility: hidden" id="btn-additional-domain">
<button name="command" value="add-domain" class="btn btn-sm">Add an additional domain</button>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="row w-100">
<button type="submit" class="m-auto btn btn-primary btn-lg">Continue</button>
</div>
</form>
@section Scripts
{
<script>
$(document).ready(function() {
var btn = $("#btn-additional-domain");
var input = $("#Settings_Domain");
toggleBtn(btn, input);
input.on("change input", function(){ toggleBtn(btn, input)});
});
function toggleBtn(btn,input){
if(input.val()){
btn.css("visibility", "visible");
}else{
btn.css("visibility", "hidden");
}
}
</script>
}

View File

@ -0,0 +1,71 @@
@{
ViewData["Title"] = "Lightning Configuration";
}
@model UpdateSettings<LightningSettings, AdditionalDataStub>;
<form class="container" method="post">
<input type="hidden" asp-for="Json"/>
<div class="row mb-md-4">
<h2 class=" text-center w-100">@ViewData["Title"]</h2>
</div>
<div class="row mb-4">
<div class="col-md-4">
<label class="card-input-element-label">
<input type="radio" asp-for="Settings.Implementation" value="lnd" class="card-input-element d-none"/>
<div class="card shadow-sm w-100">
<div class="card-img-top bg-light" style="background-image: url(assets/lnd.png); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">LND</h3>
<p class="card-text text-center">
by Lightning Labs
<br/><br/>
</p>
</div>
</div>
</label>
</div>
<div class="col-md-4">
<label class="card-input-element-label">
<input type="radio" asp-for="Settings.Implementation" value="eclair" class="card-input-element d-none"/>
<div class="card shadow-sm w-100">
<div class="card-img-top bg-light" style="position:relative; background-image: url(assets/eclair.svg); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">Eclair</h3>
<p class="card-text text-center">by ACINQ<br/><br/></p>
</div>
</div>
</label>
</div>
<div class="col-md-4">
<label class="card-input-element-label">
<input type="radio" asp-for="Settings.Implementation" value="clightning" class="card-input-element d-none"/>
<div class="card dark shadow-sm w-100 ">
<div class="card-img-top bg-dark" style="position:relative; background-image: url(assets/clightning.png); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">CLightning</h3>
<p class="card-text text-center">By Blockstream<br/><br/></p>
</div>
</div>
</label>
</div>
<div class="col-md-4 offset-md-4">
<label class="card-input-element-label">
<input type="radio" asp-for="Settings.Implementation" value="" class="card-input-element d-none" />
<div class="card dark shadow-sm w-100 ">
<div class="card-img-top bg-dark" style="position:relative; background-image: url(assets/sad.svg); background-repeat: no-repeat ; background-size: contain ;background-position: center; width: 100%; height: 225px"></div>
<div class="card-body">
<h3 class="card-title text-center">None</h3>
<p class="card-text text-center">You're missing out.<br/><br/></p>
</div>
</div>
</label>
</div>
<span asp-validation-for="Settings.Implementation" class="text-danger w-100 text-center"></span>
</div>
<div class="row w-100">
<button type="submit" class="m-auto btn btn-primary btn-lg">Continue</button>
</div>
</form>

View File

@ -0,0 +1,256 @@
@{ ViewData["Title"] = "Does this look right to you?"; }
@model UpdateSettings<ConfiguratorSettings, AdditionalDataStub>;
<div class="container">
<input type="hidden" asp-for="Json" />
<div class="row mb-md-4">
<h2 class=" text-center w-100">@ViewData["Title"]</h2>
</div>
<div class="row mb-4">
<div class="col-md-6 col-sm-12 offset-md-3 ">
<div class="card shadow-sm ">
<table class="table">
<thead>
<tr>
<th colspan="2">
<div class="d-flex justify-content-between">
<h3 class="mb-0 pb-0">Deployment</h3>
<a asp-action="DeploymentDestination">Edit</a>
</div>
</th>
</tr>
</thead>
<tr>
<td>Destination</td>
<td>@Model.Settings.DeploymentSettings.DeploymentType</td>
</tr>
@if (Model.Settings.DeploymentSettings.DeploymentType == DeploymentType.RemoteMachine) {
<tr>
<td>Machine Host</td>
<td>@Model.Settings.DeploymentSettings.Host</td>
</tr>
<tr>
<td>Machine User</td>
<td>@Model.Settings.DeploymentSettings.Username</td>
</tr>
}
<thead>
<tr>
<th colspan="2">
<div class="d-flex justify-content-between">
<h3 class="mb-0 pb-0">Domain</h3>
<a asp-action="DomainSettings">Edit</a>
</div>
</th>
</tr>
</thead>
<tr>
<td>Primary domain</td>
<td>@(string.IsNullOrEmpty(Model.Settings.DomainSettings.Domain) ? "Not set" : Model.Settings.DomainSettings.Domain)</td>
</tr>
<tr>
<td>Additional domains</td>
<td>@(Model.Settings.DomainSettings.AdditionalDomains.Any() ? string.Join(',', Model.Settings.DomainSettings.AdditionalDomains) : "None")</td>
</tr>
<thead>
<tr>
<th colspan="2">
<div class="d-flex justify-content-between">
<h3 class="mb-0 pb-0">Chain</h3>
<a asp-action="ChainSettings">Edit</a>
</div>
</th>
</tr>
</thead>
<tr>
<td>Bitcoin</td>
<td>@(Model.Settings.ChainSettings.Bitcoin ? "Enabled" : "Not enabled")</td>
</tr>
<tr>
<td>Network</td>
<td>
@Model.Settings.ChainSettings.Network.ToString()
</td>
</tr>
@if (Model.Settings.ChainSettings.AltChains.Any()) {
<tr>
<td>Altcoins</td>
<td>@string.Join(',', Model.Settings.ChainSettings.AltChains)</td>
</tr>
}
<tr>
<td>Pruning</td>
<td>
@EnumTextHelper.PruneMode[Model.Settings.ChainSettings.PruneMode]
</td>
</tr>
<thead>
<tr>
<th colspan="2">
<div class="d-flex justify-content-between">
<h3 class="mb-0 pb-0">Lightning</h3>
<a asp-action="LightningSettings">Edit</a>
</div>
</th>
</tr>
</thead>
<tr>
<td>Lightning</td>
<td>@(string.IsNullOrEmpty(Model.Settings.LightningSettings.Implementation) ? "Not enabled" : Model.Settings.LightningSettings.Implementation)</td>
</tr>
<thead>
<tr>
<th colspan="2">
<div class="d-flex justify-content-between">
<h3 class="mb-0 pb-0">Additional Services</h3>
<a asp-action="AdditionalServices">Edit</a>
</div>
</th>
</tr>
</thead>
<tr>
<td>
BTC Transmuter
</td>
<td>
@(Model.Settings.AdditionalServices.BTCTransmuterSettings.Enabled ? "Enabled" : "Not enabled")
</td>
</tr>
@if (Model.Settings.AdditionalServices.BTCTransmuterSettings.Enabled) {
<tr>
<td>
BTC Transmuter Host
</td>
<td>
@Model.Settings.AdditionalServices.BTCTransmuterSettings.Host
</td>
</tr>
}
<tr>
<td>
Libre Patron
</td>
<td>
@(Model.Settings.AdditionalServices.LibrePatronSettings.Enabled ? "Enabled" : "Not enabled")
</td>
</tr>
@if (Model.Settings.AdditionalServices.LibrePatronSettings.Enabled) {
<tr>
<td>
Libre Patron Host
</td>
<td>
@Model.Settings.AdditionalServices.LibrePatronSettings.Host
</td>
</tr>
}
<tr>
<td>
WooCommerce
</td>
<td>
@(Model.Settings.AdditionalServices.WooCommerceSettings.Enabled ? "Enabled" : "Not enabled")
</td>
</tr>
@if (Model.Settings.AdditionalServices.WooCommerceSettings.Enabled) {
<tr>
<td>
WooCommerce Host
</td>
<td>
@Model.Settings.AdditionalServices.WooCommerceSettings.Host
</td>
</tr>
}
<tr>
<td>
Tor Relay
</td>
<td>
@(Model.Settings.AdditionalServices.TorRelaySettings.Enabled ? "Enabled" : "Not enabled")
</td>
</tr>
@if (Model.Settings.AdditionalServices.TorRelaySettings.Enabled) {
<tr>
<td>
Tor Relay nickname
</td>
<td>
@Model.Settings.AdditionalServices.TorRelaySettings.Nickname
</td>
</tr>
<tr>
<td>
Tor Relay contact email
</td>
<td>
@Model.Settings.AdditionalServices.TorRelaySettings.Email
</td>
</tr>
}
<thead>
<tr>
<th colspan="2">
<div class="d-flex justify-content-between">
<h3 class="mb-0 pb-0">Advanced</h3>
<a asp-action="AdvancedSettings">Edit</a>
</div>
</th>
</tr>
</thead>
@if (Model.Settings.AdvancedSettings.AnythingSet())
{
<tr>
<td>Custom BTCPay Image</td>
<td>@(string.IsNullOrEmpty(Model.Settings.AdvancedSettings.CustomBTCPayImage) ? "Not set" : Model.Settings.AdvancedSettings.CustomBTCPayImage)</td>
</tr>
<tr>
<td>BTCPay docker Git repository</td>
<td>@(string.IsNullOrEmpty(Model.Settings.AdvancedSettings.BTCPayDockerRepository) ? "Not set" : Model.Settings.AdvancedSettings.BTCPayDockerRepository)</td>
</tr>
<tr>
<td>BTCPay docker Git branch</td>
<td>@(string.IsNullOrEmpty(Model.Settings.AdvancedSettings.BTCPayDockerBranch) ? "Not set" : Model.Settings.AdvancedSettings.BTCPayDockerBranch)</td>
</tr>
@if (Model.Settings.AdvancedSettings.AdditionalFragments.Any())
{
<tr>
<td>Additional fragments</td>
<td>@string.Join(',', Model.Settings.AdvancedSettings.AdditionalFragments)</td>
</tr>
}
@if (Model.Settings.AdvancedSettings.ExcludedFragments.Any())
{
<tr>
<td>Excluded fragments</td>
<td>@string.Join(',', Model.Settings.AdvancedSettings.ExcludedFragments)</td>
</tr>
}
}
else
{
<tr><td colspan="2">No advanced settings configured</td></tr>
}
</table>
</div>
</div>
</div>
<form method="post" asp-action="Deploy" class="row w-100">
<input type="hidden" asp-for="Json" />
<button type="submit" class="m-auto btn btn-primary btn-lg p-0 border-0 overflow-hidden justdoitbutton bg-transparent">
<img src="assets/doit.gif" height="100" />
</button>
</form>
</div>

View File

@ -0,0 +1,25 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - BTCPayServer Docker Configurator</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="~/css/site.css"/>
</head>
<body class="bg-light">
<header class="bg-dark mb-md-4">
<div class="container p-md-5 text-white">
<h1 class="text-center w-100">BTCPay Server Configurator</h1>
</div>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
</body>
</html>

View File

@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

View File

@ -0,0 +1,3 @@
@using BTCPayServerDockerConfigurator
@using BTCPayServerDockerConfigurator.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@ -0,0 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,7 @@
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="64" width="64" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<g transform="translate(0.00630876,-0.00301984)">
<path fill="#f7931a" d="m63.033,39.744c-4.274,17.143-21.637,27.576-38.782,23.301-17.138-4.274-27.571-21.638-23.295-38.78,4.272-17.145,21.635-27.579,38.775-23.305,17.144,4.274,27.576,21.64,23.302,38.784z"/>
<path fill="#FFF" d="m46.103,27.444c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1 @@
<svg id="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 106" shape-rendering="crispEdges"><defs><style>.cls-1{fill:#a3d6ff;}.cls-2{fill:#3db0ff;}.cls-3{fill:#b5d9f7;}.cls-4{fill:#75c4ef;}.cls-5{fill:#a4eaff;}.cls-6{fill:#8ec5f5;}.cls-7{fill:#b5dcff;}.cls-8{fill:#1a4184;}.cls-9{fill:#202963;}.cls-10{fill:#315e9e;}.cls-11{fill:#2c005e;}.cls-12{fill:#4786ba;}.cls-13{fill:#4991cc;}.cls-14{fill:#3f77a6;}.cls-15{fill:#9e0567;}.cls-16{fill:#163372;}.cls-17{fill:#0a103a;}.cls-18{fill:#002270;}.cls-19{fill:#150056;}.cls-20{fill:#1b6294;}.cls-21{fill:#0064bd;}.cls-22{fill:#306b94;}.cls-23{fill:#1e5ba6;}.cls-24{fill:#00143c;}.cls-25{fill:#224c69;}.cls-26{fill:#163c70;}.cls-27{fill:#1c5084;}.cls-28{fill:#371863;}.cls-29{fill:#ce84bf;}.cls-30{fill:#e2acd4;}.cls-31{fill:#872983;}.cls-32{fill:#69d0e8;}.cls-33{fill:#2e90c6;}.cls-34{fill:#efc2e6;}.cls-35{fill:#c163a9;}</style></defs><title>eclair-logo</title><g id="logo-2" data-name="logo"><polygon class="cls-1" points="64 0 56 17 64 24 64 0"/><polygon class="cls-2" points="40 39 64 24 56 17 40 39"/><polygon class="cls-3" points="30 34 56 17 64 0 30 34"/><polygon class="cls-4" points="56 17 30 34 40 39 56 17"/><polygon class="cls-5" points="0 64 40 39 30 34 0 64"/><polygon class="cls-6" points="64 0 64 24 80 27 64 0"/><polygon class="cls-7" points="64 0 80 27 94 30 64 0"/><polygon class="cls-8" points="112 72 107 43 89 49 112 72"/><polygon class="cls-9" points="122 58 107 43 112 72 122 58"/><polygon class="cls-10" points="107 43 94 30 89 49 107 43"/><polygon class="cls-11" points="122 82 122 58 112 72 122 82"/><polygon class="cls-12" points="64 24 77 37 80 27 64 24"/><polygon class="cls-13" points="77 37 89 49 80 27 77 37"/><polygon class="cls-14" points="89 49 94 30 80 27 89 49"/><polygon class="cls-15" points="149 65 122 58 122 82 149 65"/><polygon class="cls-16" points="100 71 102 86 112 84 100 71"/><polygon class="cls-17" points="102 86 122 106 112 84 102 86"/><polygon class="cls-18" points="84 68 102 86 100 71 84 68"/><polygon class="cls-19" points="112 84 122 106 122 82 112 84"/><polygon class="cls-20" points="77 49 89 49 77 37 77 49"/><polygon class="cls-21" points="64 48 84 68 77 49 64 48"/><polygon class="cls-22" points="64 24 64 48 77 37 64 24"/><polygon class="cls-23" points="89 49 100 71 112 72 89 49"/><polygon class="cls-24" points="100 71 112 84 112 72 100 71"/><polygon class="cls-25" points="77 37 64 48 77 49 77 37"/><polygon class="cls-26" points="89 49 84 68 100 71 89 49"/><polygon class="cls-27" points="77 49 84 68 89 49 77 49"/><polygon class="cls-28" points="112 84 122 82 112 72 112 84"/><polygon class="cls-29" points="122 106 157 71 149 65 122 106"/><polygon class="cls-30" points="149 65 157 71 186 42 149 65"/><polygon class="cls-31" points="122 82 122 106 149 65 122 82"/><polygon class="cls-32" points="0 64 64 48 40 39 0 64"/><polygon class="cls-33" points="40 39 64 48 64 24 40 39"/><polygon class="cls-34" points="146 52 149 65 186 42 146 52"/><polygon class="cls-35" points="146 52 122 58 149 65 146 52"/></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<g><path fill="#3E474A" d="M207.5,10c-12.4,0-22.3,10-22.3,22.3v935.3c0,12.4,10,22.4,22.3,22.4h584.9c12.4,0,22.4-10,22.4-22.4V32.3c0-12.4-10-22.3-22.4-22.3H207.5L207.5,10z M256.3,118.9h487.5v139.7H256.3V118.9L256.3,118.9z M256.3,293.3h487.5V433H256.3V293.3z M256.3,464.9h487.5v139.7H256.3V464.9L256.3,464.9z M500,834.8c28.7,0,52,23.3,52,52.1s-23.3,52-52,52c-28.7,0-52.1-23.3-52.1-52S471.3,834.8,500,834.8L500,834.8z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 764 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<g><g fill="#3E474A" transform="translate(0.000000,511.000000) scale(0.100000,-0.100000)"><path d="M6998.9,4985.3c-214.9-30.4-499.9-114.5-584-170.5c-151.8-102.8-168.2-264-32.7-378.4c84.1-72.4,123.8-70.1,471.9,23.4c934.4,250,1931.9-210.2,2359.4-1090.9c144.9-299,198.6-542,198.6-894.7c0-233.6-11.7-324.7-60.7-513.9c-95.8-355.1-95.8-387.8-14-478.9c58.4-65.4,86.4-79.4,168.2-79.4c133.2,0,214.9,93.4,282.6,320c184.6,621.4,140.2,1245.1-128.5,1831.4C9199.5,4550.8,8094.5,5146.5,6998.9,4985.3z"/><path d="M7122.7,4074.3c-200.9-35-357.4-91.1-425.1-156.5c-130.8-119.1-65.4-348.1,109.8-392.5c37.4-9.3,109.8,0,182.2,25.7c158.8,53.7,441.5,70.1,602.7,37.4c436.9-91.1,796.6-450.9,885.4-883c35-165.8,21-420.5-35-593.4c-49-151.8-35-231.3,46.7-315.4c77.1-77.1,217.3-77.1,308.4,0c130.8,109.8,214.9,539.6,170.5,869c-51.4,390.1-193.9,675.1-478.9,957.8c-205.5,205.6-455.5,350.4-719.5,415.8C7589.9,4085.9,7281.6,4102.3,7122.7,4074.3z"/><path d="M7132.1,3132.8c-306-137.8-165.8-532.6,158.9-450.9c191.5,49.1,329.4-95.8,280.3-289.7c-44.4-170.5,35-294.3,200.9-317.7c72.4-9.3,102.8,0,161.2,49.1c98.1,84.1,130.8,193.9,119.1,397.1c-16.4,259.3-147.2,460.2-376.1,579.3C7552.5,3163.2,7241.8,3184.2,7132.1,3132.8z"/><path d="M5840.2,3023c-250-49.1-196.2,0-2962.1-2763.5C-100.3-2714.3,102.9-2487.7,102.9-2849.7c0-327.1,23.4-359.8,801.3-1137.7c780.2-777.9,812.9-801.3,1137.7-801.3c362.1,0,135.5-200.9,3109.3,2775.2C7171.8,7.2,7804.9,656.6,7842.2,736.1c109.8,235.9,109.8,469.5,0,700.8c-35,72.4-243,299-682.1,742.9c-348,352.7-677.5,670.4-731.2,707.8C6251.4,3011.4,6031.8,3060.4,5840.2,3023z M6153.3,2016.2c254.6-63.1,530.3-282.7,661.1-527.9c95.8-179.9,123.8-301.4,123.8-516.3c0-532.6-364.4-960.1-894.7-1055.9c-329.4-58.4-682.1,56.1-927.4,301.3c-373.8,371.4-429.8,925.1-137.8,1359.6C5232.9,1960.1,5690.7,2130.7,6153.3,2016.2z M4244.7,453.4c264-128.5,310.7-497.6,86.4-693.8c-345.7-303.7-852.7,56.1-677.5,478.9c49.1,116.8,98.1,168.2,210.2,219.6C3987.7,516.5,4118.6,514.1,4244.7,453.4z M5207.2-488c243-161.2,243-544.3,2.4-705.5c-271-177.5-619.1-28-670.5,289.7c-28,182.2,102.8,401.8,282.7,469.5C4926.8-396.9,5106.7-422.6,5207.2-488z"/><path d="M5728.1,1579.4c-299-77.1-495.2-352.7-467.2-654.1c35-397.1,427.5-654.1,801.3-530.3c429.8,142.5,560.6,696.1,240.6,1016.2C6162.6,1553.7,5910.3,1626.1,5728.1,1579.4z"/></g></g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 121.669 121.669" style="enable-background:new 0 0 121.669 121.669;" xml:space="preserve">
<g>
<path fill="#fff" d="M60.834,121.669C27.291,121.669,0,94.379,0,60.834C0,27.29,27.291,0,60.834,0s60.834,27.29,60.834,60.834
C121.669,94.379,94.378,121.669,60.834,121.669z M60.834,6C30.599,6,6,30.599,6,60.834c0,30.236,24.599,54.835,54.834,54.835
s54.834-24.599,54.834-54.835C115.669,30.599,91.07,6,60.834,6z"/>
<path fill="#fff" d="M78.843,97.437h-6c0-5.642-5.477-10.231-12.208-10.231s-12.208,4.59-12.208,10.231h-6c0-8.95,8.168-16.231,18.208-16.231
S78.843,88.486,78.843,97.437z"/>
<path fill="#fff" d="M32.14,69.218c-7.853,0-14.242-7.115-14.242-15.86h6c0,5.437,3.697,9.86,8.242,9.86s8.242-4.423,8.242-9.86h6
C46.382,62.103,39.993,69.218,32.14,69.218z"/>
<path fill="#fff" d="M89.529,69.218c-7.854,0-14.242-7.115-14.242-15.86h6c0,5.437,3.697,9.86,8.242,9.86s8.242-4.423,8.242-9.86h6
C103.771,62.103,97.382,69.218,89.529,69.218z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,162 @@
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
/* Provide sufficient contrast against white background */
a {
color: #0366d6;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
/* Margin bottom by footer height */
margin-bottom: 60px;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px; /* Vertically center the text there */
}
/*cards as an input element*/
label.card-input-element-label {
width: 100%;
font-size: 1rem;
}
.card-input-element + .card {
border: 2px solid transparent;
display: inline-table;
}
.card-input-element + .card:hover {
cursor: pointer;
}
.card-input-element:checked + .card {
border: 2px solid var(--secondary);
-webkit-transition: border .3s;
-o-transition: border .3s;
transition: border .3s;
}
label{
cursor: pointer;
}
.card-input-element:checked+.card::after {
content: '✓';
color: #1b1e21;
font-size: 50px;
-webkit-animation-name: fadeInCheckbox;
animation-name: fadeInCheckbox;
-webkit-animation-duration: .5s;
animation-duration: .5s;
-webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
position: absolute;
right: 5px;
top:0;
text-align: center;
}
.card-input-element:checked+.card.dark::after{
color: var(--light);
}
@-webkit-keyframes fadeInCheckbox {
from {
opacity: 0;
-webkit-transform: rotateZ(-20deg);
}
to {
opacity: 1;
-webkit-transform: rotateZ(0deg);
}
}
@keyframes fadeInCheckbox {
from {
opacity: 0;
transform: rotateZ(-20deg);
}
to {
opacity: 1;
transform: rotateZ(0deg);
}
}
.styled-checkbox-label:hover{
background-color: var(--light);
}
.justdoitbutton:hover{
-webkit-filter: brightness(70%);
-webkit-transition: all 1s ease;
-moz-transition: all 1s ease;
-o-transition: all 1s ease;
-ms-transition: all 1s ease;
transition: all 1s ease;
}
/**/

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,4 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2018 Twitter, Inc.
Copyright (c) 2011-2018 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,331 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]):not([tabindex]) {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
color: inherit;
text-decoration: none;
}
a:not([href]):not([tabindex]):focus {
outline: 0;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
-webkit-appearance: listbox;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
Copyright (c) .NET Foundation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
these files except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.

View File

@ -0,0 +1,432 @@
// Unobtrusive validation support library for jQuery and jQuery Validate
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// @version v3.2.11
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */
/*global document: false, jQuery: false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define("jquery.validate.unobtrusive", ['jquery-validation'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports
module.exports = factory(require('jquery-validation'));
} else {
// Browser global
jQuery.validator.unobtrusive = factory(jQuery);
}
}(function ($) {
var $jQval = $.validator,
adapters,
data_validation = "unobtrusiveValidation";
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
function splitAndTrim(value) {
return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g);
}
function escapeAttributeValue(value) {
// As mentioned on http://api.jquery.com/category/selectors/
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
}
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
}
function appendModelPrefix(value, prefix) {
if (value.indexOf("*.") === 0) {
value = value.replace("*.", prefix);
}
return value;
}
function onError(error, inputElement) { // 'this' is the form element
var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
error.removeClass("input-validation-error").appendTo(container);
}
else {
error.hide();
}
}
function onErrors(event, validator) { // 'this' is the form element
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
function onSuccess(error) { // 'this' is the form element
var container = error.data("unobtrusiveContainer");
if (container) {
var replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null;
container.addClass("field-validation-valid").removeClass("field-validation-error");
error.removeData("unobtrusiveContainer");
if (replace) {
container.empty();
}
}
}
function onReset(event) { // 'this' is the form element
var $form = $(this),
key = '__jquery_unobtrusive_validation_form_reset';
if ($form.data(key)) {
return;
}
// Set a flag that indicates we're currently resetting the form.
$form.data(key, true);
try {
$form.data("validator").resetForm();
} finally {
$form.removeData(key);
}
$form.find(".validation-summary-errors")
.addClass("validation-summary-valid")
.removeClass("validation-summary-errors");
$form.find(".field-validation-error")
.addClass("field-validation-valid")
.removeClass("field-validation-error")
.removeData("unobtrusiveContainer")
.find(">*") // If we were using valmsg-replace, get the underlying error
.removeData("unobtrusiveContainer");
}
function validationInfo(form) {
var $form = $(form),
result = $form.data(data_validation),
onResetProxy = $.proxy(onReset, form),
defaultOptions = $jQval.unobtrusive.options || {},
execInContext = function (name, args) {
var func = defaultOptions[name];
func && $.isFunction(func) && func.apply(form, args);
};
if (!result) {
result = {
options: { // options structure passed to jQuery Validate's validate() method
errorClass: defaultOptions.errorClass || "input-validation-error",
errorElement: defaultOptions.errorElement || "span",
errorPlacement: function () {
onError.apply(form, arguments);
execInContext("errorPlacement", arguments);
},
invalidHandler: function () {
onErrors.apply(form, arguments);
execInContext("invalidHandler", arguments);
},
messages: {},
rules: {},
success: function () {
onSuccess.apply(form, arguments);
execInContext("success", arguments);
}
},
attachValidation: function () {
$form
.off("reset." + data_validation, onResetProxy)
.on("reset." + data_validation, onResetProxy)
.validate(this.options);
},
validate: function () { // a validation function that is called by unobtrusive Ajax
$form.validate();
return $form.valid();
}
};
$form.data(data_validation, result);
}
return result;
}
$jQval.unobtrusive = {
adapters: [],
parseElement: function (element, skipAttach) {
/// <summary>
/// Parses a single HTML element for unobtrusive validation attributes.
/// </summary>
/// <param name="element" domElement="true">The HTML element to be parsed.</param>
/// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the
/// validation to the form. If parsing just this single element, you should specify true.
/// If parsing several elements, you should specify false, and manually attach the validation
/// to the form when you are finished. The default is false.</param>
var $element = $(element),
form = $element.parents("form")[0],
valInfo, rules, messages;
if (!form) { // Cannot do client-side validation without a form
return;
}
valInfo = validationInfo(form);
valInfo.options.rules[element.name] = rules = {};
valInfo.options.messages[element.name] = messages = {};
$.each(this.adapters, function () {
var prefix = "data-val-" + this.name,
message = $element.attr(prefix),
paramValues = {};
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
prefix += "-";
$.each(this.params, function () {
paramValues[this] = $element.attr(prefix + this);
});
this.adapt({
element: element,
form: form,
message: message,
params: paramValues,
rules: rules,
messages: messages
});
}
});
$.extend(rules, { "__dummy__": true });
if (!skipAttach) {
valInfo.attachValidation();
}
},
parse: function (selector) {
/// <summary>
/// Parses all the HTML elements in the specified selector. It looks for input elements decorated
/// with the [data-val=true] attribute value and enables validation according to the data-val-*
/// attribute values.
/// </summary>
/// <param name="selector" type="String">Any valid jQuery selector.</param>
// $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one
// element with data-val=true
var $selector = $(selector),
$forms = $selector.parents()
.addBack()
.filter("form")
.add($selector.find("form"))
.has("[data-val=true]");
$selector.find("[data-val=true]").each(function () {
$jQval.unobtrusive.parseElement(this, true);
});
$forms.each(function () {
var info = validationInfo(this);
if (info) {
info.attachValidation();
}
});
}
};
adapters = $jQval.unobtrusive.adapters;
adapters.add = function (adapterName, params, fn) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will
/// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
/// mmmm is the parameter name).</param>
/// <param name="fn" type="Function">The function to call, which adapts the values from the HTML
/// attributes into jQuery Validate rules and/or messages.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
if (!fn) { // Called with no params, just a function
fn = params;
params = [];
}
this.push({ name: adapterName, params: params, adapt: fn });
return this;
};
adapters.addBool = function (adapterName, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has no parameter values.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, function (options) {
setValidationValues(options, ruleName || adapterName, true);
});
};
adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and
/// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a minimum value.</param>
/// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a maximum value.</param>
/// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you
/// have both a minimum and maximum value.</param>
/// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the minimum value. The default is "min".</param>
/// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the maximum value. The default is "max".</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) {
var min = options.params.min,
max = options.params.max;
if (min && max) {
setValidationValues(options, minMaxRuleName, [min, max]);
}
else if (min) {
setValidationValues(options, minRuleName, min);
}
else if (max) {
setValidationValues(options, maxRuleName, max);
}
});
};
adapters.addSingleVal = function (adapterName, attribute, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has a single value.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param>
/// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value.
/// The default is "val".</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [attribute || "val"], function (options) {
setValidationValues(options, ruleName || adapterName, options.params[attribute]);
});
};
$jQval.addMethod("__dummy__", function (value, element, params) {
return true;
});
$jQval.addMethod("regex", function (value, element, params) {
var match;
if (this.optional(element)) {
return true;
}
match = new RegExp(params).exec(value);
return (match && (match.index === 0) && (match[0].length === value.length));
});
$jQval.addMethod("nonalphamin", function (value, element, nonalphamin) {
var match;
if (nonalphamin) {
match = value.match(/\W/g);
match = match && match.length >= nonalphamin;
}
return match;
});
if ($jQval.methods.extension) {
adapters.addSingleVal("accept", "mimtype");
adapters.addSingleVal("extension", "extension");
} else {
// for backward compatibility, when the 'extension' validation method does not exist, such as with versions
// of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for
// validating the extension, and ignore mime-type validations as they are not supported.
adapters.addSingleVal("extension", "extension", "accept");
}
adapters.addSingleVal("regex", "pattern");
adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");
adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range");
adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength");
adapters.add("equalto", ["other"], function (options) {
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0];
setValidationValues(options, "equalTo", element);
});
adapters.add("required", function (options) {
// jQuery Validate equates "required" with "mandatory" for checkbox elements
if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
setValidationValues(options, "required", true);
}
});
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = appendModelPrefix(fieldName, prefix);
value.data[paramName] = function () {
var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']");
// For checkboxes and radio buttons, only pick up values from checked fields.
if (field.is(":checkbox")) {
return field.filter(":checked").val() || field.filter(":hidden").val() || '';
}
else if (field.is(":radio")) {
return field.filter(":checked").val() || '';
}
return field.val();
};
});
setValidationValues(options, "remote", value);
});
adapters.add("password", ["min", "nonalphamin", "regex"], function (options) {
if (options.params.min) {
setValidationValues(options, "minlength", options.params.min);
}
if (options.params.nonalphamin) {
setValidationValues(options, "nonalphamin", options.params.nonalphamin);
}
if (options.params.regex) {
setValidationValues(options, "regex", options.params.regex);
}
});
adapters.add("fileextensions", ["extensions"], function (options) {
setValidationValues(options, "extension", options.params.extensions);
});
$(function () {
$jQval.unobtrusive.parse(document);
});
return $jQval.unobtrusive;
}));

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
=====================
Copyright Jörn Zaefferer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More