From 7a03f814850cba4179f8dc21087bd0ce4dcb1e59 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 14 Jan 2026 15:23:48 +0800 Subject: [PATCH 1/6] Separate ServicePulse Core from host --- .../ConstantsFile.cs | 0 .../ServicePulse.Core.csproj | 24 +++++++++++ .../ServicePulseExtensions.cs | 40 +++++++++++++++++++ .../Settings.cs | 2 +- src/ServicePulse.sln | 10 ++++- src/ServicePulse/Program.cs | 19 +-------- src/ServicePulse/ServicePulse.csproj | 6 +-- 7 files changed, 77 insertions(+), 24 deletions(-) rename src/{ServicePulse => ServicePulse.Core}/ConstantsFile.cs (100%) create mode 100644 src/ServicePulse.Core/ServicePulse.Core.csproj create mode 100644 src/ServicePulse.Core/ServicePulseExtensions.cs rename src/{ServicePulse => ServicePulse.Core}/Settings.cs (99%) diff --git a/src/ServicePulse/ConstantsFile.cs b/src/ServicePulse.Core/ConstantsFile.cs similarity index 100% rename from src/ServicePulse/ConstantsFile.cs rename to src/ServicePulse.Core/ConstantsFile.cs diff --git a/src/ServicePulse.Core/ServicePulse.Core.csproj b/src/ServicePulse.Core/ServicePulse.Core.csproj new file mode 100644 index 0000000000..722e6b9136 --- /dev/null +++ b/src/ServicePulse.Core/ServicePulse.Core.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + true + false + + + + + + + + + + + + + + + + diff --git a/src/ServicePulse.Core/ServicePulseExtensions.cs b/src/ServicePulse.Core/ServicePulseExtensions.cs new file mode 100644 index 0000000000..c80b66adeb --- /dev/null +++ b/src/ServicePulse.Core/ServicePulseExtensions.cs @@ -0,0 +1,40 @@ +namespace ServicePulse; + +using System.Net.Mime; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.FileProviders; + +public static class ServicePulseExtensions +{ + public static IApplicationBuilder UseServicePulse(this IApplicationBuilder app) + { + var embeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseExtensions).Assembly, "wwwroot"); + + return app + .UseDefaultFiles(new DefaultFilesOptions + { + FileProvider = embeddedFileProvider + }) + .UseStaticFiles(new StaticFileOptions + { + FileProvider = embeddedFileProvider + }); + + } + + public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRouteBuilder app, Settings settings) + { + var constantsFile = ConstantsFile.GetContent(settings); + + app.MapGet("/js/app.constants.js", (HttpContext context) => + { + context.Response.ContentType = MediaTypeNames.Text.JavaScript; + return constantsFile; + }); + + + return app; + } +} \ No newline at end of file diff --git a/src/ServicePulse/Settings.cs b/src/ServicePulse.Core/Settings.cs similarity index 99% rename from src/ServicePulse/Settings.cs rename to src/ServicePulse.Core/Settings.cs index 51717b5cbe..91d7d8bfc1 100644 --- a/src/ServicePulse/Settings.cs +++ b/src/ServicePulse.Core/Settings.cs @@ -2,7 +2,7 @@ using System.Text.Json; -class Settings +public class Settings { public required Uri ServiceControlUri { get; init; } diff --git a/src/ServicePulse.sln b/src/ServicePulse.sln index 2d35b6f089..17984d80a0 100644 --- a/src/ServicePulse.sln +++ b/src/ServicePulse.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32319.34 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServicePulse.Host", "ServicePulse.Host\ServicePulse.Host.csproj", "{D120B791-BD1B-4E06-B4E1-69801A73209B}" EndProject @@ -36,6 +36,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePulse", "ServicePulse\ServicePulse.csproj", "{084808CF-4B93-4097-BFA1-2604AA7B4594}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePulse.Core", "ServicePulse.Core\ServicePulse.Core.csproj", "{51B99A95-FD20-4B5F-A460-0A1D589BE9F9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -88,6 +90,10 @@ Global {084808CF-4B93-4097-BFA1-2604AA7B4594}.Debug|Any CPU.Build.0 = Debug|Any CPU {084808CF-4B93-4097-BFA1-2604AA7B4594}.Release|Any CPU.ActiveCfg = Release|Any CPU {084808CF-4B93-4097-BFA1-2604AA7B4594}.Release|Any CPU.Build.0 = Release|Any CPU + {51B99A95-FD20-4B5F-A460-0A1D589BE9F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51B99A95-FD20-4B5F-A460-0A1D589BE9F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51B99A95-FD20-4B5F-A460-0A1D589BE9F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51B99A95-FD20-4B5F-A460-0A1D589BE9F9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ServicePulse/Program.cs b/src/ServicePulse/Program.cs index a4c304be50..cd0c1a230e 100644 --- a/src/ServicePulse/Program.cs +++ b/src/ServicePulse/Program.cs @@ -1,5 +1,3 @@ -using System.Net.Mime; -using Microsoft.Extensions.FileProviders; using ServicePulse; var builder = WebApplication.CreateBuilder(args); @@ -14,26 +12,13 @@ var app = builder.Build(); -var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly, "wwwroot"); -var fileProvider = new CompositeFileProvider(builder.Environment.WebRootFileProvider, manifestEmbeddedFileProvider); - -var defaultFilesOptions = new DefaultFilesOptions { FileProvider = fileProvider }; -app.UseDefaultFiles(defaultFilesOptions); - -var staticFileOptions = new StaticFileOptions { FileProvider = fileProvider }; -app.UseStaticFiles(staticFileOptions); +app.UseServicePulse(builder.Environment.ContentRootFileProvider); if (settings.EnableReverseProxy) { app.MapReverseProxy(); } -var constantsFile = ConstantsFile.GetContent(settings); - -app.MapGet("/js/app.constants.js", (HttpContext context) => -{ - context.Response.ContentType = MediaTypeNames.Text.JavaScript; - return constantsFile; -}); +app.MapServicePulseConstants(settings); app.Run(); diff --git a/src/ServicePulse/ServicePulse.csproj b/src/ServicePulse/ServicePulse.csproj index bd2c0b7f43..36ee65b9ec 100644 --- a/src/ServicePulse/ServicePulse.csproj +++ b/src/ServicePulse/ServicePulse.csproj @@ -1,21 +1,19 @@ - + net8.0 enable enable - true false - - + From f58d61e626ab13f03c5a76c25a8236377abe5c80 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 14 Jan 2026 16:27:18 +0800 Subject: [PATCH 2/6] Split host setting from app setting --- src/ServicePulse.Core/ConstantsFile.cs | 54 ----------------- .../ServicePulseExtensions.cs | 16 ++--- .../{Settings.cs => ServicePulseSettings.cs} | 58 ++++++++++++------- src/ServicePulse/Program.cs | 18 ++++-- src/ServicePulse/ReverseProxy.cs | 8 +-- src/ServicePulse/ServicePulseHostSettings.cs | 21 +++++++ 6 files changed, 84 insertions(+), 91 deletions(-) delete mode 100644 src/ServicePulse.Core/ConstantsFile.cs rename src/ServicePulse.Core/{Settings.cs => ServicePulseSettings.cs} (62%) create mode 100644 src/ServicePulse/ServicePulseHostSettings.cs diff --git a/src/ServicePulse.Core/ConstantsFile.cs b/src/ServicePulse.Core/ConstantsFile.cs deleted file mode 100644 index 9ab55df879..0000000000 --- a/src/ServicePulse.Core/ConstantsFile.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace ServicePulse; - -using System.Reflection; - -class ConstantsFile -{ - public static string GetContent(Settings settings) - { - var version = GetVersionInformation(); - - string serviceControlUrl; - string monitoringUrl; - - if (settings.EnableReverseProxy) - { - serviceControlUrl = "/api/"; - monitoringUrl = settings.MonitoringUri == null ? "!" : "/monitoring-api/"; - } - else - { - serviceControlUrl = settings.ServiceControlUri.ToString(); - monitoringUrl = settings.MonitoringUri?.ToString() ?? "!"; - } - - var constantsFile = $$""" -window.defaultConfig = { - default_route: '{{settings.DefaultRoute}}', - version: '{{version}}', - service_control_url: '{{serviceControlUrl}}', - monitoring_urls: ['{{monitoringUrl}}'], - showPendingRetry: {{(settings.ShowPendingRetry ? "true" : "false")}}, -} -"""; - - return constantsFile; - } - - static string GetVersionInformation() - { - var majorMinorPatch = "0.0.0"; - - var attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(); - - foreach (var attribute in attributes) - { - if (attribute.Key == "MajorMinorPatch") - { - majorMinorPatch = attribute.Value ?? "0.0.0"; - } - } - - return majorMinorPatch; - } -} diff --git a/src/ServicePulse.Core/ServicePulseExtensions.cs b/src/ServicePulse.Core/ServicePulseExtensions.cs index c80b66adeb..953f1bd753 100644 --- a/src/ServicePulse.Core/ServicePulseExtensions.cs +++ b/src/ServicePulse.Core/ServicePulseExtensions.cs @@ -8,25 +8,28 @@ public static class ServicePulseExtensions { - public static IApplicationBuilder UseServicePulse(this IApplicationBuilder app) + public static IApplicationBuilder UseServicePulse(this IApplicationBuilder app, IFileProvider? overrideFileProvider = null) { var embeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseExtensions).Assembly, "wwwroot"); + IFileProvider fileProvider = overrideFileProvider is null + ? embeddedFileProvider + : new CompositeFileProvider(overrideFileProvider, embeddedFileProvider); + return app .UseDefaultFiles(new DefaultFilesOptions { - FileProvider = embeddedFileProvider + FileProvider = fileProvider }) .UseStaticFiles(new StaticFileOptions { - FileProvider = embeddedFileProvider + FileProvider = fileProvider }); - } - public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRouteBuilder app, Settings settings) + public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRouteBuilder app, ServicePulseSettings constants) { - var constantsFile = ConstantsFile.GetContent(settings); + var constantsFile = constants.GetConstantsFileContents(); app.MapGet("/js/app.constants.js", (HttpContext context) => { @@ -34,7 +37,6 @@ public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRoute return constantsFile; }); - return app; } } \ No newline at end of file diff --git a/src/ServicePulse.Core/Settings.cs b/src/ServicePulse.Core/ServicePulseSettings.cs similarity index 62% rename from src/ServicePulse.Core/Settings.cs rename to src/ServicePulse.Core/ServicePulseSettings.cs index 91d7d8bfc1..def63a0213 100644 --- a/src/ServicePulse.Core/Settings.cs +++ b/src/ServicePulse.Core/ServicePulseSettings.cs @@ -1,20 +1,16 @@ namespace ServicePulse; +using System.Reflection; using System.Text.Json; -public class Settings +public record ServicePulseSettings { - public required Uri ServiceControlUri { get; init; } - - public required Uri? MonitoringUri { get; init; } - public required string DefaultRoute { get; init; } - + public required string ServiceControlUrl { get; init; } + public required string? MonitoringUrl { get; init; } public required bool ShowPendingRetry { get; init; } - public required bool EnableReverseProxy { get; init; } - - public static Settings GetFromEnvironmentVariables() + public static ServicePulseSettings GetFromEnvironmentVariables() { var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/"; @@ -43,21 +39,30 @@ public static Settings GetFromEnvironmentVariables() var showPendingRetryValue = Environment.GetEnvironmentVariable("SHOW_PENDING_RETRY"); bool.TryParse(showPendingRetryValue, out var showPendingRetry); - var enableReverseProxyValue = Environment.GetEnvironmentVariable("ENABLE_REVERSE_PROXY"); + return new() + { + ServiceControlUrl = serviceControlUri.ToString(), + MonitoringUrl = monitoringUri?.ToString(), + DefaultRoute = defaultRoute, + ShowPendingRetry = showPendingRetry + }; + } + + static string GetVersionInformation() + { + var majorMinorPatch = "0.0.0"; + + var attributes = typeof(ServicePulseSettings).GetCustomAttributes(); - if (!bool.TryParse(enableReverseProxyValue, out var enableReverseProxy)) + foreach (var attribute in attributes) { - enableReverseProxy = true; + if (attribute.Key == "MajorMinorPatch") + { + majorMinorPatch = attribute.Value ?? "0.0.0"; + } } - return new Settings - { - ServiceControlUri = serviceControlUri, - MonitoringUri = monitoringUri, - DefaultRoute = defaultRoute, - ShowPendingRetry = showPendingRetry, - EnableReverseProxy = enableReverseProxy - }; + return majorMinorPatch; } static string? ParseLegacyMonitoringValue(string? value) @@ -95,4 +100,15 @@ class MonitoringUrls { public string[] Addresses { get; set; } = []; } -} + + internal string GetConstantsFileContents() + => $$""" + window.defaultConfig = { + default_route: '{{DefaultRoute}}', + version: '{{GetVersionInformation()}}', + service_control_url: '{{ServiceControlUrl}}', + monitoring_urls: ['{{MonitoringUrl ?? "!"}}'], + showPendingRetry: {{(ShowPendingRetry ? "true" : "false")}}, + } + """; +} \ No newline at end of file diff --git a/src/ServicePulse/Program.cs b/src/ServicePulse/Program.cs index cd0c1a230e..72599849f1 100644 --- a/src/ServicePulse/Program.cs +++ b/src/ServicePulse/Program.cs @@ -2,23 +2,31 @@ var builder = WebApplication.CreateBuilder(args); -var settings = Settings.GetFromEnvironmentVariables(); +var hostSettings = ServicePulseHostSettings.GetFromEnvironmentVariables(); +var servicePulseSettings = ServicePulseSettings.GetFromEnvironmentVariables(); -if (settings.EnableReverseProxy) +if (hostSettings.EnableReverseProxy) { - var (routes, clusters) = ReverseProxy.GetConfiguration(settings); + var (routes, clusters) = ReverseProxy.GetConfiguration(servicePulseSettings); builder.Services.AddReverseProxy().LoadFromMemory(routes, clusters); + servicePulseSettings = servicePulseSettings with + { + ServiceControlUrl = "/api/", + MonitoringUrl = servicePulseSettings.MonitoringUrl is not null + ? "/monitoring-api/" + : null + }; } var app = builder.Build(); app.UseServicePulse(builder.Environment.ContentRootFileProvider); -if (settings.EnableReverseProxy) +if (hostSettings.EnableReverseProxy) { app.MapReverseProxy(); } -app.MapServicePulseConstants(settings); +app.MapServicePulseConstants(servicePulseSettings); app.Run(); diff --git a/src/ServicePulse/ReverseProxy.cs b/src/ServicePulse/ReverseProxy.cs index 90ccc591d9..ceee532117 100644 --- a/src/ServicePulse/ReverseProxy.cs +++ b/src/ServicePulse/ReverseProxy.cs @@ -5,7 +5,7 @@ static class ReverseProxy { - public static (List routes, List clusters) GetConfiguration(Settings settings) + public static (List routes, List clusters) GetConfiguration(ServicePulseSettings settings) { var routes = new List(); var clusters = new List(); @@ -15,7 +15,7 @@ public static (List routes, List clusters) GetConfig ClusterId = "serviceControlInstance", Destinations = new Dictionary { - { "instance", new DestinationConfig { Address = settings.ServiceControlUri.ToString() } } + { "instance", new DestinationConfig { Address = settings.ServiceControlUrl } } } }; var serviceControlRoute = new RouteConfig @@ -31,14 +31,14 @@ public static (List routes, List clusters) GetConfig clusters.Add(serviceControlInstance); routes.Add(serviceControlRoute); - if (settings.MonitoringUri != null) + if (settings.MonitoringUrl != null) { var monitoringInstance = new ClusterConfig { ClusterId = "monitoringInstance", Destinations = new Dictionary { - { "instance", new DestinationConfig { Address = settings.MonitoringUri.ToString() } } + { "instance", new DestinationConfig { Address = settings.MonitoringUrl } } } }; diff --git a/src/ServicePulse/ServicePulseHostSettings.cs b/src/ServicePulse/ServicePulseHostSettings.cs new file mode 100644 index 0000000000..840e43f81e --- /dev/null +++ b/src/ServicePulse/ServicePulseHostSettings.cs @@ -0,0 +1,21 @@ +namespace ServicePulse; + +class ServicePulseHostSettings +{ + public required bool EnableReverseProxy { get; init; } + + public static ServicePulseHostSettings GetFromEnvironmentVariables() + { + var enableReverseProxyValue = Environment.GetEnvironmentVariable("ENABLE_REVERSE_PROXY"); + + if (!bool.TryParse(enableReverseProxyValue, out var enableReverseProxy)) + { + enableReverseProxy = true; + } + + return new ServicePulseHostSettings + { + EnableReverseProxy = enableReverseProxy + }; + } +} \ No newline at end of file From 81d46be9b568807e964602ea11b793c767d925f7 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 14 Jan 2026 16:48:30 +0800 Subject: [PATCH 3/6] Version cannot be overridden --- .../ServicePulseExtensions.cs | 32 +++++++++++++++++-- src/ServicePulse.Core/ServicePulseSettings.cs | 29 ----------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/ServicePulse.Core/ServicePulseExtensions.cs b/src/ServicePulse.Core/ServicePulseExtensions.cs index 953f1bd753..fa5eeaf9cb 100644 --- a/src/ServicePulse.Core/ServicePulseExtensions.cs +++ b/src/ServicePulse.Core/ServicePulseExtensions.cs @@ -1,6 +1,7 @@ namespace ServicePulse; using System.Net.Mime; +using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; @@ -27,9 +28,9 @@ public static IApplicationBuilder UseServicePulse(this IApplicationBuilder app, }); } - public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRouteBuilder app, ServicePulseSettings constants) + public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRouteBuilder app, ServicePulseSettings settings) { - var constantsFile = constants.GetConstantsFileContents(); + var constantsFile = GetConstantsFileContents(settings); app.MapGet("/js/app.constants.js", (HttpContext context) => { @@ -39,4 +40,31 @@ public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRoute return app; } + + static string GetConstantsFileContents(ServicePulseSettings settings) + => $$""" + window.defaultConfig = { + default_route: '{{settings.DefaultRoute}}', + version: '{{GetVersionInformation()}}', + service_control_url: '{{settings.ServiceControlUrl}}', + monitoring_urls: ['{{settings.MonitoringUrl ?? "!"}}'], + showPendingRetry: {{(settings.ShowPendingRetry ? "true" : "false")}}, + } + """; + static string GetVersionInformation() + { + var majorMinorPatch = "0.0.0"; + + var attributes = typeof(ServicePulseExtensions).Assembly.GetCustomAttributes(); + + foreach (var attribute in attributes) + { + if (attribute.Key == "MajorMinorPatch") + { + majorMinorPatch = attribute.Value ?? "0.0.0"; + } + } + + return majorMinorPatch; + } } \ No newline at end of file diff --git a/src/ServicePulse.Core/ServicePulseSettings.cs b/src/ServicePulse.Core/ServicePulseSettings.cs index def63a0213..b8ca373910 100644 --- a/src/ServicePulse.Core/ServicePulseSettings.cs +++ b/src/ServicePulse.Core/ServicePulseSettings.cs @@ -1,6 +1,5 @@ namespace ServicePulse; -using System.Reflection; using System.Text.Json; public record ServicePulseSettings @@ -48,23 +47,6 @@ public static ServicePulseSettings GetFromEnvironmentVariables() }; } - static string GetVersionInformation() - { - var majorMinorPatch = "0.0.0"; - - var attributes = typeof(ServicePulseSettings).GetCustomAttributes(); - - foreach (var attribute in attributes) - { - if (attribute.Key == "MajorMinorPatch") - { - majorMinorPatch = attribute.Value ?? "0.0.0"; - } - } - - return majorMinorPatch; - } - static string? ParseLegacyMonitoringValue(string? value) { if (value is null) @@ -100,15 +82,4 @@ class MonitoringUrls { public string[] Addresses { get; set; } = []; } - - internal string GetConstantsFileContents() - => $$""" - window.defaultConfig = { - default_route: '{{DefaultRoute}}', - version: '{{GetVersionInformation()}}', - service_control_url: '{{ServiceControlUrl}}', - monitoring_urls: ['{{MonitoringUrl ?? "!"}}'], - showPendingRetry: {{(ShowPendingRetry ? "true" : "false")}}, - } - """; } \ No newline at end of file From aaa433d0ce58a329c83a8ecaee5f2e4878e0aa1d Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 14 Jan 2026 16:54:39 +0800 Subject: [PATCH 4/6] Document public APIs --- .../ServicePulseExtensions.cs | 10 +++++++++ src/ServicePulse.Core/ServicePulseSettings.cs | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/ServicePulse.Core/ServicePulseExtensions.cs b/src/ServicePulse.Core/ServicePulseExtensions.cs index fa5eeaf9cb..058cc1104a 100644 --- a/src/ServicePulse.Core/ServicePulseExtensions.cs +++ b/src/ServicePulse.Core/ServicePulseExtensions.cs @@ -7,8 +7,14 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.FileProviders; +/// +/// Extension methods for hosting a ServicePulse instance. +/// public static class ServicePulseExtensions { + /// + /// Adds ServicePulse static file hosting to the application builder. + /// public static IApplicationBuilder UseServicePulse(this IApplicationBuilder app, IFileProvider? overrideFileProvider = null) { var embeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseExtensions).Assembly, "wwwroot"); @@ -28,6 +34,10 @@ public static IApplicationBuilder UseServicePulse(this IApplicationBuilder app, }); } + /// + /// Maps the ServicePulse constants endpoint. + /// Used to pass settings to the frontend application. + /// public static IEndpointRouteBuilder MapServicePulseConstants(this IEndpointRouteBuilder app, ServicePulseSettings settings) { var constantsFile = GetConstantsFileContents(settings); diff --git a/src/ServicePulse.Core/ServicePulseSettings.cs b/src/ServicePulse.Core/ServicePulseSettings.cs index b8ca373910..4ed984305b 100644 --- a/src/ServicePulse.Core/ServicePulseSettings.cs +++ b/src/ServicePulse.Core/ServicePulseSettings.cs @@ -2,13 +2,34 @@ using System.Text.Json; +/// +/// The runtime settings of a ServicePulse instance. +/// public record ServicePulseSettings { + /// + /// The default route to navigate to. + /// public required string DefaultRoute { get; init; } + + /// + /// The location of the ServiceControl API. + /// public required string ServiceControlUrl { get; init; } + + /// + /// The location of the ServiceControl Monitoring API. + /// public required string? MonitoringUrl { get; init; } + + /// + /// Show the pending retry tab. + /// public required bool ShowPendingRetry { get; init; } + /// + /// Loads the settings from environment variables. + /// public static ServicePulseSettings GetFromEnvironmentVariables() { var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/"; From b08917098a3fb9f4c9d12e84b21ab2d66c292a3a Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 12:41:39 +0800 Subject: [PATCH 5/6] Extract reverse proxy configuration --- src/ServicePulse/Program.cs | 10 +--------- src/ServicePulse/ReverseProxy.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/ServicePulse/Program.cs b/src/ServicePulse/Program.cs index 72599849f1..41ae439515 100644 --- a/src/ServicePulse/Program.cs +++ b/src/ServicePulse/Program.cs @@ -7,15 +7,7 @@ if (hostSettings.EnableReverseProxy) { - var (routes, clusters) = ReverseProxy.GetConfiguration(servicePulseSettings); - builder.Services.AddReverseProxy().LoadFromMemory(routes, clusters); - servicePulseSettings = servicePulseSettings with - { - ServiceControlUrl = "/api/", - MonitoringUrl = servicePulseSettings.MonitoringUrl is not null - ? "/monitoring-api/" - : null - }; + builder.Services.AddServicePulseReverseProxy(ref servicePulseSettings); } var app = builder.Build(); diff --git a/src/ServicePulse/ReverseProxy.cs b/src/ServicePulse/ReverseProxy.cs index ceee532117..019e0d1830 100644 --- a/src/ServicePulse/ReverseProxy.cs +++ b/src/ServicePulse/ReverseProxy.cs @@ -5,6 +5,20 @@ static class ReverseProxy { + public static void AddServicePulseReverseProxy(this IServiceCollection services, ref ServicePulseSettings settings) + { + var (routes, clusters) = GetConfiguration(settings); + services.AddReverseProxy().LoadFromMemory(routes, clusters); + settings = settings with + { + ServiceControlUrl = "/api/", + MonitoringUrl = settings.MonitoringUrl is not null + ? "/monitoring-api/" + : null + }; + + } + public static (List routes, List clusters) GetConfiguration(ServicePulseSettings settings) { var routes = new List(); From 275154722895d14171a1e30c062305334fd6a7ef Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 12:57:38 +0800 Subject: [PATCH 6/6] Refactor reverse proxy setup --- src/ServicePulse/ReverseProxy.cs | 69 ++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/src/ServicePulse/ReverseProxy.cs b/src/ServicePulse/ReverseProxy.cs index 019e0d1830..6cb4750f3b 100644 --- a/src/ServicePulse/ReverseProxy.cs +++ b/src/ServicePulse/ReverseProxy.cs @@ -7,23 +7,17 @@ static class ReverseProxy { public static void AddServicePulseReverseProxy(this IServiceCollection services, ref ServicePulseSettings settings) { - var (routes, clusters) = GetConfiguration(settings); - services.AddReverseProxy().LoadFromMemory(routes, clusters); - settings = settings with - { - ServiceControlUrl = "/api/", - MonitoringUrl = settings.MonitoringUrl is not null - ? "/monitoring-api/" - : null - }; + var routes = new List(); + var clusters = new List(); + AddServiceControl(ref settings, routes, clusters); + AddMonitoringIfEnabled(ref settings, routes, clusters); + + services.AddReverseProxy().LoadFromMemory(routes, clusters); } - public static (List routes, List clusters) GetConfiguration(ServicePulseSettings settings) + static void AddServiceControl(ref ServicePulseSettings settings, List routes, List clusters) { - var routes = new List(); - var clusters = new List(); - var serviceControlInstance = new ClusterConfig { ClusterId = "serviceControlInstance", @@ -45,28 +39,41 @@ public static (List routes, List clusters) GetConfig clusters.Add(serviceControlInstance); routes.Add(serviceControlRoute); - if (settings.MonitoringUrl != null) + settings = settings with { - var monitoringInstance = new ClusterConfig - { - ClusterId = "monitoringInstance", - Destinations = new Dictionary - { - { "instance", new DestinationConfig { Address = settings.MonitoringUrl } } - } - }; + ServiceControlUrl = "/api/" + }; + } - var monitoringRoute = new RouteConfig + static void AddMonitoringIfEnabled(ref ServicePulseSettings settings, List routes, List clusters) + { + if (settings.MonitoringUrl is null) + { + return; + } + + var monitoringInstance = new ClusterConfig + { + ClusterId = "monitoringInstance", + Destinations = new Dictionary { - RouteId = "monitoringRoute", - ClusterId = nameof(monitoringInstance), - Match = new RouteMatch { Path = "/monitoring-api/{**catch-all}" } - }.WithTransformPathRemovePrefix("/monitoring-api"); + { "instance", new DestinationConfig { Address = settings.MonitoringUrl } } + } + }; - clusters.Add(monitoringInstance); - routes.Add(monitoringRoute); - } + var monitoringRoute = new RouteConfig + { + RouteId = "monitoringRoute", + ClusterId = nameof(monitoringInstance), + Match = new RouteMatch { Path = "/monitoring-api/{**catch-all}" } + }.WithTransformPathRemovePrefix("/monitoring-api"); - return (routes, clusters); + clusters.Add(monitoringInstance); + routes.Add(monitoringRoute); + settings = settings with + { + MonitoringUrl = "/monitoring-api/" + }; } + }