From 5fd4ba79f2ee4fdfc95c1865b709ffadfb1b8e7b Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 15:25:27 +0800 Subject: [PATCH 01/13] Split application settings and host settings --- src/ServicePulse/ConstantsFile.cs | 18 +- src/ServicePulse/Program.cs | 13 +- src/ServicePulse/ReverseProxy.cs | 10 +- src/ServicePulse/ServicePulseHostSettings.cs | 212 ++++++++++++++++++ src/ServicePulse/Settings.cs | 212 +----------------- .../WebApplicationBuilderExtensions.cs | 4 +- src/ServicePulse/WebApplicationExtensions.cs | 4 +- 7 files changed, 237 insertions(+), 236 deletions(-) create mode 100644 src/ServicePulse/ServicePulseHostSettings.cs diff --git a/src/ServicePulse/ConstantsFile.cs b/src/ServicePulse/ConstantsFile.cs index 9ab55df879..bf408f051e 100644 --- a/src/ServicePulse/ConstantsFile.cs +++ b/src/ServicePulse/ConstantsFile.cs @@ -8,26 +8,12 @@ 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}}'], + service_control_url: '{{settings.ServiceControlUrl}}', + monitoring_urls: ['{{settings.MonitoringUrl ?? "!"}}'], showPendingRetry: {{(settings.ShowPendingRetry ? "true" : "false")}}, } """; diff --git a/src/ServicePulse/Program.cs b/src/ServicePulse/Program.cs index 9b4ca85024..a6e2e193a3 100644 --- a/src/ServicePulse/Program.cs +++ b/src/ServicePulse/Program.cs @@ -5,23 +5,24 @@ var builder = WebApplication.CreateBuilder(args); var settings = Settings.GetFromEnvironmentVariables(); +var hostSettings = ServicePulseHostSettings.GetFromEnvironmentVariables(); // Configure Kestrel for HTTPS if enabled -builder.ConfigureHttps(settings); +builder.ConfigureHttps(hostSettings); -if (settings.EnableReverseProxy) +if (hostSettings.EnableReverseProxy) { - var (routes, clusters) = ReverseProxy.GetConfiguration(settings); + var (routes, clusters) = ReverseProxy.GetConfiguration(ref settings); builder.Services.AddReverseProxy().LoadFromMemory(routes, clusters); } var app = builder.Build(); // Forwarded headers must be first in the pipeline for correct scheme/host detection -app.UseForwardedHeaders(settings); +app.UseForwardedHeaders(hostSettings); // HTTPS middleware (HSTS and redirect) -app.UseHttpsConfiguration(settings); +app.UseHttpsConfiguration(hostSettings); var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly, "wwwroot"); var fileProvider = new CompositeFileProvider(builder.Environment.WebRootFileProvider, manifestEmbeddedFileProvider); @@ -32,7 +33,7 @@ var staticFileOptions = new StaticFileOptions { FileProvider = fileProvider }; app.UseStaticFiles(staticFileOptions); -if (settings.EnableReverseProxy) +if (hostSettings.EnableReverseProxy) { app.MapReverseProxy(); } diff --git a/src/ServicePulse/ReverseProxy.cs b/src/ServicePulse/ReverseProxy.cs index 90ccc591d9..72305c328d 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(ref Settings 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 @@ -30,15 +30,16 @@ public static (List routes, List clusters) GetConfig clusters.Add(serviceControlInstance); routes.Add(serviceControlRoute); + settings = settings with { ServiceControlUrl = "/api/" }; - 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 } } } }; @@ -51,6 +52,7 @@ public static (List routes, List clusters) GetConfig clusters.Add(monitoringInstance); routes.Add(monitoringRoute); + settings = settings with { MonitoringUrl = "/monitoring-api/" }; } return (routes, clusters); diff --git a/src/ServicePulse/ServicePulseHostSettings.cs b/src/ServicePulse/ServicePulseHostSettings.cs new file mode 100644 index 0000000000..2fb7e1d8ec --- /dev/null +++ b/src/ServicePulse/ServicePulseHostSettings.cs @@ -0,0 +1,212 @@ +namespace ServicePulse; + +using System.Net; +using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; + +class ServicePulseHostSettings +{ + public required bool EnableReverseProxy { get; init; } + + /// + /// Indicates whether forwarded headers processing for reverse proxy scenarios is enabled. + /// + public required bool ForwardedHeadersEnabled { get; init; } + + /// + /// Indicates whether all proxies are trusted for forwarded headers. + /// + public required bool ForwardedHeadersTrustAllProxies { get; init; } + + /// + /// List of known proxy IP addresses for forwarded headers. + /// + public required IReadOnlyList ForwardedHeadersKnownProxies { get; init; } + + /// + /// List of known networks for forwarded headers. + /// + public required IReadOnlyList ForwardedHeadersKnownNetworks { get; init; } + + /// + /// Indicates whether HTTPS is enabled. + /// + public required bool HttpsEnabled { get; init; } + + /// + /// Path to the HTTPS certificate file. + /// + public required string? HttpsCertificatePath { get; init; } + + /// + /// Password for the HTTPS certificate. + /// + public required string? HttpsCertificatePassword { get; init; } + + /// + /// Indicates whether HTTP requests should be redirected to HTTPS. + /// + public required bool HttpsRedirectHttpToHttps { get; init; } + + /// + /// The HTTPS port to use. + /// + public required int? HttpsPort { get; init; } + + /// + /// Indicates whether HSTS is enabled. + /// + public required bool HttpsEnableHsts { get; init; } + + /// + /// The max age for HSTS in seconds. + /// + public required int HttpsHstsMaxAgeSeconds { get; init; } + + /// + /// Indicates whether HSTS should include subdomains. + /// + public required bool HttpsHstsIncludeSubDomains { get; init; } + + public static ServicePulseHostSettings GetFromEnvironmentVariables() + { + var enableReverseProxyValue = Environment.GetEnvironmentVariable("ENABLE_REVERSE_PROXY"); + + if (!bool.TryParse(enableReverseProxyValue, out var enableReverseProxy)) + { + enableReverseProxy = true; + } + + var forwardedHeadersEnabled = ParseBool( + Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_ENABLED"), + defaultValue: true); + + var forwardedHeadersTrustAllProxies = ParseBool( + Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_TRUSTALLPROXIES"), + defaultValue: true); + + var forwardedHeadersKnownProxies = ParseIpAddresses( + Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_KNOWNPROXIES")); + var forwardedHeadersKnownNetworks = ParseNetworks( + Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_KNOWNNETWORKS")); + + // If specific proxies or networks are configured, disable trust all proxies + if (forwardedHeadersKnownProxies.Count > 0 || forwardedHeadersKnownNetworks.Count > 0) + { + forwardedHeadersTrustAllProxies = false; + } + + // HTTPS settings + var httpsEnabled = ParseBool( + Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_ENABLED"), + defaultValue: false); + + var httpsCertificatePath = Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_CERTIFICATEPATH"); + + var httpsCertificatePassword = Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_CERTIFICATEPASSWORD"); + + var httpsRedirectHttpToHttps = ParseBool( + Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_REDIRECTHTTPTOHTTPS"), + defaultValue: false); + + var httpsPort = ParseNullableInt( + Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_PORT")); + + var httpsEnableHsts = ParseBool( + Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_ENABLEHSTS"), + defaultValue: false); + + var httpsHstsMaxAgeSeconds = ParseInt( + Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_HSTSMAXAGESECONDS"), + defaultValue: 31536000); // 1 year + + var httpsHstsIncludeSubDomains = ParseBool( + Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_HSTSINCLUDESUBDOMAINS"), + defaultValue: false); + + return new ServicePulseHostSettings + { + EnableReverseProxy = enableReverseProxy, + ForwardedHeadersEnabled = forwardedHeadersEnabled, + ForwardedHeadersTrustAllProxies = forwardedHeadersTrustAllProxies, + ForwardedHeadersKnownProxies = forwardedHeadersKnownProxies, + ForwardedHeadersKnownNetworks = forwardedHeadersKnownNetworks, + HttpsEnabled = httpsEnabled, + HttpsCertificatePath = httpsCertificatePath, + HttpsCertificatePassword = httpsCertificatePassword, + HttpsRedirectHttpToHttps = httpsRedirectHttpToHttps, + HttpsPort = httpsPort, + HttpsEnableHsts = httpsEnableHsts, + HttpsHstsMaxAgeSeconds = httpsHstsMaxAgeSeconds, + HttpsHstsIncludeSubDomains = httpsHstsIncludeSubDomains + }; + } + + static bool ParseBool(string? value, bool defaultValue) + { + if (bool.TryParse(value, out var result)) + { + return result; + } + return defaultValue; + } + + static int ParseInt(string? value, int defaultValue) + { + if (int.TryParse(value, out var result)) + { + return result; + } + return defaultValue; + } + + static int? ParseNullableInt(string? value) + { + if (int.TryParse(value, out var result)) + { + return result; + } + return null; + } + + static IReadOnlyList ParseIpAddresses(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return []; + } + + var addresses = new List(); + var parts = value.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (var part in parts) + { + if (IPAddress.TryParse(part, out var address)) + { + addresses.Add(address); + } + } + + return addresses; + } + + static IReadOnlyList ParseNetworks(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return []; + } + + var networks = new List(); + var parts = value.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (var part in parts) + { + if (IPNetwork.TryParse(part, out var network)) + { + networks.Add(network); + } + } + + return networks; + } +} diff --git a/src/ServicePulse/Settings.cs b/src/ServicePulse/Settings.cs index a83eb5165f..7cf884aaed 100644 --- a/src/ServicePulse/Settings.cs +++ b/src/ServicePulse/Settings.cs @@ -1,81 +1,17 @@ namespace ServicePulse; -using System.Net; using System.Text.Json; -using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; -class Settings +record Settings { - public required Uri ServiceControlUri { get; init; } + public required string ServiceControlUrl { get; init; } - public required Uri? MonitoringUri { get; init; } + public required string? MonitoringUrl { get; init; } public required string DefaultRoute { get; init; } public required bool ShowPendingRetry { get; init; } - public required bool EnableReverseProxy { get; init; } - - /// - /// Indicates whether forwarded headers processing for reverse proxy scenarios is enabled. - /// - public required bool ForwardedHeadersEnabled { get; init; } - - /// - /// Indicates whether all proxies are trusted for forwarded headers. - /// - public required bool ForwardedHeadersTrustAllProxies { get; init; } - - /// - /// List of known proxy IP addresses for forwarded headers. - /// - public required IReadOnlyList ForwardedHeadersKnownProxies { get; init; } - - /// - /// List of known networks for forwarded headers. - /// - public required IReadOnlyList ForwardedHeadersKnownNetworks { get; init; } - - /// - /// Indicates whether HTTPS is enabled. - /// - public required bool HttpsEnabled { get; init; } - - /// - /// Path to the HTTPS certificate file. - /// - public required string? HttpsCertificatePath { get; init; } - - /// - /// Password for the HTTPS certificate. - /// - public required string? HttpsCertificatePassword { get; init; } - - /// - /// Indicates whether HTTP requests should be redirected to HTTPS. - /// - public required bool HttpsRedirectHttpToHttps { get; init; } - - /// - /// The HTTPS port to use. - /// - public required int? HttpsPort { get; init; } - - /// - /// Indicates whether HSTS is enabled. - /// - public required bool HttpsEnableHsts { get; init; } - - /// - /// The max age for HSTS in seconds. - /// - public required int HttpsHstsMaxAgeSeconds { get; init; } - - /// - /// Indicates whether HSTS should include subdomains. - /// - public required bool HttpsHstsIncludeSubDomains { get; init; } - public static Settings GetFromEnvironmentVariables() { var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/"; @@ -105,151 +41,15 @@ public static Settings GetFromEnvironmentVariables() var showPendingRetryValue = Environment.GetEnvironmentVariable("SHOW_PENDING_RETRY"); bool.TryParse(showPendingRetryValue, out var showPendingRetry); - var enableReverseProxyValue = Environment.GetEnvironmentVariable("ENABLE_REVERSE_PROXY"); - - if (!bool.TryParse(enableReverseProxyValue, out var enableReverseProxy)) - { - enableReverseProxy = true; - } - - var forwardedHeadersEnabled = ParseBool( - Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_ENABLED"), - defaultValue: true); - - var forwardedHeadersTrustAllProxies = ParseBool( - Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_TRUSTALLPROXIES"), - defaultValue: true); - - var forwardedHeadersKnownProxies = ParseIpAddresses( - Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_KNOWNPROXIES")); - var forwardedHeadersKnownNetworks = ParseNetworks( - Environment.GetEnvironmentVariable("SERVICEPULSE_FORWARDEDHEADERS_KNOWNNETWORKS")); - - // If specific proxies or networks are configured, disable trust all proxies - if (forwardedHeadersKnownProxies.Count > 0 || forwardedHeadersKnownNetworks.Count > 0) - { - forwardedHeadersTrustAllProxies = false; - } - - // HTTPS settings - var httpsEnabled = ParseBool( - Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_ENABLED"), - defaultValue: false); - - var httpsCertificatePath = Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_CERTIFICATEPATH"); - - var httpsCertificatePassword = Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_CERTIFICATEPASSWORD"); - - var httpsRedirectHttpToHttps = ParseBool( - Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_REDIRECTHTTPTOHTTPS"), - defaultValue: false); - - var httpsPort = ParseNullableInt( - Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_PORT")); - - var httpsEnableHsts = ParseBool( - Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_ENABLEHSTS"), - defaultValue: false); - - var httpsHstsMaxAgeSeconds = ParseInt( - Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_HSTSMAXAGESECONDS"), - defaultValue: 31536000); // 1 year - - var httpsHstsIncludeSubDomains = ParseBool( - Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_HSTSINCLUDESUBDOMAINS"), - defaultValue: false); - return new Settings { - ServiceControlUri = serviceControlUri, - MonitoringUri = monitoringUri, + ServiceControlUrl = serviceControlUri.ToString(), + MonitoringUrl = monitoringUri?.ToString(), DefaultRoute = defaultRoute, - ShowPendingRetry = showPendingRetry, - EnableReverseProxy = enableReverseProxy, - ForwardedHeadersEnabled = forwardedHeadersEnabled, - ForwardedHeadersTrustAllProxies = forwardedHeadersTrustAllProxies, - ForwardedHeadersKnownProxies = forwardedHeadersKnownProxies, - ForwardedHeadersKnownNetworks = forwardedHeadersKnownNetworks, - HttpsEnabled = httpsEnabled, - HttpsCertificatePath = httpsCertificatePath, - HttpsCertificatePassword = httpsCertificatePassword, - HttpsRedirectHttpToHttps = httpsRedirectHttpToHttps, - HttpsPort = httpsPort, - HttpsEnableHsts = httpsEnableHsts, - HttpsHstsMaxAgeSeconds = httpsHstsMaxAgeSeconds, - HttpsHstsIncludeSubDomains = httpsHstsIncludeSubDomains + ShowPendingRetry = showPendingRetry }; } - static bool ParseBool(string? value, bool defaultValue) - { - if (bool.TryParse(value, out var result)) - { - return result; - } - return defaultValue; - } - - static int ParseInt(string? value, int defaultValue) - { - if (int.TryParse(value, out var result)) - { - return result; - } - return defaultValue; - } - - static int? ParseNullableInt(string? value) - { - if (int.TryParse(value, out var result)) - { - return result; - } - return null; - } - - static IReadOnlyList ParseIpAddresses(string? value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return []; - } - - var addresses = new List(); - var parts = value.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - - foreach (var part in parts) - { - if (IPAddress.TryParse(part, out var address)) - { - addresses.Add(address); - } - } - - return addresses; - } - - static IReadOnlyList ParseNetworks(string? value) - { - if (string.IsNullOrWhiteSpace(value)) - { - return []; - } - - var networks = new List(); - var parts = value.Split([',', ';'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - - foreach (var part in parts) - { - if (IPNetwork.TryParse(part, out var network)) - { - networks.Add(network); - } - } - - return networks; - } - static string? ParseLegacyMonitoringValue(string? value) { if (value is null) diff --git a/src/ServicePulse/WebApplicationBuilderExtensions.cs b/src/ServicePulse/WebApplicationBuilderExtensions.cs index 8fa74e1283..dec99d0610 100644 --- a/src/ServicePulse/WebApplicationBuilderExtensions.cs +++ b/src/ServicePulse/WebApplicationBuilderExtensions.cs @@ -4,7 +4,7 @@ namespace ServicePulse; static class WebApplicationBuilderExtensions { - public static void ConfigureHttps(this WebApplicationBuilder builder, Settings settings) + public static void ConfigureHttps(this WebApplicationBuilder builder, ServicePulseHostSettings settings) { // EnableHsts is disabled by default // Hsts is automatically disabled in Development environments @@ -39,7 +39,7 @@ public static void ConfigureHttps(this WebApplicationBuilder builder, Settings s } } - static X509Certificate2 LoadCertificate(Settings settings) + static X509Certificate2 LoadCertificate(ServicePulseHostSettings settings) { var certPath = settings.HttpsCertificatePath ?? throw new InvalidOperationException("HTTPS is enabled but HTTPS_CERTIFICATEPATH is not set."); diff --git a/src/ServicePulse/WebApplicationExtensions.cs b/src/ServicePulse/WebApplicationExtensions.cs index 3f2f76da01..14f6a8c619 100644 --- a/src/ServicePulse/WebApplicationExtensions.cs +++ b/src/ServicePulse/WebApplicationExtensions.cs @@ -4,7 +4,7 @@ namespace ServicePulse; static class WebApplicationExtensions { - public static void UseForwardedHeaders(this WebApplication app, Settings settings) + public static void UseForwardedHeaders(this WebApplication app, ServicePulseHostSettings settings) { // Register debug endpoint first (before early return) so it's always available in Development if (app.Environment.IsDevelopment()) @@ -83,7 +83,7 @@ public static void UseForwardedHeaders(this WebApplication app, Settings setting app.UseForwardedHeaders(options); } - public static void UseHttpsConfiguration(this WebApplication app, Settings settings) + public static void UseHttpsConfiguration(this WebApplication app, ServicePulseHostSettings settings) { // EnableHsts is disabled by default // Hsts is automatically disabled in Development environments From 15b3cfd5d814e95f6eb31cff983045a58c64598f Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 15:28:32 +0800 Subject: [PATCH 02/13] Extract App from Host --- src/ServicePulse/Program.cs | 18 +------------ .../ServicePulseHostingExtensions.cs | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 src/ServicePulse/ServicePulseHostingExtensions.cs diff --git a/src/ServicePulse/Program.cs b/src/ServicePulse/Program.cs index a6e2e193a3..1ac66ed29c 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); @@ -24,27 +22,13 @@ // HTTPS middleware (HSTS and redirect) app.UseHttpsConfiguration(hostSettings); -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); if (hostSettings.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.UseServicePulse(settings, builder.Environment.WebRootFileProvider); app.Run(); diff --git a/src/ServicePulse/ServicePulseHostingExtensions.cs b/src/ServicePulse/ServicePulseHostingExtensions.cs new file mode 100644 index 0000000000..ac274ec34c --- /dev/null +++ b/src/ServicePulse/ServicePulseHostingExtensions.cs @@ -0,0 +1,26 @@ +using System.Net.Mime; +using Microsoft.Extensions.FileProviders; +using ServicePulse; + +static class ServicePulseHostingExtensions +{ + public static void UseServicePulse(this WebApplication app, Settings settings, IFileProvider overrideFileProvider) + { + var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly, "wwwroot"); + var fileProvider = new CompositeFileProvider(overrideFileProvider, manifestEmbeddedFileProvider); + + var defaultFilesOptions = new DefaultFilesOptions { FileProvider = fileProvider }; + app.UseDefaultFiles(defaultFilesOptions); + + var staticFileOptions = new StaticFileOptions { FileProvider = fileProvider }; + app.UseStaticFiles(staticFileOptions); + + var constantsFile = ConstantsFile.GetContent(settings); + + app.MapGet("/js/app.constants.js", (HttpContext context) => + { + context.Response.ContentType = MediaTypeNames.Text.JavaScript; + return constantsFile; + }); + } +} From 0c3d0ffae66692f13c42e528996a408a0acee107 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 15:35:12 +0800 Subject: [PATCH 03/13] Extract core from host --- .../ConstantsFile.cs | 2 +- .../ServicePulse.Core.csproj | 23 +++++++++++++++++++ .../ServicePulseHostingExtensions.cs | 6 +++-- .../Settings.cs | 2 +- src/ServicePulse.sln | 10 ++++++-- src/ServicePulse/ServicePulse.csproj | 8 +++---- 6 files changed, 40 insertions(+), 11 deletions(-) rename src/{ServicePulse => ServicePulse.Core}/ConstantsFile.cs (89%) create mode 100644 src/ServicePulse.Core/ServicePulse.Core.csproj rename src/{ServicePulse => ServicePulse.Core}/ServicePulseHostingExtensions.cs (83%) rename src/{ServicePulse => ServicePulse.Core}/Settings.cs (99%) diff --git a/src/ServicePulse/ConstantsFile.cs b/src/ServicePulse.Core/ConstantsFile.cs similarity index 89% rename from src/ServicePulse/ConstantsFile.cs rename to src/ServicePulse.Core/ConstantsFile.cs index bf408f051e..870dfcef00 100644 --- a/src/ServicePulse/ConstantsFile.cs +++ b/src/ServicePulse.Core/ConstantsFile.cs @@ -25,7 +25,7 @@ static string GetVersionInformation() { var majorMinorPatch = "0.0.0"; - var attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(); + var attributes = typeof(ConstantsFile).Assembly.GetCustomAttributes(); foreach (var attribute in attributes) { diff --git a/src/ServicePulse.Core/ServicePulse.Core.csproj b/src/ServicePulse.Core/ServicePulse.Core.csproj new file mode 100644 index 0000000000..2e93112650 --- /dev/null +++ b/src/ServicePulse.Core/ServicePulse.Core.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + diff --git a/src/ServicePulse/ServicePulseHostingExtensions.cs b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs similarity index 83% rename from src/ServicePulse/ServicePulseHostingExtensions.cs rename to src/ServicePulse.Core/ServicePulseHostingExtensions.cs index ac274ec34c..2775e6ebcb 100644 --- a/src/ServicePulse/ServicePulseHostingExtensions.cs +++ b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs @@ -1,12 +1,14 @@ using System.Net.Mime; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.FileProviders; using ServicePulse; -static class ServicePulseHostingExtensions +public static class ServicePulseHostingExtensions { public static void UseServicePulse(this WebApplication app, Settings settings, IFileProvider overrideFileProvider) { - var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly, "wwwroot"); + var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseHostingExtensions).Assembly, "wwwroot"); var fileProvider = new CompositeFileProvider(overrideFileProvider, manifestEmbeddedFileProvider); var defaultFilesOptions = new DefaultFilesOptions { FileProvider = fileProvider }; 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 7cf884aaed..069005690d 100644 --- a/src/ServicePulse/Settings.cs +++ b/src/ServicePulse.Core/Settings.cs @@ -2,7 +2,7 @@ using System.Text.Json; -record Settings +public record Settings { public required string ServiceControlUrl { get; init; } diff --git a/src/ServicePulse.sln b/src/ServicePulse.sln index 1114063682..6a96732f0b 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 @@ -38,6 +38,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePulse", "ServicePuls EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePulse.Tests", "ServicePulse.Tests\ServicePulse.Tests.csproj", "{9B75F526-937E-4B25-A9F6-2862129993EB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServicePulse.Core", "ServicePulse.Core\ServicePulse.Core.csproj", "{8FCA3827-719C-49B7-A143-E48461D60F3C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -94,6 +96,10 @@ Global {9B75F526-937E-4B25-A9F6-2862129993EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B75F526-937E-4B25-A9F6-2862129993EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {9B75F526-937E-4B25-A9F6-2862129993EB}.Release|Any CPU.Build.0 = Release|Any CPU + {8FCA3827-719C-49B7-A143-E48461D60F3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FCA3827-719C-49B7-A143-E48461D60F3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FCA3827-719C-49B7-A143-E48461D60F3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FCA3827-719C-49B7-A143-E48461D60F3C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ServicePulse/ServicePulse.csproj b/src/ServicePulse/ServicePulse.csproj index 41f67538e4..42ab61e064 100644 --- a/src/ServicePulse/ServicePulse.csproj +++ b/src/ServicePulse/ServicePulse.csproj @@ -1,10 +1,9 @@ - + net8.0 enable enable - true false @@ -13,13 +12,12 @@ - - + - + \ No newline at end of file From 333ccbc9b1dc19f64e77eafba38396df180f146d Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 15:37:51 +0800 Subject: [PATCH 04/13] Add public API documentation --- .../ServicePulseHostingExtensions.cs | 6 ++++++ src/ServicePulse.Core/Settings.cs | 18 ++++++++++++++++++ src/ServicePulse/Program.cs | 1 - 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ServicePulse.Core/ServicePulseHostingExtensions.cs b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs index 2775e6ebcb..5c31aa5159 100644 --- a/src/ServicePulse.Core/ServicePulseHostingExtensions.cs +++ b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs @@ -4,8 +4,14 @@ using Microsoft.Extensions.FileProviders; using ServicePulse; +/// +/// Extensions for hosting ServicePulse within a WebApplication. +/// public static class ServicePulseHostingExtensions { + /// + /// Adds ServicePulse static file serving and configuration endpoint to the WebApplication. + /// public static void UseServicePulse(this WebApplication app, Settings settings, IFileProvider overrideFileProvider) { var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseHostingExtensions).Assembly, "wwwroot"); diff --git a/src/ServicePulse.Core/Settings.cs b/src/ServicePulse.Core/Settings.cs index 069005690d..78c2b2c82d 100644 --- a/src/ServicePulse.Core/Settings.cs +++ b/src/ServicePulse.Core/Settings.cs @@ -2,16 +2,34 @@ using System.Text.Json; +/// +/// Application Settings for ServicePulse. +/// public record Settings { + /// + /// 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; } + /// + /// The default route to navigate to from the root. + /// public required string DefaultRoute { get; init; } + /// + /// Flag to enable the pending retry feature. + /// public required bool ShowPendingRetry { get; init; } + /// + /// Loads the settings from environment variables. + /// public static Settings GetFromEnvironmentVariables() { var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/"; diff --git a/src/ServicePulse/Program.cs b/src/ServicePulse/Program.cs index 1ac66ed29c..1768a52458 100644 --- a/src/ServicePulse/Program.cs +++ b/src/ServicePulse/Program.cs @@ -22,7 +22,6 @@ // HTTPS middleware (HSTS and redirect) app.UseHttpsConfiguration(hostSettings); - if (hostSettings.EnableReverseProxy) { app.MapReverseProxy(); From 355984d7ee1481b373881501a0bea3196567c746 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 15:38:30 +0800 Subject: [PATCH 05/13] Rename Settings to make it clear in other contexts --- src/ServicePulse.Core/ConstantsFile.cs | 2 +- src/ServicePulse.Core/ServicePulseHostingExtensions.cs | 2 +- .../{Settings.cs => ServicePulseSettings.cs} | 6 +++--- src/ServicePulse/Program.cs | 2 +- src/ServicePulse/ReverseProxy.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename src/ServicePulse.Core/{Settings.cs => ServicePulseSettings.cs} (95%) diff --git a/src/ServicePulse.Core/ConstantsFile.cs b/src/ServicePulse.Core/ConstantsFile.cs index 870dfcef00..cfba1ddf4b 100644 --- a/src/ServicePulse.Core/ConstantsFile.cs +++ b/src/ServicePulse.Core/ConstantsFile.cs @@ -4,7 +4,7 @@ class ConstantsFile { - public static string GetContent(Settings settings) + public static string GetContent(ServicePulseSettings settings) { var version = GetVersionInformation(); diff --git a/src/ServicePulse.Core/ServicePulseHostingExtensions.cs b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs index 5c31aa5159..a42a1bcf7a 100644 --- a/src/ServicePulse.Core/ServicePulseHostingExtensions.cs +++ b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs @@ -12,7 +12,7 @@ public static class ServicePulseHostingExtensions /// /// Adds ServicePulse static file serving and configuration endpoint to the WebApplication. /// - public static void UseServicePulse(this WebApplication app, Settings settings, IFileProvider overrideFileProvider) + public static void UseServicePulse(this WebApplication app, ServicePulseSettings settings, IFileProvider overrideFileProvider) { var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseHostingExtensions).Assembly, "wwwroot"); var fileProvider = new CompositeFileProvider(overrideFileProvider, manifestEmbeddedFileProvider); diff --git a/src/ServicePulse.Core/Settings.cs b/src/ServicePulse.Core/ServicePulseSettings.cs similarity index 95% rename from src/ServicePulse.Core/Settings.cs rename to src/ServicePulse.Core/ServicePulseSettings.cs index 78c2b2c82d..003c559c69 100644 --- a/src/ServicePulse.Core/Settings.cs +++ b/src/ServicePulse.Core/ServicePulseSettings.cs @@ -5,7 +5,7 @@ /// /// Application Settings for ServicePulse. /// -public record Settings +public record ServicePulseSettings { /// /// The location of the ServiceControl API. @@ -30,7 +30,7 @@ public record Settings /// /// Loads the settings from environment variables. /// - public static Settings GetFromEnvironmentVariables() + public static ServicePulseSettings GetFromEnvironmentVariables() { var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/"; @@ -59,7 +59,7 @@ public static Settings GetFromEnvironmentVariables() var showPendingRetryValue = Environment.GetEnvironmentVariable("SHOW_PENDING_RETRY"); bool.TryParse(showPendingRetryValue, out var showPendingRetry); - return new Settings + return new ServicePulseSettings { ServiceControlUrl = serviceControlUri.ToString(), MonitoringUrl = monitoringUri?.ToString(), diff --git a/src/ServicePulse/Program.cs b/src/ServicePulse/Program.cs index 1768a52458..02c3f696c7 100644 --- a/src/ServicePulse/Program.cs +++ b/src/ServicePulse/Program.cs @@ -2,7 +2,7 @@ var builder = WebApplication.CreateBuilder(args); -var settings = Settings.GetFromEnvironmentVariables(); +var settings = ServicePulseSettings.GetFromEnvironmentVariables(); var hostSettings = ServicePulseHostSettings.GetFromEnvironmentVariables(); // Configure Kestrel for HTTPS if enabled diff --git a/src/ServicePulse/ReverseProxy.cs b/src/ServicePulse/ReverseProxy.cs index 72305c328d..468aee90cf 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(ref Settings settings) + public static (List routes, List clusters) GetConfiguration(ref ServicePulseSettings settings) { var routes = new List(); var clusters = new List(); From 00aabf9590e815a24ad512735e0387aac114b0dd Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 15:49:32 +0800 Subject: [PATCH 06/13] Skip AutomaticVersionRange on MS Dependency --- src/ServicePulse.Core/ServicePulse.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServicePulse.Core/ServicePulse.Core.csproj b/src/ServicePulse.Core/ServicePulse.Core.csproj index 2e93112650..5fa951ef0c 100644 --- a/src/ServicePulse.Core/ServicePulse.Core.csproj +++ b/src/ServicePulse.Core/ServicePulse.Core.csproj @@ -8,7 +8,7 @@ - + From 367b14e653b25d1f022bda7996708d671867c288 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Thu, 15 Jan 2026 15:55:17 +0800 Subject: [PATCH 07/13] Expect an additional nuget --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d17c012ba4..7b4fb399cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,7 @@ jobs: $nugetsCount = (Get-ChildItem -Recurse -File nugets).Count $expectedAssetsCount = 1 - $expectedNugetsCount = 1 + $expectedNugetsCount = 2 if ($assetsCount -ne $expectedAssetsCount) { From 19c60857d00f9028ae5e9dd10f1380a1f563b367 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:04:20 +0800 Subject: [PATCH 08/13] Serving files off disk is optional --- src/ServicePulse.Core/ServicePulseHostingExtensions.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ServicePulse.Core/ServicePulseHostingExtensions.cs b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs index a42a1bcf7a..03a4a92062 100644 --- a/src/ServicePulse.Core/ServicePulseHostingExtensions.cs +++ b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs @@ -12,10 +12,12 @@ public static class ServicePulseHostingExtensions /// /// Adds ServicePulse static file serving and configuration endpoint to the WebApplication. /// - public static void UseServicePulse(this WebApplication app, ServicePulseSettings settings, IFileProvider overrideFileProvider) + public static void UseServicePulse(this WebApplication app, ServicePulseSettings settings, IFileProvider? overrideFileProvider = null) { var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseHostingExtensions).Assembly, "wwwroot"); - var fileProvider = new CompositeFileProvider(overrideFileProvider, manifestEmbeddedFileProvider); + IFileProvider fileProvider = overrideFileProvider is null + ? manifestEmbeddedFileProvider + : new CompositeFileProvider(overrideFileProvider, manifestEmbeddedFileProvider); var defaultFilesOptions = new DefaultFilesOptions { FileProvider = fileProvider }; app.UseDefaultFiles(defaultFilesOptions); From 8574d670ff29b186abf120e18393ab85401d78aa Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 09:28:57 +0800 Subject: [PATCH 09/13] Indicate to UI if running in embedded mode --- src/ServicePulse.Core/ConstantsFile.cs | 1 + src/ServicePulse.Core/ServicePulseSettings.cs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ServicePulse.Core/ConstantsFile.cs b/src/ServicePulse.Core/ConstantsFile.cs index cfba1ddf4b..6c7c209373 100644 --- a/src/ServicePulse.Core/ConstantsFile.cs +++ b/src/ServicePulse.Core/ConstantsFile.cs @@ -15,6 +15,7 @@ public static string GetContent(ServicePulseSettings settings) service_control_url: '{{settings.ServiceControlUrl}}', monitoring_urls: ['{{settings.MonitoringUrl ?? "!"}}'], showPendingRetry: {{(settings.ShowPendingRetry ? "true" : "false")}}, + isEmbedded: {{(settings.IsEmbedded ? "true" : "false")}} } """; diff --git a/src/ServicePulse.Core/ServicePulseSettings.cs b/src/ServicePulse.Core/ServicePulseSettings.cs index 003c559c69..ce8afddf60 100644 --- a/src/ServicePulse.Core/ServicePulseSettings.cs +++ b/src/ServicePulse.Core/ServicePulseSettings.cs @@ -27,6 +27,11 @@ public record ServicePulseSettings /// public required bool ShowPendingRetry { get; init; } + /// + /// Flag to indicate if ServicePulse is running in embedded mode. + /// + public required bool IsEmbedded { get; init; } + /// /// Loads the settings from environment variables. /// @@ -64,7 +69,8 @@ public static ServicePulseSettings GetFromEnvironmentVariables() ServiceControlUrl = serviceControlUri.ToString(), MonitoringUrl = monitoringUri?.ToString(), DefaultRoute = defaultRoute, - ShowPendingRetry = showPendingRetry + ShowPendingRetry = showPendingRetry, + IsEmbedded = false }; } From bece31708f66431d73cff59d8dfedc414fa3d5d6 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 10:45:16 +0800 Subject: [PATCH 10/13] Adjust SP UI when embedded --- src/Frontend/env.d.ts | 1 + src/Frontend/public/js/app.constants.js | 1 + src/Frontend/src/components/PageFooter.vue | 6 ++++-- .../configuration/PlatformConnections.vue | 21 ++++++++++++------- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Frontend/env.d.ts b/src/Frontend/env.d.ts index 25bf2dc338..63511c0f87 100644 --- a/src/Frontend/env.d.ts +++ b/src/Frontend/env.d.ts @@ -10,6 +10,7 @@ declare global { service_control_url: string; monitoring_urls: string[]; showPendingRetry: boolean; + isEmbedded?: boolean; }; } } diff --git a/src/Frontend/public/js/app.constants.js b/src/Frontend/public/js/app.constants.js index d138f99109..4e49c18235 100644 --- a/src/Frontend/public/js/app.constants.js +++ b/src/Frontend/public/js/app.constants.js @@ -4,4 +4,5 @@ window.defaultConfig = { service_control_url: 'http://localhost:33333/api/', monitoring_urls: ['http://localhost:33633/'], showPendingRetry: false, + isEmbedded: true, }; diff --git a/src/Frontend/src/components/PageFooter.vue b/src/Frontend/src/components/PageFooter.vue index ecb826af4f..0d6f8ecd99 100644 --- a/src/Frontend/src/components/PageFooter.vue +++ b/src/Frontend/src/components/PageFooter.vue @@ -21,6 +21,7 @@ const environment = environmentAndVersionsStore.environment; const licenseStore = useLicenseStore(); const { licenseStatus, license } = licenseStore; const isMonitoringEnabled = monitoringClient.isMonitoringEnabled; +const isEmbedded = window.defaultConfig.isEmbedded; const scAddressTooltip = computed(() => { return `ServiceControl URL ${serviceControlClient.url}`; @@ -44,8 +45,9 @@ const { configuration } = storeToRefs(configurationStore); Connect new endpoint - ServicePulse v{{ environment.sp_version }} - + ServicePulse (EMBEDDED) + ServicePulse v{{ environment.sp_version }} + ServicePulse v{{ environment.sp_version }} ( v{{ newVersions.newSPVersion.newspversionnumber }} available) diff --git a/src/Frontend/src/components/configuration/PlatformConnections.vue b/src/Frontend/src/components/configuration/PlatformConnections.vue index 2f97ee54ea..02ccf41c68 100644 --- a/src/Frontend/src/components/configuration/PlatformConnections.vue +++ b/src/Frontend/src/components/configuration/PlatformConnections.vue @@ -20,6 +20,7 @@ const serviceControlValid = ref(null); const testingMonitoring = ref(false); const monitoringValid = ref(null); const connectionSaved = ref(null); +const isEmbedded = window.defaultConfig.isEmbedded; async function testServiceControlUrl() { if (localServiceControlUrl.value) { @@ -64,10 +65,15 @@ function saveConnections() { } function updateServiceControlUrls() { - if (!localServiceControlUrl.value) { - throw new Error("ServiceControl URL is mandatory"); - } else if (!localServiceControlUrl.value.endsWith("/")) { - localServiceControlUrl.value += "/"; + const params = new URLSearchParams(); + + if (!isEmbedded) { + if (!localServiceControlUrl.value) { + throw new Error("ServiceControl URL is mandatory"); + } else if (!localServiceControlUrl.value.endsWith("/")) { + localServiceControlUrl.value += "/"; + } + params.set("scu", localServiceControlUrl.value); } if (!localMonitoringUrl.value) { @@ -76,8 +82,6 @@ function updateServiceControlUrls() { localMonitoringUrl.value += "/"; } - const params = new URLSearchParams(); - params.set("scu", localServiceControlUrl.value); params.set("mu", localMonitoringUrl.value); window.location.search = `?${params.toString()}`; } @@ -94,11 +98,14 @@ function updateServiceControlUrls() {
- +
From bf7b828e63c0242a880b79dc2cc7be57b3efc6d4 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 16 Jan 2026 12:20:49 +0800 Subject: [PATCH 11/13] Cleanup --- src/Frontend/public/js/app.constants.js | 2 +- src/Frontend/src/components/PageFooter.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Frontend/public/js/app.constants.js b/src/Frontend/public/js/app.constants.js index 4e49c18235..8d65f8e78b 100644 --- a/src/Frontend/public/js/app.constants.js +++ b/src/Frontend/public/js/app.constants.js @@ -4,5 +4,5 @@ window.defaultConfig = { service_control_url: 'http://localhost:33333/api/', monitoring_urls: ['http://localhost:33633/'], showPendingRetry: false, - isEmbedded: true, + isEmbedded: false, }; diff --git a/src/Frontend/src/components/PageFooter.vue b/src/Frontend/src/components/PageFooter.vue index 0d6f8ecd99..3f3ece70f7 100644 --- a/src/Frontend/src/components/PageFooter.vue +++ b/src/Frontend/src/components/PageFooter.vue @@ -45,7 +45,7 @@ const { configuration } = storeToRefs(configurationStore); Connect new endpoint - ServicePulse (EMBEDDED) + ServicePulse: Embedded ServicePulse v{{ environment.sp_version }} ServicePulse v{{ environment.sp_version }} ( From eb33962ff2237c5f074b76c3e75b0010afd40356 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 23 Jan 2026 12:17:53 +0800 Subject: [PATCH 12/13] Update StaticMiddlewareTests.cs --- src/ServicePulse.Host.Tests/Owin/StaticMiddlewareTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ServicePulse.Host.Tests/Owin/StaticMiddlewareTests.cs b/src/ServicePulse.Host.Tests/Owin/StaticMiddlewareTests.cs index f6036cf31e..97972d7e54 100644 --- a/src/ServicePulse.Host.Tests/Owin/StaticMiddlewareTests.cs +++ b/src/ServicePulse.Host.Tests/Owin/StaticMiddlewareTests.cs @@ -129,7 +129,7 @@ public async Task Should_find_prefer_constants_file_on_disk_over_embedded_if_bot } }; await middleware.Invoke(context); - const long sizeOfFileOnDisk = 215; // this is the /app/js/app.constants.js file + const long sizeOfFileOnDisk = 237; // this is the /app/js/app.constants.js file Assert.That(context.Response.ContentLength, Is.EqualTo(sizeOfFileOnDisk)); Assert.That(context.Response.ContentType, Is.EqualTo("application/javascript")); } From f282079e13fe9a97dc41126c42973cc701fea720 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 23 Jan 2026 12:34:26 +0800 Subject: [PATCH 13/13] Use passed ServiceControl url if embedded --- src/Frontend/src/components/serviceControlClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Frontend/src/components/serviceControlClient.ts b/src/Frontend/src/components/serviceControlClient.ts index a5d025b9dd..d002c1a42f 100644 --- a/src/Frontend/src/components/serviceControlClient.ts +++ b/src/Frontend/src/components/serviceControlClient.ts @@ -96,6 +96,10 @@ class ServiceControlClient { } private getUrl() { + if (window.defaultConfig && window.defaultConfig.isEmbedded && window.defaultConfig.service_control_url && window.defaultConfig.service_control_url.length) { + return window.defaultConfig.service_control_url; + } + const searchParams = new URLSearchParams(window.location.search); const scu = searchParams.get("scu"); const existingScu = window.localStorage.getItem("scu");