Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
25a4257
Embed ServicePulse
mikeminutillo Jan 16, 2026
1ff3c88
Add setting to enable/disable embedded ServicePulse
mikeminutillo Jan 16, 2026
8ae9cd1
Move settings
mikeminutillo Jan 16, 2026
083d500
Tell ServicePulse it is running in embedded mode
mikeminutillo Jan 16, 2026
5c25bd4
Show SP url in SCMU and default to enabled
mikeminutillo Jan 20, 2026
747c290
Allow disabling embedded ServicePulse at creation
mikeminutillo Jan 20, 2026
a549901
Enable updating a running instance
mikeminutillo Jan 20, 2026
0c5d665
Show ServicePulse heading if using embedded
mikeminutillo Jan 21, 2026
35b04dd
Fix ApiUrl
mikeminutillo Jan 21, 2026
2fed931
Ask about embedded ServicePulse on upgrade
mikeminutillo Jan 22, 2026
fbf96da
Enable configuration of embedded SP in PWSH
mikeminutillo Jan 23, 2026
bd2cb9a
Update SCMU to use https if enabled
mikeminutillo Jan 28, 2026
b4d0d35
Change URL for monitoring instance when https is enabled
mikeminutillo Jan 29, 2026
7b74ccc
Use new package
mikeminutillo Feb 6, 2026
b065d69
Improvements to upgrade questions based on review
mikeminutillo Feb 6, 2026
30ef2ed
Add abstraction for environment data providers
mikeminutillo Feb 6, 2026
6713c30
Include Integrated ServicePulse status in usage report
mikeminutillo Feb 6, 2026
b0e591d
Fix version number for introduction of setting
mikeminutillo Feb 6, 2026
8ca26e5
Clean up project file
mikeminutillo Feb 6, 2026
af7a4b8
Clean up project file 2
mikeminutillo Feb 6, 2026
b1d1ab0
Test with real ServicePulse package
mikeminutillo Feb 24, 2026
4ca4e6d
Fix setting version number
mikeminutillo Feb 24, 2026
dffc67b
Approve settings
mikeminutillo Feb 24, 2026
db842e7
Update docker documentation
mikeminutillo Feb 24, 2026
1013c57
Switch to using release package
mikeminutillo Feb 26, 2026
b209e05
Log integrated ServicePulse status at startup
mikeminutillo Feb 26, 2026
c6e567c
Do not use host name
mikeminutillo Feb 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nuget.config
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
</configuration>
4 changes: 3 additions & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
<PackageVersion Include="Particular.LicensingComponent.Report" Version="1.0.0" />
<PackageVersion Include="Particular.Licensing.Sources" Version="6.1.0" />
<PackageVersion Include="Particular.Obsoletes" Version="1.0.0" />
<PackageVersion Include="Particular.ServicePulse.Core" Version="2.6.0" />
<PackageVersion Include="Polly.Core" Version="8.6.5" />
<PackageVersion Include="PropertyChanged.Fody" Version="4.1.0" />
<PackageVersion Include="PropertyChanging.Fody" Version="1.31.0" />
Expand All @@ -80,6 +81,7 @@
<PackageVersion Include="System.Reactive" Version="6.1.0" />
<PackageVersion Include="System.Reflection.MetadataLoadContext" Version="10.0.3" />
<PackageVersion Include="System.ServiceProcess.ServiceController" Version="10.0.3" />
<PackageVersion Include="ServicePulse.Core" Version="2.5.0-alpha.0.58" />
<PackageVersion Include="Validar.Fody" Version="1.9.0" />
<PackageVersion Include="Yarp.ReverseProxy" Version="2.3.0" />
</ItemGroup>
Expand All @@ -92,4 +94,4 @@
<GlobalPackageReference Include="Microsoft.Build.CopyOnWrite" Version="1.0.334" />
<GlobalPackageReference Include="Particular.Packaging" Version="4.5.0" />
</ItemGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Particular.LicensingComponent.Contracts;

/// <summary>
/// Provides environment data that is included in usage reports
/// </summary>
public interface IEnvironmentDataProvider
{
IEnumerable<(string key, string value)> GetData();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Particular.LicensingComponent.UnitTests;

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Particular.LicensingComponent.Contracts;
using Particular.LicensingComponent.UnitTests.Infrastructure;

[TestFixture]
class ThroughputCollector_AdditionalEnvironmentDataProvider_Tests : ThroughputCollectorTestFixture
{
public override Task Setup()
{
SetExtraDependencies = services => services.AddSingleton<IEnvironmentDataProvider, TestAdditionalEnvironmentDataProvider>();

return base.Setup();
}

[Test]
public async Task Should_include_additional_environment_data_in_throughput_report()
{
// Arrange
// Act
var report = await ThroughputCollector.GenerateThroughputReport(null, null, default);
// Assert
Assert.That(report, Is.Not.Null);
Assert.That(report.ReportData, Is.Not.Null);
Assert.That(report.ReportData.EnvironmentInformation, Is.Not.Null);
Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData, Is.Not.Null);
Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData.ContainsKey("TestKey"));
Assert.That(report.ReportData.EnvironmentInformation.EnvironmentData["TestKey"], Is.EqualTo("TestValue"));
}

