make the version name match the actual name for FTP deployment

fix rare issue with summing session scores
copy font to expected wwwroot dir in debug mode so we get pretty icons when developing
upgrade some packages

pretty much reworked the entire server web config to support better validation and stuff.. not really a small fix

finish web configuration changes (I think)

finish up configuration changes and update shared library nuget
This commit is contained in:
RaidMax
2020-01-17 17:31:53 -06:00
parent 3a1cfba251
commit 697a752be0
38 changed files with 531 additions and 200 deletions

View File

@ -2,8 +2,11 @@
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Configuration.Attributes;
using SharedLibraryCore.Configuration.Validation;
using SharedLibraryCore.Interfaces;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using WebfrontCore.ViewModels;
@ -12,11 +15,17 @@ namespace WebfrontCore.Controllers
[Authorize]
public class ConfigurationController : BaseController
{
public ConfigurationController(IManager manager) : base (manager)
{
private readonly ApplicationConfigurationValidator _validator;
public ConfigurationController(IManager manager) : base(manager)
{
_validator = new ApplicationConfigurationValidator();
}
/// <summary>
/// Endpoint to get the current configuration view
/// </summary>
/// <returns></returns>
public IActionResult Edit()
{
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
@ -27,56 +36,132 @@ namespace WebfrontCore.Controllers
return View("Index", Manager.GetApplicationSettings().Configuration());
}
/// <summary>
/// Endpoint for the save action
/// </summary>
/// <param name="newConfiguration">bound configuration</param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> Edit(ApplicationConfiguration newConfiguration, bool addNewServer = false, bool shouldSave = false)
public async Task<IActionResult> Save(ApplicationConfiguration newConfiguration)
{
// todo: make this authorization middleware instead of these checks
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
{
return Unauthorized();
}
CleanConfiguration(newConfiguration);
var validationResult = _validator.Validate(newConfiguration);
if (validationResult.IsValid)
{
var currentConfiguration = Manager.GetApplicationSettings().Configuration();
CopyConfiguration(newConfiguration, currentConfiguration);
await Manager.GetApplicationSettings().Save();
return Ok(new { message = new[] { Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVED"] } });
}
else
{
return BadRequest(new { message = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVE_FAILED"], errors = new[] { validationResult.Errors.Select(_error => _error.ErrorMessage) } });
}
}
/// <summary>
/// Cleans the configuration by removing empty items from from the array
/// </summary>
/// <param name="newConfiguration"></param>
private void CleanConfiguration(ApplicationConfiguration newConfiguration)
{
void cleanProperties(object config)
{
foreach (var property in config.GetType()
.GetProperties().Where(_prop => _prop.CanWrite))
{
var newPropValue = property.GetValue(config);
if (newPropValue is ServerConfiguration[] serverConfig)
{
foreach (var c in serverConfig)
{
cleanProperties(c);
}
}
// this clears out any null or empty items in the string array
if (newPropValue is string[] configArray)
{
newPropValue = configArray.Where(_str => !string.IsNullOrWhiteSpace(_str)).ToArray();
}
property.SetValue(config, newPropValue);
}
}
cleanProperties(newConfiguration);
}
/// <summary>
/// Copies required config fields from new to old
/// </summary>
/// <param name="newConfiguration">Source config</param>
/// <param name="oldConfiguration">Destination config</param>
private void CopyConfiguration(ApplicationConfiguration newConfiguration, ApplicationConfiguration oldConfiguration)
{
foreach (var property in newConfiguration.GetType()
.GetProperties().Where(_prop => _prop.CanWrite))
{
var newPropValue = property.GetValue(newConfiguration);
bool isPropNullArray = property.PropertyType.IsArray && newPropValue == null;
// this prevents us from setting a null array as that could screw reading up
if (!ShouldIgnoreProperty(property) && !isPropNullArray)
{
property.SetValue(oldConfiguration, newPropValue);
}
}
}
/// <summary>
/// Generates the partial view for a new list item
/// </summary>
/// <param name="propertyName">name of the property the input element is generated for</param>
/// <param name="itemCount">how many items exist already</param>
/// <param name="serverIndex">if it's a server property, which one</param>
/// <returns></returns>
public IActionResult GetNewListItem(string propertyName, int itemCount, int serverIndex = -1)
{
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
{
return Unauthorized();
}
if (shouldSave)
// todo: maybe make this cleaner in the future
if (propertyName.StartsWith("Servers") && serverIndex < 0)
{
var currentConfiguration = Manager.GetApplicationSettings().Configuration();
var newConfigurationProperties = newConfiguration.GetType().GetProperties();
foreach (var property in currentConfiguration.GetType().GetProperties())
return PartialView("_ServerItem", new ApplicationConfiguration()
{
var newProp = newConfigurationProperties.First(_prop => _prop.Name == property.Name);
var newPropValue = newProp.GetValue(newConfiguration);
if (newPropValue != null && newProp.CanWrite)
{
property.SetValue(currentConfiguration, newPropValue);
}
}
await Manager.GetApplicationSettings().Save();
Servers = Enumerable.Repeat(new ServerConfiguration(), itemCount + 1).ToArray()
});
}
if (addNewServer)
var model = new BindingHelper()
{
newConfiguration.Servers.Add(new ServerConfiguration());
}
return View("Index", newConfiguration);
}
public IActionResult GetNewListItem(string propertyName, int itemCount)
{
if (Client.Level != SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
{
return Unauthorized();
}
var configInfo = new ConfigurationInfo()
{
NewItemCount = itemCount,
PropertyName = propertyName
Properties = propertyName.Split("."),
ItemIndex = itemCount,
ParentItemIndex = serverIndex
};
return PartialView("_ListItem", configInfo);
return PartialView("_ListItem", model);
}
/// <summary>
/// Indicates if the property should be ignored when cleaning/copying from one config to another
/// </summary>
/// <param name="info">property info of the current property</param>
/// <returns></returns>
private bool ShouldIgnoreProperty(PropertyInfo info) => (info.GetCustomAttributes(false)
.Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore))
.FirstOrDefault() as ConfigurationIgnore) != null;
}
}

View File

@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
@ -13,32 +13,39 @@ namespace WebfrontCore.Middleware
/// </summary>
internal sealed class IPWhitelist
{
private readonly List<byte[]> whitelistedIps;
private readonly RequestDelegate nextRequest;
private readonly byte[][] _whitelistedIps;
private readonly RequestDelegate _nextRequest;
private readonly ILogger _logger;
/// <summary>
/// constructor
/// </summary>
/// <param name="nextRequest"></param>
/// <param name="logger"></param>
/// <param name="whitelistedIps">list of textual ip addresses</param>
public IPWhitelist(RequestDelegate nextRequest, ILogger<IPWhitelist> logger, List<string> whitelistedIps)
public IPWhitelist(RequestDelegate nextRequest, ILogger logger, string[] whitelistedIps)
{
this.whitelistedIps = whitelistedIps.Select(_ip => System.Net.IPAddress.Parse(_ip).GetAddressBytes()).ToList();
this.nextRequest = nextRequest;
_whitelistedIps = whitelistedIps.Select(_ip => System.Net.IPAddress.Parse(_ip).GetAddressBytes()).ToArray();
_nextRequest = nextRequest;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
bool isAlllowed = whitelistedIps.Any(_ip => _ip.SequenceEqual(context.Connection.RemoteIpAddress.GetAddressBytes()));
bool isAlllowed = true;
if (_whitelistedIps.Length > 0)
{
isAlllowed = _whitelistedIps.Any(_ip => _ip.SequenceEqual(context.Connection.RemoteIpAddress.GetAddressBytes()));
}
if (isAlllowed)
{
await nextRequest.Invoke(context);
await _nextRequest.Invoke(context);
}
else
{
_logger.WriteInfo($"Blocking HTTP request from {context.Connection.RemoteIpAddress.ToString()}");
context.Abort();
}
}

View File

@ -96,7 +96,7 @@ namespace WebfrontCore
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IManager manager)
{
app.UseStatusCodePages(_context =>
{
@ -120,7 +120,7 @@ namespace WebfrontCore
if (Program.Manager.GetApplicationSettings().Configuration().EnableWebfrontConnectionWhitelist)
{
app.UseMiddleware<IPWhitelist>(Program.Manager.GetApplicationSettings().Configuration().WebfrontConnectionWhitelist);
app.UseMiddleware<IPWhitelist>(manager.GetLogger(0), manager.GetApplicationSettings().Configuration().WebfrontConnectionWhitelist);
}
app.UseStaticFiles();

View File

@ -0,0 +1,23 @@
namespace WebfrontCore.ViewModels
{
/// <summary>
/// Helper class that hold information to assist with binding lists of items
/// </summary>
public class BindingHelper
{
/// <summary>
/// Sequential property mapping items
/// </summary>
public string[] Properties { get; set; }
/// <summary>
/// Index in the array this new item lives
/// </summary>
public int ItemIndex { get; set; }
/// <summary>
/// Index in the array of the parent item
/// </summary>
public int ParentItemIndex { get; set; }
}
}

View File

@ -1,19 +0,0 @@
using SharedLibraryCore.Interfaces;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace WebfrontCore.ViewModels
{
public class ConfigurationInfo
{
public string PropertyName { get; set; }
public PropertyInfo PropertyInfo { get; set; }
public IList PropertyValue { get; set; }
public IBaseConfiguration Configuration { get; set; }
public int NewItemCount { get; set; }
}
}

View File

@ -1,4 +1,5 @@
@using SharedLibraryCore.Configuration.Attributes
@using SharedLibraryCore.Configuration;
@model SharedLibraryCore.Configuration.ApplicationConfiguration
@{
@ -39,7 +40,7 @@
<div class="col-12 text-white-50 ">
<h3 class="text-white">@ViewData["Title"]</h3>
<h5 class="mb-4">@noticeText</h5>
<form method="post">
<form id="configurationForm" asp-controller="Configuration" asp-action="Save" method="post">
@foreach (var property in properties)
{
if (shouldIgnore(property))
@ -49,22 +50,36 @@
string[] linkedPropertyNames = getLinkedPropertyName(property);
if (property.PropertyType.Name == typeof(System.Boolean).Name)
// bool type
if (property.PropertyType == typeof(bool))
{
<div class="form-group form-check bg-primary mb-0 pl-3 pr-3 p-2">
@Html.Editor(property.Name, linkedPropertyNames.Length > 0 ? new { htmlAttributes = new { @class= "has-related-content mb-0", data_related_content = string.Join(',', linkedPropertyNames.Select(_id => $"#{_id}_content")) } } : null)
@Html.Editor(property.Name, linkedPropertyNames.Length > 0 ? new { htmlAttributes = new { @class = "has-related-content mb-0", data_related_content = string.Join(',', linkedPropertyNames.Select(_id => $"#{_id}_content")) } } : null)
@Html.Label(property.Name, null, new { @class = "form-check-label ml-1" })
</div>
}
else if (property.PropertyType.Name.Contains("List"))
// array type
else if (property.PropertyType.IsArray)
{
if (hasLinkedParent(property))
// special type for server config, I don't like this but for now it's ok
@if (property.PropertyType.GetElementType() == typeof(ServerConfiguration))
{
<div id="@($"{property.Name}_content")" class="pl-3 pr-3 pt-2 pb-3 bg-dark">
@for (int i = 0; i < Model.Servers.Length; i++)
{
@Html.EditorFor(model => model.Servers[i]);
}
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-server-add-new">@addServerText</a>
</div>
}
else if (hasLinkedParent(property))
{
<div id="@($"{property.Name}_content")" class="@(linkedPropertyNames.Length == 0 ? "hide" : "hide") bg-dark pl-3 pr-3 pb-2">
@if (linkedPropertyNames.Length == 0)
{
@Html.Label(property.Name, null, new { @class = "mt-2" })
@Html.Label(property.Name, null, new { @class = "mt-2 d-block" })
}
@Html.Editor(property.Name, new { htmlAttributes = new { @class = $"form-group form-control bg-dark text-white-50 {(linkedPropertyNames.Length == 0 ? "mb-3" : "mb-0")}" } })
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-add-new">@addText</a>
@ -76,14 +91,7 @@
@Html.Label(property.Name, null, new { @class = "bg-primary pl-3 pr-3 p-2 mb-0 w-100" })
<div id="@($"{property.Name}_content")" class="pl-3 pr-3 pt-2 pb-3 bg-dark">
@Html.Editor(property.Name, new { htmlAttributes = new { @class = "form-control bg-dark text-white-50 mt-3 mb-3", placeholder = isOptional(property) ? optionalText : "" } })
@if (property.PropertyType.GenericTypeArguments[0].Name == "ServerConfiguration")
{
<button asp-action="Edit" asp-route-addNewServer="true" class="btn btn-primary">@addServerText</button>
}
else
{
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-add-new">@addText</a>
}
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-add-new">@addText</a>
</div>
}
}
@ -107,7 +115,7 @@
}
}
}
<button asp-controller="Configuration" asp-action="Edit" asp-route-shouldSave="true" class="btn btn-primary btn-block">@saveText</button>
<button class="btn btn-primary btn-block">@saveText</button>
</form>
</div>
</div>

View File

@ -1,3 +1,13 @@
@model WebfrontCore.ViewModels.ConfigurationInfo
@model WebfrontCore.ViewModels.BindingHelper
<input class="form-control bg-dark text-white-50 mb-2 text-box single-line" id="@($"{Model.PropertyName.Replace('[', '_').Replace(']', '_').Replace('.', '_')}_{Model.NewItemCount - 1}_")" name="@($"{Model.PropertyName}[{Model.NewItemCount- 1}]")" type="text" />
@if (Model.Properties.Length == 1)
{
<!-- we're working off that top level model -->
@Html.Editor($"{Model.Properties.Last()}[{Model.ItemIndex}]", new { htmlAttributes = new { @class = "form-control bg-dark text-white-50 mb-2 text-box single-line" } })
}
@if (Model.Properties.Length > 1)
{
<!-- we're working off child model -->
@Html.Editor($"{Model.Properties.First()}[{Model.ParentItemIndex}].{Model.Properties.Last()}[{Model.ItemIndex}]", new { htmlAttributes = new { @class = "form-control bg-dark text-white-50 mb-2 text-box single-line" } })
}

View File

@ -0,0 +1,10 @@
@model SharedLibraryCore.Configuration.ApplicationConfiguration
@{
int start = Model.Servers.Length - 1;
int end = start + 1;
}
<!-- this is an ugly hack that allows us to create new server layout with correct array indices -->
@for (int i = start; i < end; i++)
{
@Html.EditorFor(model => model.Servers[i]);
}

View File

@ -1,52 +1,59 @@
@model IList<SharedLibraryCore.Configuration.ServerConfiguration>
@model SharedLibraryCore.Configuration.ServerConfiguration
@{
string labelClass = "mb-2 mt-1";
string editorClass = "form-control bg-dark text-white-50 text-box single-line mb-2 mt-0";
string addText = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_ADD"];
string optionalText = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["COMMAND_HELP_OPTIONAL"];
int startAt = ViewBag.AddNew ?? false ? Model.Count - 1 : 0;
int i = 0;
}
@for (int i = startAt; i < Model.Count; i++)
{
<div class="h4 text-white mb-0">@Model[i].IPAddress:@Model[i].Port</div>
<div class="server-configuration-header">
<div class="d-flex h4">
<div class="text-white mb-0">@Model.IPAddress:@Model.Port</div>
<!--span class="oi oi-trash link-inverse ml-auto delete-server-button"></!--span>-->
</div>
<div class="border-bottom mb-3">
@Html.LabelFor(model => model[i].IPAddress, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].IPAddress, new { htmlAttributes = new { @class = editorClass } })
<label asp-for="@Model.IPAddress" class="@labelClass"></label>
<input asp-for="@Model.IPAddress" class="@editorClass" />
@Html.LabelFor(model => model[i].Port, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].Port, new { htmlAttributes = new { @class = editorClass } })
<label asp-for="@Model.Port" class="@labelClass"></label>
<input asp-for="@Model.Port" class="@editorClass" />
@Html.LabelFor(model => model[i].Password, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].Password, new { htmlAttributes = new { @class = editorClass } })
<label asp-for="@Model.Password" class="@labelClass"></label>
<input asp-for="@Model.Password" class="@editorClass" />
@Html.LabelFor(model => model[i].ManualLogPath, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].ManualLogPath, new { htmlAttributes = new { @class = editorClass, placeholder = optionalText } })
<label asp-for="@Model.ManualLogPath" class="@labelClass"></label>
<input asp-for="@Model.ManualLogPath" class="@editorClass" placeholder="@optionalText" />
@Html.LabelFor(model => model[i].GameLogServerUrl, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].GameLogServerUrl, new { htmlAttributes = new { @class = editorClass, placeholder = optionalText } })
<label asp-for="@Model.GameLogServerUrl" class="@labelClass"></label>
<input asp-for="@Model.GameLogServerUrl" class="@editorClass" placeholder="@optionalText" />
@Html.LabelFor(model => model[i].RConParserVersion, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].RConParserVersion, new { htmlAttributes = new { @class = editorClass } })
<label asp-for="@Model.RConParserVersion" class="@labelClass"></label>
<input asp-for="@Model.RConParserVersion" class="@editorClass" />
@Html.LabelFor(model => model[i].EventParserVersion, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].EventParserVersion, new { htmlAttributes = new { @class = editorClass } })
<label asp-for="@Model.EventParserVersion" class="@labelClass"></label>
<input asp-for="@Model.EventParserVersion" class="@editorClass" />
@Html.LabelFor(model => model[i].ReservedSlotNumber, null, new { @class = labelClass })
@Html.EditorFor(model => model[i].ReservedSlotNumber, new { htmlAttributes = new { @class = editorClass } })
<label asp-for="@Model.ReservedSlotNumber" class="@labelClass"></label>
<input asp-for="@Model.ReservedSlotNumber" class="@editorClass" />
<div>
@Html.LabelFor(model => model[i].Rules, null, new { @class = "bg-primary pl-3 pr-3 p-2 w-100 mt-3" })
@Html.EditorFor(model => model[i].Rules, new { htmlAttributes = new { @class = editorClass } })
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@($"Servers[{i}].Rules")" class="btn btn-primary configuration-add-new mt-2">@addText</a>
<label asp-for="@Model.Rules" class="bg-primary pl-3 pr-3 p-2 w-100 mt-3"></label>
@for(i = 0; i < Model.Rules.Length; i++)
{
<input asp-for="@Model.Rules[i]" class="@editorClass" />
}
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="Servers.Rules" class="btn btn-primary configuration-add-new mt-2">@addText</a>
</div>
<div class="mb-3">
@Html.LabelFor(model => model[i].AutoMessages, null, new { @class = "bg-primary pl-3 pr-3 p-2 w-100 mt-3" })
@Html.EditorFor(model => model[i].AutoMessages, new { htmlAttributes = new { @class = editorClass } })
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@($"Servers[{i}].Automessages")" class="btn btn-primary configuration-add-new mt-2">@addText</a>
<label asp-for="@Model.AutoMessages" class="bg-primary pl-3 pr-3 p-2 w-100 mt-3"></label>
@for(i = 0; i < Model.AutoMessages.Length; i++)
{
<input asp-for="@Model.AutoMessages[i]" class="@editorClass" />
}
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="Servers.Automessages" class="btn btn-primary configuration-add-new mt-2">@addText</a>
</div>
</div>
}
@*<button asp-controller="Configuration" asp-action="Edit" asp-route-addNewServer="true" class="btn btn-primary">@addServerText</button>*@
</div>

