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