class TestAdditionalEnvironmentDataProvider : IEnvironmentDataProvider
{
public IEnumerable<(string key, string value)> GetData()
{
yield return ("TestKey", "TestValue");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();

var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());
var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());

// Act
var summary = await throughputCollector.GetThroughputSummary(default);
Expand All @@ -61,7 +61,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();

var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());
var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithLowerCaseSanitizedNameCleanse());

// Act
var report = await throughputCollector.GenerateThroughputReport(null, null, default);
Expand All @@ -88,7 +88,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();

var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithNoSanitizedNameCleanse());
var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithNoSanitizedNameCleanse());

// Act
var summary = await throughputCollector.GetThroughputSummary(default);
Expand All @@ -114,7 +114,7 @@ await DataStore.CreateBuilder()
.WithThroughput(data: [60])
.Build();

var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, new BrokerThroughputQuery_WithNoSanitizedNameCleanse());
var throughputCollector = new ThroughputCollector(DataStore, configuration.ThroughputSettings, configuration.AuditQuery, configuration.MonitoringService, [], new BrokerThroughputQuery_WithNoSanitizedNameCleanse());

// Act
var report = await throughputCollector.GenerateThroughputReport(null, null, default);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Particular.LicensingComponent;

using Contracts;
using Microsoft.Extensions.DependencyInjection;

public static class LicensingComponentServiceCollectionExtensions
{
public static IServiceCollection AddEnvironmentDataProvider<T>(this IServiceCollection services)
where T : class, IEnvironmentDataProvider
=> services.AddSingleton<IEnvironmentDataProvider, T>();
}
10 changes: 9 additions & 1 deletion src/Particular.LicensingComponent/ThroughputCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
using Shared;
using QueueThroughput = Report.QueueThroughput;

public class ThroughputCollector(ILicensingDataStore dataStore, ThroughputSettings throughputSettings, IAuditQuery auditQuery, MonitoringService monitoringService, IBrokerThroughputQuery? throughputQuery = null)
public class ThroughputCollector(ILicensingDataStore dataStore, ThroughputSettings throughputSettings, IAuditQuery auditQuery, MonitoringService monitoringService, IEnumerable<IEnvironmentDataProvider> environmentDataProviders, IBrokerThroughputQuery? throughputQuery = null)
: IThroughputCollector
{
public async Task<ThroughputConnectionSettings> GetThroughputConnectionSettingsInformation(CancellationToken cancellationToken)
Expand Down Expand Up @@ -179,6 +179,14 @@ public async Task<SignedReport> GenerateThroughputReport(string spVersion, DateT
report.EnvironmentInformation.EnvironmentData[EnvironmentDataType.AuditEnabled.ToString()] = systemHasAuditEnabled.ToString();
report.EnvironmentInformation.EnvironmentData[EnvironmentDataType.MonitoringEnabled.ToString()] = systemHasMonitoringEnabled.ToString();

foreach (var environmentDataProvider in environmentDataProviders)
{
foreach (var (key, value) in environmentDataProvider.GetData())
{
report.EnvironmentInformation.EnvironmentData[key] = value;
}
}

var throughputReport = new SignedReport { ReportData = report, Signature = Signature.SignReport(report) };
return throughputReport;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ public override async Task ExecuteAsync(InstanceDetailsViewModel model)
}
}

if (!instance.AppConfig.AppSettingExists(ServiceControlSettings.EnableIntegratedServicePulse.Name))
{
var result = await windowManager.ShowYesNoCancelDialog("INPUT REQUIRED - INTEGRATED SERVICEPULSE",
"ServiceControl can host an integrated version of ServicePulse which allows you to monitor your ServiceControl instance without needing to install ServicePulse separately.",
"Should an integrated ServicePulse be enabled for this ServiceControl instance?",
"Enable integrated ServicePulse",
"Do NOT enable integrated ServicePulse");

if (!result.HasValue)
{
//Dialog was cancelled
await eventAggregator.PublishOnUIThreadAsync(new RefreshInstances());
return;
}

upgradeOptions.EnableIntegratedServicePulse = result.Value;
}

