diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 7e49d6d805..1c237f8b61 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -61,6 +61,7 @@ + diff --git a/src/ServiceControl/Hosting/Commands/RunCommand.cs b/src/ServiceControl/Hosting/Commands/RunCommand.cs index db658857de..8cab478a20 100644 --- a/src/ServiceControl/Hosting/Commands/RunCommand.cs +++ b/src/ServiceControl/Hosting/Commands/RunCommand.cs @@ -1,13 +1,13 @@ namespace ServiceControl.Hosting.Commands { using System.Threading.Tasks; - using Infrastructure.WebApi; using Microsoft.AspNetCore.Builder; using NServiceBus; using Particular.ServiceControl; using Particular.ServiceControl.Hosting; using ServiceBus.Management.Infrastructure.Settings; using ServiceControl; + using ServiceControl.Infrastructure.WebApi; class RunCommand : AbstractCommand { @@ -25,6 +25,8 @@ public override async Task Execute(HostArguments args, Settings settings) var app = hostBuilder.Build(); app.UseServiceControl(); + app.UseServicePulse(); + await app.RunAsync(settings.RootUrl); } } diff --git a/src/ServiceControl/Infrastructure/WebApi/AppConstantsMiddleware.cs b/src/ServiceControl/Infrastructure/WebApi/AppConstantsMiddleware.cs new file mode 100644 index 0000000000..483c455961 --- /dev/null +++ b/src/ServiceControl/Infrastructure/WebApi/AppConstantsMiddleware.cs @@ -0,0 +1,47 @@ +namespace ServiceControl.Infrastructure.WebApi +{ + using System.Net.Mime; + using System.Text.Json; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + + class AppConstantsMiddleware + { + readonly RequestDelegate next; + readonly string content; + static AppConstantsMiddleware() => FileVersion = ServiceControlVersion.GetFileVersion(); + static readonly string FileVersion; + + public AppConstantsMiddleware(RequestDelegate next) + { + this.next = next; + + var settings = ServicePulseSettings.GetFromEnvironmentVariables(); + var constants = new + { + default_route = settings.DefaultRoute, + service_control_url = "api/", + monitoring_urls = new[] { settings.MonitoringUri.ToString() }, + showPendingRetry = settings.ShowPendingRetry, + version = FileVersion, + embedded = true + }; + var options = new JsonSerializerOptions { PropertyNamingPolicy = null }; + + content = $"window.defaultConfig = {JsonSerializer.Serialize(constants, options)}"; + } + + public async Task InvokeAsync(HttpContext context) + { + if (context.Request.Path.StartsWithSegments("/js/app.constants.js")) + { + context.Response.ContentType = MediaTypeNames.Text.JavaScript; + + await context.Response.WriteAsync(content); + return; + } + + await next(context); + } + } +} diff --git a/src/ServiceControl/Infrastructure/WebApi/ServicePulseSettings.cs b/src/ServiceControl/Infrastructure/WebApi/ServicePulseSettings.cs new file mode 100644 index 0000000000..63687480ab --- /dev/null +++ b/src/ServiceControl/Infrastructure/WebApi/ServicePulseSettings.cs @@ -0,0 +1,70 @@ +using System; +using System.Text.Json; + +class ServicePulseSettings +{ + public required Uri MonitoringUri { get; init; } + + public required string DefaultRoute { get; init; } + + public required bool ShowPendingRetry { get; init; } + + + public static ServicePulseSettings GetFromEnvironmentVariables() + { + var monitoringUrls = ParseLegacyMonitoringValue(Environment.GetEnvironmentVariable("MONITORING_URLS")); + var monitoringUrl = Environment.GetEnvironmentVariable("MONITORING_URL"); + + monitoringUrl ??= monitoringUrls; + monitoringUrl ??= "http://localhost:33633/"; + + var monitoringUri = monitoringUrl == "!" ? null : new Uri(monitoringUrl); + var defaultRoute = Environment.GetEnvironmentVariable("DEFAULT_ROUTE") ?? "/dashboard"; + + var showPendingRetryValue = Environment.GetEnvironmentVariable("SHOW_PENDING_RETRY"); + bool.TryParse(showPendingRetryValue, out var showPendingRetry); + + return new ServicePulseSettings + { + MonitoringUri = monitoringUri, + DefaultRoute = defaultRoute, + ShowPendingRetry = showPendingRetry, + }; + } + + static string ParseLegacyMonitoringValue(string value) + { + if (value is null) + { + return null; + } + + var cleanedValue = value.Replace('\'', '"'); + var json = $$"""{"Addresses":{{cleanedValue}}}"""; + + MonitoringUrls result; + + try + { + result = JsonSerializer.Deserialize(json); + } + catch (JsonException) + { + return null; + } + + var addresses = result?.Addresses; + + if (addresses is not null && addresses.Length > 0) + { + return addresses[0]; + } + + return null; + } + + class MonitoringUrls + { + public string[] Addresses { get; set; } = []; + } +} diff --git a/src/ServiceControl/ServiceControl.csproj b/src/ServiceControl/ServiceControl.csproj index 04f5956ccf..7d65a229be 100644 --- a/src/ServiceControl/ServiceControl.csproj +++ b/src/ServiceControl/ServiceControl.csproj @@ -32,6 +32,7 @@ + diff --git a/src/ServiceControl/WebApplicationExtensions.cs b/src/ServiceControl/WebApplicationExtensions.cs index dfa7511613..bbc35b5a29 100644 --- a/src/ServiceControl/WebApplicationExtensions.cs +++ b/src/ServiceControl/WebApplicationExtensions.cs @@ -1,13 +1,17 @@ namespace ServiceControl; +using System; +using System.IO; +using System.Reflection; using Infrastructure.SignalR; using Infrastructure.WebApi; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.FileProviders; public static class WebApplicationExtensions { - public static void UseServiceControl(this WebApplication app) + public static IApplicationBuilder UseServiceControl(this WebApplication app) { app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All }); app.UseResponseCompression(); @@ -16,5 +20,22 @@ public static void UseServiceControl(this WebApplication app) app.MapHub("/api/messagestream"); app.UseCors(); app.MapControllers(); + + return app; + } + + public static IApplicationBuilder UseServicePulse(this WebApplication app) + { + var servicePulsePath = Path.Combine(AppContext.BaseDirectory, "platform", "servicepulse", "ServicePulse.dll"); + var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(Assembly.LoadFrom(servicePulsePath), "wwwroot"); + var fileProvider = new CompositeFileProvider(app.Environment.WebRootFileProvider, manifestEmbeddedFileProvider); + var defaultFilesOptions = new DefaultFilesOptions { FileProvider = fileProvider }; + var staticFileOptions = new StaticFileOptions { FileProvider = fileProvider }; + + app.UseMiddleware() + .UseDefaultFiles(defaultFilesOptions) + .UseStaticFiles(staticFileOptions); + + return app; } } \ No newline at end of file