2021-09-16 17:27:40 -04:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2019-03-30 23:04:15 -04:00
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2019-12-02 16:52:36 -05:00
|
|
|
|
using SharedLibraryCore;
|
2019-04-11 21:43:05 -04:00
|
|
|
|
using SharedLibraryCore.Configuration;
|
2020-01-17 18:31:53 -05:00
|
|
|
|
using SharedLibraryCore.Configuration.Attributes;
|
|
|
|
|
using SharedLibraryCore.Configuration.Validation;
|
2019-12-02 16:52:36 -05:00
|
|
|
|
using SharedLibraryCore.Interfaces;
|
2019-04-14 11:55:05 -04:00
|
|
|
|
using System.Linq;
|
2020-01-17 18:31:53 -05:00
|
|
|
|
using System.Reflection;
|
2021-09-16 17:27:40 -04:00
|
|
|
|
using System.Text;
|
2019-04-14 11:55:05 -04:00
|
|
|
|
using System.Threading.Tasks;
|
2021-09-16 17:27:40 -04:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
2019-04-11 21:43:05 -04:00
|
|
|
|
using WebfrontCore.ViewModels;
|
2019-03-30 23:04:15 -04:00
|
|
|
|
|
|
|
|
|
namespace WebfrontCore.Controllers
|
|
|
|
|
{
|
2019-04-14 11:55:05 -04:00
|
|
|
|
[Authorize]
|
2019-03-30 23:04:15 -04:00
|
|
|
|
public class ConfigurationController : BaseController
|
|
|
|
|
{
|
2020-01-17 18:31:53 -05:00
|
|
|
|
private readonly ApplicationConfigurationValidator _validator;
|
2019-12-02 16:52:36 -05:00
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
public ConfigurationController(IManager manager) : base(manager)
|
|
|
|
|
{
|
|
|
|
|
_validator = new ApplicationConfigurationValidator();
|
2019-12-02 16:52:36 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Endpoint to get the current configuration view
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
2019-03-30 23:04:15 -04:00
|
|
|
|
public IActionResult Edit()
|
|
|
|
|
{
|
2019-07-27 09:18:49 -04:00
|
|
|
|
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
|
2019-04-16 12:32:42 -04:00
|
|
|
|
{
|
|
|
|
|
return Unauthorized();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-19 19:43:58 -04:00
|
|
|
|
return RedirectToAction("Files");
|
2019-03-30 23:04:15 -04:00
|
|
|
|
}
|
2019-04-11 21:43:05 -04:00
|
|
|
|
|
2021-09-16 17:27:40 -04:00
|
|
|
|
public async Task<IActionResult> Files()
|
|
|
|
|
{
|
|
|
|
|
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
|
|
|
|
|
{
|
|
|
|
|
return Unauthorized();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// todo: move this into a service a some point
|
|
|
|
|
var model = await Task.WhenAll(System.IO.Directory
|
|
|
|
|
.GetFiles(System.IO.Path.Join(Utilities.OperatingDirectory, "Configuration"))
|
|
|
|
|
.Where(file => file.EndsWith(".json", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
|
.Select(async fileName => new ConfigurationFileInfo
|
|
|
|
|
{
|
|
|
|
|
FileName = fileName.Split(System.IO.Path.DirectorySeparatorChar).Last(),
|
|
|
|
|
FileContent = await System.IO.File.ReadAllTextAsync(fileName)
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
return View(model);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
return Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpPatch("{Controller}/File/{fileName}")]
|
|
|
|
|
public async Task<IActionResult> PatchFiles([FromRoute] string fileName)
|
|
|
|
|
{
|
|
|
|
|
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
|
|
|
|
|
{
|
|
|
|
|
return Unauthorized();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fileName.EndsWith(".json"))
|
|
|
|
|
{
|
|
|
|
|
return BadRequest("File must be of json format.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using var reader = new StreamReader(Request.Body, Encoding.UTF8);
|
|
|
|
|
var content = await reader.ReadToEndAsync();
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(content))
|
|
|
|
|
{
|
|
|
|
|
return BadRequest("File content cannot be empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var file = JObject.Parse(content);
|
|
|
|
|
}
|
|
|
|
|
catch (JsonReaderException ex)
|
|
|
|
|
{
|
|
|
|
|
return BadRequest($"{fileName}: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var path = System.IO.Path.Join(Utilities.OperatingDirectory, "Configuration",
|
|
|
|
|
fileName.Replace($"{System.IO.Path.DirectorySeparatorChar}", ""));
|
|
|
|
|
|
|
|
|
|
// todo: move into a service at some point
|
|
|
|
|
if (!System.IO.File.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
return BadRequest($"{fileName} does not exist");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await System.IO.File.WriteAllTextAsync(path, content);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
return Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NoContent();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Endpoint for the save action
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="newConfiguration">bound configuration</param>
|
|
|
|
|
/// <returns></returns>
|
2019-04-11 21:43:05 -04:00
|
|
|
|
[HttpPost]
|
2020-01-17 18:31:53 -05:00
|
|
|
|
public async Task<IActionResult> Save(ApplicationConfiguration newConfiguration)
|
2019-04-11 21:43:05 -04:00
|
|
|
|
{
|
2020-01-17 18:31:53 -05:00
|
|
|
|
// todo: make this authorization middleware instead of these checks
|
2019-07-27 09:18:49 -04:00
|
|
|
|
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
|
2019-04-16 12:32:42 -04:00
|
|
|
|
{
|
|
|
|
|
return Unauthorized();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
CleanConfiguration(newConfiguration);
|
|
|
|
|
var validationResult = _validator.Validate(newConfiguration);
|
|
|
|
|
|
|
|
|
|
if (validationResult.IsValid)
|
2019-04-12 23:25:18 -04:00
|
|
|
|
{
|
2019-04-14 11:55:05 -04:00
|
|
|
|
var currentConfiguration = Manager.GetApplicationSettings().Configuration();
|
2020-01-17 18:31:53 -05:00
|
|
|
|
CopyConfiguration(newConfiguration, currentConfiguration);
|
|
|
|
|
await Manager.GetApplicationSettings().Save();
|
2021-09-16 17:27:40 -04:00
|
|
|
|
return Ok(new
|
|
|
|
|
{
|
|
|
|
|
message = new[] {Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVED"]}
|
|
|
|
|
});
|
2020-01-17 18:31:53 -05:00
|
|
|
|
}
|
2019-04-12 23:25:18 -04:00
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
else
|
|
|
|
|
{
|
2021-09-16 17:27:40 -04:00
|
|
|
|
return BadRequest(new
|
|
|
|
|
{
|
|
|
|
|
message = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVE_FAILED"],
|
|
|
|
|
errors = new[] {validationResult.Errors.Select(_error => _error.ErrorMessage)}
|
|
|
|
|
});
|
2020-01-17 18:31:53 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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()
|
2021-09-16 17:27:40 -04:00
|
|
|
|
.GetProperties().Where(_prop => _prop.CanWrite))
|
2019-04-12 23:25:18 -04:00
|
|
|
|
{
|
2020-01-17 18:31:53 -05:00
|
|
|
|
var newPropValue = property.GetValue(config);
|
2019-04-14 11:55:05 -04:00
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
if (newPropValue is ServerConfiguration[] serverConfig)
|
2019-04-14 11:55:05 -04:00
|
|
|
|
{
|
2020-01-17 18:31:53 -05:00
|
|
|
|
foreach (var c in serverConfig)
|
|
|
|
|
{
|
|
|
|
|
cleanProperties(c);
|
|
|
|
|
}
|
2019-04-14 11:55:05 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
// 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);
|
|
|
|
|
}
|
2019-04-12 23:25:18 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
cleanProperties(newConfiguration);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Copies required config fields from new to old
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="newConfiguration">Source config</param>
|
|
|
|
|
/// <param name="oldConfiguration">Destination config</param>
|
2021-09-16 17:27:40 -04:00
|
|
|
|
private void CopyConfiguration(ApplicationConfiguration newConfiguration,
|
|
|
|
|
ApplicationConfiguration oldConfiguration)
|
2020-01-17 18:31:53 -05:00
|
|
|
|
{
|
|
|
|
|
foreach (var property in newConfiguration.GetType()
|
|
|
|
|
.GetProperties().Where(_prop => _prop.CanWrite))
|
2019-04-14 11:55:05 -04:00
|
|
|
|
{
|
2020-01-17 18:31:53 -05:00
|
|
|
|
var newPropValue = property.GetValue(newConfiguration);
|
|
|
|
|
bool isPropNullArray = property.PropertyType.IsArray && newPropValue == null;
|
2019-04-14 11:55:05 -04:00
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
// this prevents us from setting a null array as that could screw reading up
|
|
|
|
|
if (!ShouldIgnoreProperty(property) && !isPropNullArray)
|
|
|
|
|
{
|
|
|
|
|
property.SetValue(oldConfiguration, newPropValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-11 21:43:05 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
/// <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)
|
2019-04-11 21:43:05 -04:00
|
|
|
|
{
|
2020-01-17 18:31:53 -05:00
|
|
|
|
if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
|
2019-04-16 12:32:42 -04:00
|
|
|
|
{
|
|
|
|
|
return Unauthorized();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
// todo: maybe make this cleaner in the future
|
|
|
|
|
if (propertyName.StartsWith("Servers") && serverIndex < 0)
|
2019-04-11 21:43:05 -04:00
|
|
|
|
{
|
2020-01-17 18:31:53 -05:00
|
|
|
|
return PartialView("_ServerItem", new ApplicationConfiguration()
|
|
|
|
|
{
|
|
|
|
|
Servers = Enumerable.Repeat(new ServerConfiguration(), itemCount + 1).ToArray()
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var model = new BindingHelper()
|
|
|
|
|
{
|
|
|
|
|
Properties = propertyName.Split("."),
|
|
|
|
|
ItemIndex = itemCount,
|
|
|
|
|
ParentItemIndex = serverIndex
|
2019-04-11 21:43:05 -04:00
|
|
|
|
};
|
|
|
|
|
|
2020-01-17 18:31:53 -05:00
|
|
|
|
return PartialView("_ListItem", model);
|
2019-04-11 21:43:05 -04:00
|
|
|
|
}
|
2020-01-17 18:31:53 -05:00
|
|
|
|
|
|
|
|
|
/// <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)
|
2021-09-16 17:27:40 -04:00
|
|
|
|
.Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore))
|
|
|
|
|
.FirstOrDefault() as ConfigurationIgnore) != null;
|
2019-03-30 23:04:15 -04:00
|
|
|
|
}
|
2022-04-19 19:43:58 -04:00
|
|
|
|
}
|