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..058cc1104a --- /dev/null +++ b/src/ServicePulse.Core/ServicePulseExtensions.cs @@ -0,0 +1,80 @@ +namespace ServicePulse; + +using System.Net.Mime; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +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"); + + IFileProvider fileProvider = overrideFileProvider is null + ? embeddedFileProvider + : new CompositeFileProvider(overrideFileProvider, embeddedFileProvider); + + return app + .UseDefaultFiles(new DefaultFilesOptions + { + FileProvider = fileProvider + }) + .UseStaticFiles(new StaticFileOptions + { + FileProvider = fileProvider + }); + } + + /// + /// 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); + + app.MapGet("/js/app.constants.js", (HttpContext context) => + { + context.Response.ContentType = MediaTypeNames.Text.JavaScript; + return constantsFile; + }); + + 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/Settings.cs b/src/ServicePulse.Core/ServicePulseSettings.cs similarity index 69% rename from src/ServicePulse/Settings.cs rename to src/ServicePulse.Core/ServicePulseSettings.cs index 51717b5cbe..4ed984305b 100644 --- a/src/ServicePulse/Settings.cs +++ b/src/ServicePulse.Core/ServicePulseSettings.cs @@ -2,19 +2,35 @@ using System.Text.Json; -class Settings +/// +/// The runtime settings of a ServicePulse instance. +/// +public record ServicePulseSettings { - public required Uri ServiceControlUri { get; init; } + /// + /// The default route to navigate to. + /// + public required string DefaultRoute { get; init; } - public required Uri? MonitoringUri { get; init; } + /// + /// The location of the ServiceControl API. + /// + public required string ServiceControlUrl { get; init; } - public required string DefaultRoute { 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; } - public required bool EnableReverseProxy { get; init; } - - public static Settings GetFromEnvironmentVariables() + /// + /// Loads the settings from environment variables. + /// + public static ServicePulseSettings GetFromEnvironmentVariables() { var serviceControlUrl = Environment.GetEnvironmentVariable("SERVICECONTROL_URL") ?? "http://localhost:33333/api/"; @@ -43,20 +59,12 @@ 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; - } - - return new Settings + return new() { - ServiceControlUri = serviceControlUri, - MonitoringUri = monitoringUri, + ServiceControlUrl = serviceControlUri.ToString(), + MonitoringUrl = monitoringUri?.ToString(), DefaultRoute = defaultRoute, - ShowPendingRetry = showPendingRetry, - EnableReverseProxy = enableReverseProxy + ShowPendingRetry = showPendingRetry }; } @@ -95,4 +103,4 @@ class MonitoringUrls { public string[] Addresses { get; set; } = []; } -} +} \ No newline at end of file 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/ConstantsFile.cs b/src/ServicePulse/ConstantsFile.cs deleted file mode 100644 index 9ab55df879..0000000000 --- a/src/ServicePulse/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/Program.cs b/src/ServicePulse/Program.cs index a4c304be50..41ae439515 100644 --- a/src/ServicePulse/Program.cs +++ b/src/ServicePulse/Program.cs @@ -1,39 +1,24 @@ -using System.Net.Mime; -using Microsoft.Extensions.FileProviders; using ServicePulse; 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); - builder.Services.AddReverseProxy().LoadFromMemory(routes, clusters); + builder.Services.AddServicePulseReverseProxy(ref servicePulseSettings); } var app = builder.Build(); -var manifestEmbeddedFileProvider = new ManifestEmbeddedFileProvider(typeof(Program).Assembly, "wwwroot"); -var fileProvider = new CompositeFileProvider(builder.Environment.WebRootFileProvider, manifestEmbeddedFileProvider); +app.UseServicePulse(builder.Environment.ContentRootFileProvider); -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.MapServicePulseConstants(servicePulseSettings); app.Run(); diff --git a/src/ServicePulse/ReverseProxy.cs b/src/ServicePulse/ReverseProxy.cs index 90ccc591d9..6cb4750f3b 100644 --- a/src/ServicePulse/ReverseProxy.cs +++ b/src/ServicePulse/ReverseProxy.cs @@ -5,17 +5,25 @@ static class ReverseProxy { - public static (List routes, List clusters) GetConfiguration(Settings settings) + public static void AddServicePulseReverseProxy(this IServiceCollection services, ref ServicePulseSettings settings) { var routes = new List(); var clusters = new List(); + AddServiceControl(ref settings, routes, clusters); + AddMonitoringIfEnabled(ref settings, routes, clusters); + + services.AddReverseProxy().LoadFromMemory(routes, clusters); + } + + static void AddServiceControl(ref ServicePulseSettings settings, List routes, List clusters) + { var serviceControlInstance = new ClusterConfig { ClusterId = "serviceControlInstance", Destinations = new Dictionary { - { "instance", new DestinationConfig { Address = settings.ServiceControlUri.ToString() } } + { "instance", new DestinationConfig { Address = settings.ServiceControlUrl } } } }; var serviceControlRoute = new RouteConfig @@ -31,28 +39,41 @@ public static (List routes, List clusters) GetConfig clusters.Add(serviceControlInstance); routes.Add(serviceControlRoute); - if (settings.MonitoringUri != null) + settings = settings with { - var monitoringInstance = new ClusterConfig - { - ClusterId = "monitoringInstance", - Destinations = new Dictionary - { - { "instance", new DestinationConfig { Address = settings.MonitoringUri.ToString() } } - } - }; - - var monitoringRoute = new RouteConfig - { - RouteId = "monitoringRoute", - ClusterId = nameof(monitoringInstance), - Match = new RouteMatch { Path = "/monitoring-api/{**catch-all}" } - }.WithTransformPathRemovePrefix("/monitoring-api"); + ServiceControlUrl = "/api/" + }; + } - clusters.Add(monitoringInstance); - routes.Add(monitoringRoute); + static void AddMonitoringIfEnabled(ref ServicePulseSettings settings, List routes, List clusters) + { + if (settings.MonitoringUrl is null) + { + return; } - return (routes, clusters); + var monitoringInstance = new ClusterConfig + { + ClusterId = "monitoringInstance", + Destinations = new Dictionary + { + { "instance", new DestinationConfig { Address = settings.MonitoringUrl } } + } + }; + + var monitoringRoute = new RouteConfig + { + RouteId = "monitoringRoute", + ClusterId = nameof(monitoringInstance), + Match = new RouteMatch { Path = "/monitoring-api/{**catch-all}" } + }.WithTransformPathRemovePrefix("/monitoring-api"); + + clusters.Add(monitoringInstance); + routes.Add(monitoringRoute); + settings = settings with + { + MonitoringUrl = "/monitoring-api/" + }; } + } 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 - - + 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