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");
diff --git a/src/ServicePulse/ConstantsFile.cs b/src/ServicePulse.Core/ConstantsFile.cs
similarity index 51%
rename from src/ServicePulse/ConstantsFile.cs
rename to src/ServicePulse.Core/ConstantsFile.cs
index 9ab55df879..6c7c209373 100644
--- a/src/ServicePulse/ConstantsFile.cs
+++ b/src/ServicePulse.Core/ConstantsFile.cs
@@ -4,31 +4,18 @@
class ConstantsFile
{
- public static string GetContent(Settings settings)
+ public static string GetContent(ServicePulseSettings 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")}},
+ isEmbedded: {{(settings.IsEmbedded ? "true" : "false")}}
}
""";
@@ -39,7 +26,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..5fa951ef0c
--- /dev/null
+++ b/src/ServicePulse.Core/ServicePulse.Core.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ServicePulse.Core/ServicePulseHostingExtensions.cs b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs
new file mode 100644
index 0000000000..03a4a92062
--- /dev/null
+++ b/src/ServicePulse.Core/ServicePulseHostingExtensions.cs
@@ -0,0 +1,36 @@
+using System.Net.Mime;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+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, ServicePulseSettings settings, IFileProvider? overrideFileProvider = null)
+ {
+ var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(ServicePulseHostingExtensions).Assembly, "wwwroot");
+ IFileProvider fileProvider = overrideFileProvider is null
+ ? manifestEmbeddedFileProvider
+ : 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;
+ });
+ }
+}
diff --git a/src/ServicePulse.Core/ServicePulseSettings.cs b/src/ServicePulse.Core/ServicePulseSettings.cs
new file mode 100644
index 0000000000..ce8afddf60
--- /dev/null
+++ b/src/ServicePulse.Core/ServicePulseSettings.cs
@@ -0,0 +1,112 @@
+namespace ServicePulse;
+
+using System.Text.Json;
+
+///
+/// Application Settings for ServicePulse.
+///
+public record ServicePulseSettings
+{
+ ///
+ /// 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; }
+
+ ///
+ /// Flag to indicate if ServicePulse is running in embedded mode.
+ ///
+ public required bool IsEmbedded { get; init; }
+
+ ///
+ /// Loads the settings from environment variables.
+ ///
+ public static ServicePulseSettings GetFromEnvironmentVariables()
+ {
+ var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/";
+
+ if (!serviceControlUrl.EndsWith("/", StringComparison.Ordinal))
+ {
+ serviceControlUrl += "/";
+ }
+
+ if (!serviceControlUrl.EndsWith("api/", StringComparison.Ordinal))
+ {
+ serviceControlUrl += "api/";
+ }
+
+ var serviceControlUri = new Uri(serviceControlUrl);
+
+ 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
+ {
+ ServiceControlUrl = serviceControlUri.ToString(),
+ MonitoringUrl = monitoringUri?.ToString(),
+ DefaultRoute = defaultRoute,
+ ShowPendingRetry = showPendingRetry,
+ IsEmbedded = false
+ };
+ }
+
+ 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/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"));
}
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/Program.cs b/src/ServicePulse/Program.cs
index 9b4ca85024..02c3f696c7 100644
--- a/src/ServicePulse/Program.cs
+++ b/src/ServicePulse/Program.cs
@@ -1,49 +1,33 @@
-using System.Net.Mime;
-using Microsoft.Extensions.FileProviders;
using ServicePulse;
var builder = WebApplication.CreateBuilder(args);
-var settings = Settings.GetFromEnvironmentVariables();
+var settings = ServicePulseSettings.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);
-
-var defaultFilesOptions = new DefaultFilesOptions { FileProvider = fileProvider };
-app.UseDefaultFiles(defaultFilesOptions);
-
-var staticFileOptions = new StaticFileOptions { FileProvider = fileProvider };
-app.UseStaticFiles(staticFileOptions);
-
-if (settings.EnableReverseProxy)
+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/ReverseProxy.cs b/src/ServicePulse/ReverseProxy.cs
index 90ccc591d9..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(Settings settings)
+ public static (List routes, List clusters) GetConfiguration(ref 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
@@ -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/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
diff --git a/src/ServicePulse/Settings.cs b/src/ServicePulse/ServicePulseHostSettings.cs
similarity index 74%
rename from src/ServicePulse/Settings.cs
rename to src/ServicePulse/ServicePulseHostSettings.cs
index a83eb5165f..2fb7e1d8ec 100644
--- a/src/ServicePulse/Settings.cs
+++ b/src/ServicePulse/ServicePulseHostSettings.cs
@@ -1,19 +1,10 @@
namespace ServicePulse;
using System.Net;
-using System.Text.Json;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
-class Settings
+class ServicePulseHostSettings
{
- public required Uri ServiceControlUri { get; init; }
-
- public required Uri? MonitoringUri { get; init; }
-
- public required string DefaultRoute { get; init; }
-
- public required bool ShowPendingRetry { get; init; }
-
public required bool EnableReverseProxy { get; init; }
///
@@ -76,35 +67,8 @@ class Settings
///
public required bool HttpsHstsIncludeSubDomains { get; init; }
- public static Settings GetFromEnvironmentVariables()
+ public static ServicePulseHostSettings GetFromEnvironmentVariables()
{
- var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/";
-
- if (!serviceControlUrl.EndsWith("/", StringComparison.Ordinal))
- {
- serviceControlUrl += "/";
- }
-
- if (!serviceControlUrl.EndsWith("api/", StringComparison.Ordinal))
- {
- serviceControlUrl += "api/";
- }
-
- var serviceControlUri = new Uri(serviceControlUrl);
-
- 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);
-
var enableReverseProxyValue = Environment.GetEnvironmentVariable("ENABLE_REVERSE_PROXY");
if (!bool.TryParse(enableReverseProxyValue, out var enableReverseProxy))
@@ -159,12 +123,8 @@ public static Settings GetFromEnvironmentVariables()
Environment.GetEnvironmentVariable("SERVICEPULSE_HTTPS_HSTSINCLUDESUBDOMAINS"),
defaultValue: false);
- return new Settings
+ return new ServicePulseHostSettings
{
- ServiceControlUri = serviceControlUri,
- MonitoringUri = monitoringUri,
- DefaultRoute = defaultRoute,
- ShowPendingRetry = showPendingRetry,
EnableReverseProxy = enableReverseProxy,
ForwardedHeadersEnabled = forwardedHeadersEnabled,
ForwardedHeadersTrustAllProxies = forwardedHeadersTrustAllProxies,
@@ -249,40 +209,4 @@ static IReadOnlyList ParseNetworks(string? value)
return networks;
}
-
- 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/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