diff --git a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs index 7e450874f5..ec763dd955 100644 --- a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs @@ -30,6 +30,11 @@ namespace Tgstation.Server.Host.Controllers /// public const string ControlPanelRoute = "/app"; + /// + /// The route to the control panel channel .json. + /// + public const string ChannelJsonRoute = "channel.json"; + /// /// Header for forcing channel.json to be fetched. /// @@ -70,7 +75,7 @@ namespace Tgstation.Server.Host.Controllers /// Returns the . /// /// A with the . - [Route("channel.json")] + [Route(ChannelJsonRoute)] [HttpGet] public IActionResult GetChannelJson() { diff --git a/src/Tgstation.Server.Host/Controllers/RootController.cs b/src/Tgstation.Server.Host/Controllers/RootController.cs index 32a7987857..df363970bb 100644 --- a/src/Tgstation.Server.Host/Controllers/RootController.cs +++ b/src/Tgstation.Server.Host/Controllers/RootController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; @@ -61,6 +62,27 @@ namespace Tgstation.Server.Host.Controllers /// readonly ControlPanelConfiguration controlPanelConfiguration; + /// + /// Gets a giving the and action names for a given . + /// + /// An expression invoking the action on . + /// A containing the controller and action names. + static Tuple GetControlPanelActionLink(Expression> actionExpression) + { + var memberSelectorExpression = (MethodCallExpression)actionExpression.Body; + var method = memberSelectorExpression.Method; + + var controllerName = typeof(ControlPanelController).Name; + + const string ControllerSuffix = nameof(Controller); + if (controllerName.EndsWith(ControllerSuffix, StringComparison.Ordinal)) + controllerName = controllerName.Substring(0, controllerName.Length - ControllerSuffix.Length); + + var actionName = method.Name; + + return Tuple.Create(controllerName, actionName); + } + /// /// Initializes a new instance of the class. /// @@ -136,5 +158,19 @@ namespace Tgstation.Server.Host.Controllers return (IActionResult?)this.TryServeFile(hostEnvironment, logger, $"{logoFileName}.svg") ?? NotFound(); } + + /// + /// Workaround for the webpanel always expecting a trailing slash. + /// + /// A to the . + [HttpGet(ControlPanelController.ChannelJsonRoute)] + public RedirectToActionResult WebpanelChannelRedirect() + { + var actionTuple = GetControlPanelActionLink(controller => controller.GetChannelJson()); + + return RedirectToActionPermanent( + actionTuple.Item2, + actionTuple.Item1); + } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 702ab054da..176c946e05 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Frozen; using System.Collections.Generic; using System.Globalization; @@ -524,6 +524,7 @@ namespace Tgstation.Server.Host.Core RequestPath = ControlPanelController.ControlPanelRoute, EnableDefaultFiles = true, EnableDirectoryBrowsing = false, + RedirectToAppendTrailingSlash = false, }); } else