View File

@ -70,11 +70,7 @@
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" />
</ItemGroup>
<ItemGroup Condition="'$(CONFIG)'=='Debug'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
@ -98,6 +94,6 @@
</ProjectExtensions>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="if $(ConfigurationName) == Debug ( &#xD;&#xA;powershell -Command wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap.scss&#xD;&#xA;)" />
<Exec Command="if $(ConfigurationName) == Debug ( &#xD;&#xA;powershell -Command wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap.scss&#xD;&#xA;echo d | xcopy /f /y $(ProjectDir)wwwroot\lib\open-iconic\font\fonts $(ProjectDir)wwwroot\font\&#xD;&#xA;)" />
</Target>
</Project>

View File

@ -1,23 +1,76 @@
$(document).ready(function() {
$.each($('.has-related-content'), function (key, value) {
$.each($('.has-related-content'), function(key, value) {
value = $(value);
if (value.attr('checked') !== undefined && value.attr('checked').length > 0) {
$(value.data('related-content')).slideDown();
}
});
$('input:checkbox').change(function () {
$('input:checkbox').change(function() {
var isChecked = $(this).is(':checked');
isChecked ? $($(this).data('related-content')).slideDown() : $($(this).data('related-content')).slideUp();
});
$('.configuration-add-new').click(function (e) {
// this is used for regular simple form adds
$(document).on('click', '.configuration-add-new', function(e) {
e.preventDefault();
let parentElement = $(this).parent();
$.get($(this).attr('href') + '&itemCount=' + $(this).siblings().length, function (response) {
let parentElement = $(this).parent();
let label = $(this).siblings('label');
let forAttr = $(label).attr('for');
let match = /Servers_+([0-9+])_+.*/g.exec(forAttr);
let additionalData = '';
if (match !== null && match.length === 2) {
additionalData = '&serverIndex=' + match[1].toString();
}
$.get($(this).attr('href') + '&itemCount=' + $(this).siblings('input').length.toString() + additionalData, function (response) {
$(response).insertBefore(parentElement.children().last());
});
});
// this is used for server adds which are little more complex
$(document).on('click', '.configuration-server-add-new', function (e) {
e.preventDefault();
let parentElement = $(this).parent();
$.get($(this).attr('href') + '&itemCount=' + $('.server-configuration-header').length.toString(), function (response) {
$(response).insertBefore(parentElement.children().last());
});
});
// removes the server when clicking the delete button
$(document).on('click', '.delete-server-button', function (e) {
$(this).parents('.server-configuration-header').remove();
});
$('#configurationForm').submit(function (e) {
$.ajax({
data: $(this).serialize(),
type: $(this).attr('method'),
url: $(this).attr('action'),
complete: function(response) {
if (response.status === 200) {
$('#actionModal .modal-message').removeClass('text-danger');
$('#actionModal').data('should-refresh', true);
}
else {
$('#actionModal .modal-message').addClass('text-danger');
}
$('#actionModal .modal-body-content').html('');
let errors = '';
if (response.responseJSON.errors !== undefined) {
errors = response.responseJSON.errors[0].join('<br/>');
}
message = response.responseJSON.message;
$('#actionModal .modal-message').html(message + '<br/>' + errors);
$('#actionModal').modal();
$('#actionModal .modal-message').fadeIn('fast');
}
});
return false;
});
});