if (await commandChecks.StopBecauseInstanceIsRunning(instance))
{
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ServiceControl.Config.UI.InstanceAdd
{
public class EnableIntegratedServicePulseOption
{
public string Name { get; set; }
public bool Value { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ async Task Add()
serviceControlNewInstance.ServiceAccount = viewModel.ServiceControl.ServiceAccount;
serviceControlNewInstance.ServiceAccountPwd = viewModel.ServiceControl.Password;
serviceControlNewInstance.EnableFullTextSearchOnBodies = viewModel.ServiceControl.EnableFullTextSearchOnBodies.Value;
serviceControlNewInstance.EnableIntegratedServicePulse = viewModel.ServiceControl.EnableIntegratedServicePulse.Value;
}

var auditNewInstance = viewModel.InstallAuditInstance ? ServiceControlAuditNewInstance.CreateWithDefaultPersistence() : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,12 @@
Header="FULL TEXT SEARCH ON MESSAGE BODIES"
ItemsSource="{Binding ErrorEnableFullTextSearchOnBodiesOptions}"
SelectedValue="{Binding ErrorEnableFullTextSearchOnBodies}" />
<controls:FormComboBox HorizontalAlignment="Stretch"
VerticalAlignment="Top"
DisplayMemberPath="Name"
Header="ENABLE INTEGRATED SERVICEPULSE"
ItemsSource="{Binding ErrorEnableIntegratedServicePulseOptions}"
SelectedValue="{Binding ErrorEnableIntegratedServicePulse}" />
</StackPanel>
</Expander>
</Grid>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,15 @@ public EnableFullTextSearchOnBodiesOption ErrorEnableFullTextSearchOnBodies
set => ServiceControl.EnableFullTextSearchOnBodies = value;
}

public IEnumerable<EnableIntegratedServicePulseOption> ErrorEnableIntegratedServicePulseOptions =>
ServiceControl.EnableIntegratedServicePulseOptions;

public EnableIntegratedServicePulseOption ErrorEnableIntegratedServicePulse
{
get => ServiceControl.EnableIntegratedServicePulse;
set => ServiceControl.EnableIntegratedServicePulse = value;
}

/* Add Audit Instance */

public string AuditInstanceName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ public ServiceControlInformation(ServiceControlEditorViewModel viewModelParent)
Value = false
}
};
EnableIntegratedServicePulseOptions = new[]
{
new EnableIntegratedServicePulseOption
{
Name = "On",
Value = true
},
new EnableIntegratedServicePulseOption
{
Name = "Off",
Value = false
}
};
ErrorRetention = SettingConstants.ErrorRetentionPeriodDefaultInDaysForUI;
Description = "ServiceControl Service";
HostName = "localhost";
Expand All @@ -48,6 +61,7 @@ public ServiceControlInformation(ServiceControlEditorViewModel viewModelParent)
PortNumber = "33333";
DatabaseMaintenancePortNumber = "33334";
EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodiesOptions.First(p => p.Value); //Default to On.
EnableIntegratedServicePulse = EnableIntegratedServicePulseOptions.First(p => p.Value); //Default to On.
ViewModelParent = viewModelParent;
}

Expand Down Expand Up @@ -92,6 +106,10 @@ public ForwardingOption ErrorForwarding

public EnableFullTextSearchOnBodiesOption EnableFullTextSearchOnBodies { get; set; }

public IEnumerable<EnableIntegratedServicePulseOption> EnableIntegratedServicePulseOptions { get; }

public EnableIntegratedServicePulseOption EnableIntegratedServicePulse { get; set; }

protected void UpdateErrorRetention(TimeSpan value)
{
ErrorRetention = ErrorRetentionUnits == TimeSpanUnits.Days ? value.TotalDays : value.TotalHours;
Expand Down Expand Up @@ -122,6 +140,7 @@ public void UpdateFromInstance(ServiceControlInstance instance)
ErrorForwardingQueueName = instance.ErrorLogQueue;
UpdateErrorRetention(instance.ErrorRetentionPeriod);
EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodiesOptions.FirstOrDefault(p => p.Value == instance.EnableFullTextSearchOnBodies);
EnableIntegratedServicePulse = EnableIntegratedServicePulseOptions.FirstOrDefault(p => p.Value == instance.EnableIntegratedServicePulse);
}

ForwardingOption errorForwarding;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
<GroupBox Grid.Row="4"
Grid.Column="1"
Visibility="{Binding HasBrowsableUrl, Converter={StaticResource boolToVis}}"
Header="URL"
Header="{Binding UrlHeading}"
HeaderTemplate="{StaticResource SimpleHeaderedGroupBox}">
<Hyperlink Command="{Binding OpenUrl}" CommandParameter="{Binding BrowsableUrl}">
<Hyperlink.ContextMenu>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ public string BrowsableUrl

