mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 22:44:29 +01:00
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
@@ -10,6 +11,7 @@ using NBitcoin;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using BTCPayServer.Services;
|
using BTCPayServer.Services;
|
||||||
using BTCPayServer.HostedServices;
|
using BTCPayServer.HostedServices;
|
||||||
|
using BTCPayServer.Services.Apps;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
@@ -25,37 +27,58 @@ namespace BTCPayServer.Controllers
|
|||||||
_cachedServerSettings = cachedServerSettings;
|
_cachedServerSettings = cachedServerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<ViewResult> GoToApp(string appId, AppType? appType)
|
||||||
|
{
|
||||||
|
if (appType.HasValue && !string.IsNullOrEmpty(appId))
|
||||||
|
{
|
||||||
|
switch (appType.Value)
|
||||||
|
{
|
||||||
|
case AppType.Crowdfund:
|
||||||
|
{
|
||||||
|
var serviceProvider = HttpContext.RequestServices;
|
||||||
|
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||||
|
controller.Url = Url;
|
||||||
|
controller.ControllerContext = ControllerContext;
|
||||||
|
var res = await controller.ViewCrowdfund(appId, null) as ViewResult;
|
||||||
|
if (res != null)
|
||||||
|
{
|
||||||
|
res.ViewName = "/Views/AppsPublic/ViewCrowdfund.cshtml";
|
||||||
|
return res; // return
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AppType.PointOfSale:
|
||||||
|
{
|
||||||
|
var serviceProvider = HttpContext.RequestServices;
|
||||||
|
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||||
|
controller.Url = Url;
|
||||||
|
controller.ControllerContext = ControllerContext;
|
||||||
|
var res = await controller.ViewPointOfSale(appId) as ViewResult;
|
||||||
|
if (res != null)
|
||||||
|
{
|
||||||
|
res.ViewName = "/Views/AppsPublic/ViewPointOfSale.cshtml";
|
||||||
|
return res; // return
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
if (_cachedServerSettings.RootAppType is Services.Apps.AppType.Crowdfund)
|
var matchedDomainMapping = _cachedServerSettings.DomainToAppMapping.FirstOrDefault(item =>
|
||||||
|
item.Domain.Equals(Request.Host.Host, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
if (matchedDomainMapping != null)
|
||||||
{
|
{
|
||||||
var serviceProvider = HttpContext.RequestServices;
|
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? View("Home");
|
||||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
|
||||||
controller.Url = Url;
|
|
||||||
controller.ControllerContext = ControllerContext;
|
|
||||||
var res = await controller.ViewCrowdfund(_cachedServerSettings.RootAppId, null) as ViewResult;
|
|
||||||
if (res != null)
|
|
||||||
{
|
|
||||||
res.ViewName = "/Views/AppsPublic/ViewCrowdfund.cshtml";
|
|
||||||
return res; // return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_cachedServerSettings.RootAppType is Services.Apps.AppType.PointOfSale)
|
|
||||||
{
|
|
||||||
var serviceProvider = HttpContext.RequestServices;
|
|
||||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
|
||||||
controller.Url = Url;
|
|
||||||
controller.ControllerContext = ControllerContext;
|
|
||||||
var res = await controller.ViewPointOfSale(_cachedServerSettings.RootAppId) as ViewResult;
|
|
||||||
if (res != null)
|
|
||||||
{
|
|
||||||
res.ViewName = "/Views/AppsPublic/ViewPointOfSale.cshtml";
|
|
||||||
return res; // return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return View("Home");
|
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? View("Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("translate")]
|
[Route("translate")]
|
||||||
@@ -116,20 +139,6 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult About()
|
|
||||||
{
|
|
||||||
ViewData["Message"] = "Your application description page.";
|
|
||||||
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult Contact()
|
|
||||||
{
|
|
||||||
ViewData["Message"] = "Your contact page.";
|
|
||||||
|
|
||||||
return View();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult Error()
|
public IActionResult Error()
|
||||||
{
|
{
|
||||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ using NBitcoin.DataEncoders;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -33,6 +34,7 @@ using BTCPayServer.Storage.Services.Providers;
|
|||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using BTCPayServer.Data;
|
using BTCPayServer.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
@@ -459,43 +461,61 @@ namespace BTCPayServer.Controllers
|
|||||||
public async Task<IActionResult> Policies()
|
public async Task<IActionResult> Policies()
|
||||||
{
|
{
|
||||||
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
var data = (await _SettingsRepository.GetSettingAsync<PoliciesSettings>()) ?? new PoliciesSettings();
|
||||||
|
await GetAppSelectList();
|
||||||
// load display app dropdown
|
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
|
||||||
{
|
|
||||||
var userId = _UserManager.GetUserId(base.User);
|
|
||||||
var selectList = ctx.Users.Where(user => user.Id == userId)
|
|
||||||
.SelectMany(s => s.UserStores)
|
|
||||||
.Select(s => s.StoreData)
|
|
||||||
.SelectMany(s => s.Apps)
|
|
||||||
.Select(a => new SelectListItem($"{a.AppType} - {a.Name}", a.Id)).ToList();
|
|
||||||
selectList.Insert(0, new SelectListItem("(None)", null));
|
|
||||||
ViewBag.AppsList = new SelectList(selectList, "Value", "Text", data.RootAppId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return View(data);
|
return View(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/policies")]
|
[Route("server/policies")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Policies(PoliciesSettings settings)
|
public async Task<IActionResult> Policies(PoliciesSettings settings, string command = "")
|
||||||
{
|
{
|
||||||
if (!String.IsNullOrEmpty(settings.RootAppId))
|
await GetAppSelectList();
|
||||||
|
if (command == "add-domain")
|
||||||
{
|
{
|
||||||
using (var ctx = _ContextFactory.CreateContext())
|
ModelState.Clear();
|
||||||
{
|
settings.DomainToAppMapping.Add(new PoliciesSettings.DomainToAppMappingItem());
|
||||||
var app = ctx.Apps.SingleOrDefault(a => a.Id == settings.RootAppId);
|
return View(settings);
|
||||||
if (app != null)
|
}
|
||||||
settings.RootAppType = Enum.Parse<AppType>(app.AppType);
|
if (command.StartsWith("remove-domain", StringComparison.InvariantCultureIgnoreCase))
|
||||||
else
|
{
|
||||||
settings.RootAppType = null;
|
ModelState.Clear();
|
||||||
}
|
var index = int.Parse(command.Substring(command.IndexOf(":",StringComparison.InvariantCultureIgnoreCase) + 1), CultureInfo.InvariantCulture);
|
||||||
|
settings.DomainToAppMapping.RemoveAt(index);
|
||||||
|
return View(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return View(settings);
|
||||||
|
}
|
||||||
|
var appIdsToFetch = settings.DomainToAppMapping.Select(item => item.AppId).ToList();
|
||||||
|
if (!string.IsNullOrEmpty(settings.RootAppId))
|
||||||
|
{
|
||||||
|
appIdsToFetch.Add(settings.RootAppId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// not preserved on client side, but clearing it just in case
|
|
||||||
settings.RootAppType = null;
|
settings.RootAppType = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appIdsToFetch.Any())
|
||||||
|
{
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var apps = await ctx.Apps.Where(data => appIdsToFetch.Contains(data.Id))
|
||||||
|
.ToDictionaryAsync(data => data.Id, data => Enum.Parse<AppType>(data.AppType));
|
||||||
|
if (!string.IsNullOrEmpty(settings.RootAppId))
|
||||||
|
{
|
||||||
|
settings.RootAppType = apps[settings.RootAppId];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var domainToAppMappingItem in settings.DomainToAppMapping)
|
||||||
|
{
|
||||||
|
domainToAppMappingItem.AppType = apps[domainToAppMappingItem.AppId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await _SettingsRepository.UpdateSetting(settings);
|
await _SettingsRepository.UpdateSetting(settings);
|
||||||
TempData["StatusMessage"] = "Policies updated successfully";
|
TempData["StatusMessage"] = "Policies updated successfully";
|
||||||
return RedirectToAction(nameof(Policies));
|
return RedirectToAction(nameof(Policies));
|
||||||
@@ -555,6 +575,22 @@ namespace BTCPayServer.Controllers
|
|||||||
return View(result);
|
return View(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GetAppSelectList()
|
||||||
|
{
|
||||||
|
// load display app dropdown
|
||||||
|
using (var ctx = _ContextFactory.CreateContext())
|
||||||
|
{
|
||||||
|
var userId = _UserManager.GetUserId(base.User);
|
||||||
|
var selectList = await ctx.Users.Where(user => user.Id == userId)
|
||||||
|
.SelectMany(s => s.UserStores)
|
||||||
|
.Select(s => s.StoreData)
|
||||||
|
.SelectMany(s => s.Apps)
|
||||||
|
.Select(a => new SelectListItem($"{a.AppType} - {a.Name}", a.Id)).ToListAsync();
|
||||||
|
selectList.Insert(0, new SelectListItem("(None)", null));
|
||||||
|
ViewBag.AppsList = selectList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryParseAsExternalService(TorService torService, out ExternalService externalService)
|
private static bool TryParseAsExternalService(TorService torService, out ExternalService externalService)
|
||||||
{
|
{
|
||||||
externalService = null;
|
externalService = null;
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ namespace BTCPayServer.HostedServices
|
|||||||
public AppType? RootAppType { get; set; }
|
public AppType? RootAppType { get; set; }
|
||||||
public string RootAppId { get; set; }
|
public string RootAppId { get; set; }
|
||||||
|
|
||||||
|
public List<PoliciesSettings.DomainToAppMappingItem> DomainToAppMapping { get; set; }
|
||||||
|
|
||||||
internal void Update(PoliciesSettings data)
|
internal void Update(PoliciesSettings data)
|
||||||
{
|
{
|
||||||
ShowRegister = !data.LockSubscription;
|
ShowRegister = !data.LockSubscription;
|
||||||
@@ -59,6 +61,7 @@ namespace BTCPayServer.HostedServices
|
|||||||
|
|
||||||
RootAppType = data.RootAppType;
|
RootAppType = data.RootAppType;
|
||||||
RootAppId = data.RootAppId;
|
RootAppId = data.RootAppId;
|
||||||
|
DomainToAppMapping = data.DomainToAppMapping;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Services.Apps;
|
using BTCPayServer.Services.Apps;
|
||||||
|
using BTCPayServer.Validation;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace BTCPayServer.Services
|
namespace BTCPayServer.Services
|
||||||
@@ -23,7 +24,16 @@ namespace BTCPayServer.Services
|
|||||||
|
|
||||||
[Display(Name = "Display app on website root")]
|
[Display(Name = "Display app on website root")]
|
||||||
public string RootAppId { get; set; }
|
public string RootAppId { get; set; }
|
||||||
|
|
||||||
public AppType? RootAppType { get; set; }
|
public AppType? RootAppType { get; set; }
|
||||||
|
|
||||||
|
public List<DomainToAppMappingItem> DomainToAppMapping { get; set; } = new List<DomainToAppMappingItem>();
|
||||||
|
|
||||||
|
public class DomainToAppMappingItem
|
||||||
|
{
|
||||||
|
[Display(Name = "Domain")][Required][HostName] public string Domain { get; set; }
|
||||||
|
[Display(Name = "App")][Required] public string AppId { get; set; }
|
||||||
|
|
||||||
|
public AppType AppType { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
BTCPayServer/Validation/HostNameAttribute.cs
Normal file
23
BTCPayServer/Validation/HostNameAttribute.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Validation
|
||||||
|
{
|
||||||
|
//from http://stackoverflow.com/questions/967516/ddg#967610
|
||||||
|
public class HostNameAttribute : ValidationAttribute
|
||||||
|
{
|
||||||
|
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
|
||||||
|
{
|
||||||
|
var str = value == null ? null : Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||||
|
var valid = string.IsNullOrWhiteSpace(str) || Uri.CheckHostName(str) != UriHostNameType.Unknown;
|
||||||
|
|
||||||
|
if (!valid)
|
||||||
|
{
|
||||||
|
return new ValidationResult(ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,39 +4,95 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
<partial name="_StatusMessage" for="@TempData["StatusMessage"]"/>
|
||||||
@if (!this.ViewContext.ModelState.IsValid)
|
@if (!this.ViewContext.ModelState.IsValid)
|
||||||
{
|
{
|
||||||
<div class="row">
|
<div asp-validation-summary="All" class="text-danger"></div>
|
||||||
<div class="col-lg-6">
|
|
||||||
<div asp-validation-summary="All" class="text-danger"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-6">
|
<form method="post">
|
||||||
<form method="post">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label asp-for="RequiresConfirmedEmail"></label>
|
||||||
<label asp-for="RequiresConfirmedEmail"></label>
|
<input asp-for="RequiresConfirmedEmail" type="checkbox" class="form-check-inline"/>
|
||||||
<input asp-for="RequiresConfirmedEmail" type="checkbox" class="form-check-inline" />
|
<span asp-validation-for="RequiresConfirmedEmail" class="text-danger"></span>
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label asp-for="LockSubscription"></label>
|
|
||||||
<input asp-for="LockSubscription" type="checkbox" class="form-check-inline" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label asp-for="DiscourageSearchEngines"></label>
|
|
||||||
<input asp-for="DiscourageSearchEngines" type="checkbox" class="form-check-inline" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label asp-for="RootAppId"></label>
|
|
||||||
<select asp-for="RootAppId" asp-items="ViewBag.AppsList" class="form-control"></select>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
|
<label asp-for="LockSubscription"></label>
|
||||||
|
<input asp-for="LockSubscription" type="checkbox" class="form-check-inline"/>
|
||||||
|
<span asp-validation-for="LockSubscription" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="DiscourageSearchEngines"></label>
|
||||||
|
<input asp-for="DiscourageSearchEngines" type="checkbox" class="form-check-inline"/>
|
||||||
|
<span asp-validation-for="DiscourageSearchEngines" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="RootAppId"></label>
|
||||||
|
<select asp-for="RootAppId" asp-items="@(new SelectList(ViewBag.AppsList, nameof(SelectListItem.Value), nameof(SelectListItem.Text), Model.RootAppId))" class="form-control"></select>
|
||||||
|
@if (!Model.DomainToAppMapping.Any())
|
||||||
|
{
|
||||||
|
<button type="submit" name="command" value="add-domain" class="btn btn-link"> Map specific domains to specific apps</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.DomainToAppMapping.Any())
|
||||||
|
{
|
||||||
|
<div class="list-group mb-2">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<h5 class="mb-1">
|
||||||
|
Domain to app mapping
|
||||||
|
<button type="submit" name="command" value="add-domain" class="ml-1 btn btn-secondary btn-sm ">Add domain mapping </button>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
@for (var index = 0; index < Model.DomainToAppMapping.Count; index++)
|
||||||
|
{
|
||||||
|
<div class="list-group-item p-0 pl-lg-2">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-md-12 col-lg-10 py-2 ">
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="DomainToAppMapping[index].Domain" class="control-label"></label>
|
||||||
|
<input asp-for="DomainToAppMapping[index].Domain" class="form-control"/>
|
||||||
|
<span asp-validation-for="DomainToAppMapping[index].Domain" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="DomainToAppMapping[index].AppId"></label>
|
||||||
|
<select asp-for="DomainToAppMapping[index].AppId"
|
||||||
|
asp-items="@(new SelectList(ViewBag.AppsList,
|
||||||
|
nameof(SelectListItem.Value),
|
||||||
|
nameof(SelectListItem.Text),
|
||||||
|
Model.DomainToAppMapping[index].AppId))"
|
||||||
|
class="form-control">
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<span asp-validation-for="DomainToAppMapping[index].AppId" class="text-danger"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-12 col-md-12 col-lg-2 pull-right">
|
||||||
|
<button type="submit" title="Remove domain mapping" name="command" value="@($"remove-domain:{index}")"
|
||||||
|
class="d-block d-lg-none d-xl-none btn btn-danger mb-2 ml-2">
|
||||||
|
Remove Destination
|
||||||
|
</button>
|
||||||
|
<button type="submit" title="Remove domain mapping" name="command" value="@($"remove-domain:{index}")"
|
||||||
|
class="d-none d-lg-block remove-domain-btn text-decoration-none h-100 align-middle btn text-danger btn-link fa fa-times rounded-0 pull-right">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<button type="submit" class="btn btn-primary" name="command" value="Save">Save</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
@section Scripts {
|
@section Scripts {
|
||||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
<style>
|
||||||
|
.remove-domain-btn{
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.remove-domain-btn:hover{
|
||||||
|
background-color: #CCCCCC;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user