public bool HasBrowsableUrl => ServiceInstance is IURLInfo;

public string UrlHeading
{
get
{
if (IsServiceControlInstance)
{
if (ServiceControlInstance.EnableIntegratedServicePulse)
{
return "SERVICEPULSE";
}
}

return "URL";
}
}

public string InstallPath => ((IServicePaths)ServiceInstance).InstallPath;

public string DBPath => GetDBPathIfAvailable();
Expand Down Expand Up @@ -291,6 +307,7 @@ public Task HandleAsync(PostRefreshInstances message, CancellationToken cancella
NotifyOfPropertyChange("HasNewVersion");
NotifyOfPropertyChange("Transport");
NotifyOfPropertyChange("BrowsableUrl");
NotifyOfPropertyChange("UrlHeading");
return Task.CompletedTask;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ async Task Save()
instance.DatabaseMaintenancePort = !string.IsNullOrWhiteSpace(viewModel.ServiceControl.DatabaseMaintenancePortNumber) ? Convert.ToInt32(viewModel.ServiceControl.DatabaseMaintenancePortNumber) : null;
instance.VirtualDirectory = null;
instance.ForwardErrorMessages = viewModel.ServiceControl.ErrorForwarding.Value;
instance.EnableIntegratedServicePulse = viewModel.ServiceControl.EnableIntegratedServicePulse.Value;
instance.ErrorQueue = viewModel.ServiceControl.ErrorQueueName;
instance.ErrorLogQueue = viewModel.ServiceControl.ErrorForwardingQueueName;
instance.ErrorRetentionPeriod = viewModel.ServiceControl.ErrorRetentionPeriod;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@
Header="FULL TEXT SEARCH ON MESSAGE BODIES"
ItemsSource="{Binding EnableFullTextSearchOnBodiesOptions}"
SelectedValue="{Binding EnableFullTextSearchOnBodies}" />
<controls:FormComboBox HorizontalAlignment="Stretch"
VerticalAlignment="Top"
DisplayMemberPath="Name"
Header="ENABLE INTEGRATED SERVICEPULSE"
ItemsSource="{Binding EnableIntegratedServicePulseOptions}"
SelectedValue="{Binding EnableIntegratedServicePulse}" />
</StackPanel>
</sie:SharedServiceControlEditorView.SharedContent>
</sie:SharedServiceControlEditorView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public void UpdateInstanceFromViewModel(ServiceControlInstance instance)
instance.ConnectionString = ConnectionString;
instance.DatabaseMaintenancePort = Convert.ToInt32(ServiceControl.DatabaseMaintenancePortNumber);
instance.EnableFullTextSearchOnBodies = ServiceControl.EnableFullTextSearchOnBodies.Value;
instance.EnableIntegratedServicePulse = ServiceControl.EnableIntegratedServicePulse.Value;
}

public string InstanceName => ServiceControl.InstanceName;
Expand Down Expand Up @@ -189,6 +190,15 @@ public EnableFullTextSearchOnBodiesOption EnableFullTextSearchOnBodies
set => ServiceControl.EnableFullTextSearchOnBodies = value;
}

public IEnumerable<EnableIntegratedServicePulseOption> EnableIntegratedServicePulseOptions =>
ServiceControl.EnableIntegratedServicePulseOptions;

public EnableIntegratedServicePulseOption EnableIntegratedServicePulse
{
get => ServiceControl.EnableIntegratedServicePulse;
set => ServiceControl.EnableIntegratedServicePulse = value;
}

public bool SubmitAttempted { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public class NewServiceControlInstance : PSCmdlet
[Parameter(Mandatory = false, HelpMessage = "Specify whether to enable full text search on error messages.")]
public SwitchParameter EnableFullTextSearchOnBodies { get; set; } = true;

[Parameter(Mandatory = false, HelpMessage = "Specify whether to enable integrated ServicePulse instance.")]
public SwitchParameter EnableIntegratedServicePulse { get; set; }

[Parameter(Mandatory = false, HelpMessage = "Reuse the specified log, db, and install paths even if they are not empty")]
public SwitchParameter Force { get; set; }

Expand Down Expand Up @@ -172,6 +175,7 @@ protected override void ProcessRecord()
details.TransportPackage = ServiceControlCoreTransports.Find(Transport);
details.SkipQueueCreation = SkipQueueCreation;
details.EnableFullTextSearchOnBodies = EnableFullTextSearchOnBodies;
details.EnableIntegratedServicePulse = EnableIntegratedServicePulse;

var modulePath = Path.GetDirectoryName(MyInvocation.MyCommand.Module.Path);

Expand Down
Loading