Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions ProjectCommander.Tests/ProjectCommander.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="9.2.1" />
<PackageReference Include="Aspire.Hosting.Testing" Version="9.3.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
10 changes: 10 additions & 0 deletions ProjectCommander.sln
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectCommander.Tests", "ProjectCommander.Tests\ProjectCommander.Tests.csproj", "{3FA2AEB2-18B5-4DF8-A556-09492F6408D4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpiraLog", "Sample\SpiraLog\SpiraLog.csproj", "{D45D1DF8-C846-1C71-D6DD-AC07B173733A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -58,6 +60,10 @@ Global
{3FA2AEB2-18B5-4DF8-A556-09492F6408D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FA2AEB2-18B5-4DF8-A556-09492F6408D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FA2AEB2-18B5-4DF8-A556-09492F6408D4}.Release|Any CPU.Build.0 = Release|Any CPU
{D45D1DF8-C846-1C71-D6DD-AC07B173733A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D45D1DF8-C846-1C71-D6DD-AC07B173733A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D45D1DF8-C846-1C71-D6DD-AC07B173733A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D45D1DF8-C846-1C71-D6DD-AC07B173733A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -67,5 +73,9 @@ Global
{DECE34B3-8776-4684-A92F-8B9C8A1147A7} = {A12F2526-69A6-444C-82CA-49DAAB8D0C7E}
{62A936C1-61B6-479A-81CB-1B864E093332} = {A12F2526-69A6-444C-82CA-49DAAB8D0C7E}
{6F6EF83D-432C-48DC-8C5C-50356F6A97DA} = {A12F2526-69A6-444C-82CA-49DAAB8D0C7E}
{D45D1DF8-C846-1C71-D6DD-AC07B173733A} = {A12F2526-69A6-444C-82CA-49DAAB8D0C7E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {812F688B-A919-42B2-9FC8-B26C65E615C0}
EndGlobalSection
EndGlobal
2 changes: 1 addition & 1 deletion Sample/Consumer/Consumer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Azure.Messaging.EventHubs" Version="9.2.1" />
<PackageReference Include="Aspire.Azure.Messaging.EventHubs" Version="9.3.0" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 1 addition & 2 deletions Sample/Consumer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

builder.AddServiceDefaults();

builder.AddAzureEventHubConsumerClient("data",
settings => settings.EventHubName = "hub");
builder.AddAzureEventHubConsumerClient("hub");

builder.Services.AddHostedService<ConsumerWorker>();

Expand Down
2 changes: 1 addition & 1 deletion Sample/DataGenerator/DataGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Azure.Messaging.EventHubs" Version="9.2.1" />
<PackageReference Include="Aspire.Azure.Messaging.EventHubs" Version="9.3.0" />
</ItemGroup>

<ItemGroup>
Expand Down
3 changes: 1 addition & 2 deletions Sample/DataGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@

builder.Services.AddAspireProjectCommanderClient();

builder.AddAzureEventHubProducerClient("data",
settings => settings.EventHubName = "hub");
builder.AddAzureEventHubProducerClient("hub");

builder.Services.AddHostedService<DataGeneratorWorker>();

Expand Down
4 changes: 2 additions & 2 deletions Sample/ProjectCommander.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
var commander = builder.AddAspireProjectCommander();

var datahub = builder.AddAzureEventHubs("data")
.AddEventHub("hub")
.RunAsEmulator();
.RunAsEmulator()
.AddHub("hub");

builder.AddProject<Projects.DataGenerator>("datagenerator")
.WithReference(datahub)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />
<Sdk Name="Aspire.AppHost.Sdk" Version="9.3.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -13,8 +13,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.1" />
<PackageReference Include="Aspire.Hosting.Azure.EventHubs" Version="9.2.1" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.0" />
<PackageReference Include="Aspire.Hosting.Azure.EventHubs" Version="9.3.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />

<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.4.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.2.1" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.5.0" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" Version="9.3.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
Expand Down
115 changes: 115 additions & 0 deletions Sample/SpiraLog/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// See https://aka.ms/new-console-template for more information

using Microsoft.AspNetCore.SignalR.Client;
using Spectre.Console;

AnsiConsole.MarkupLine("[green]SpiraLog[/] 0.1");
AnsiConsole.WriteLine();

if (args.Length == 0)
{
AnsiConsole.WriteLine("Usage: spiralog <resourceName> [hubPort]");
return;
}

string resourceName = args[0];

int? port = args.Length == 2 ? int.Parse(args[1]) : null;

//var builder = Host.CreateApplicationBuilder(args);

var hubBuilder = new HubConnectionBuilder()
.WithUrl($"https://localhost:{port ?? 27960}/projectcommander/")
.WithAutomaticReconnect()
.WithKeepAliveInterval(TimeSpan.FromSeconds(120)); // two minute keep alive

await using var hubConnection = hubBuilder.Build();

await hubConnection.StartAsync();

AnsiConsole.MarkupLine($"[green]Connected[/] to Aspire Project Commander Hub for resource '[yellow]{resourceName}[/]'");
AnsiConsole.MarkupLine("Press [bold]SPACE[/] to pause/resume log output");

// Simple pause mechanism
bool isPaused = false;
var pauseEvent = new ManualResetEventSlim(true); // Initially not paused
var cts = new CancellationTokenSource();

// Start a task to monitor for space bar presses
_ = Task.Run(() =>
{
try
{
while (!cts.Token.IsCancellationRequested)
{
if (Console.KeyAvailable)
{
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.Spacebar)
{
isPaused = !isPaused;

if (isPaused)
{
pauseEvent.Reset(); // Block processing
AnsiConsole.MarkupLine("[bold red]PAUSED[/] (Press SPACE to resume)");
}
else
{
pauseEvent.Set(); // Allow processing to continue
AnsiConsole.MarkupLine("[bold green]RESUMED[/]");
}
}
}
Thread.Sleep(50);
}
}
catch (Exception ex)
{
AnsiConsole.MarkupLine($"[red]Error in key monitoring:[/] {ex.Message}");
}
}, cts.Token);

try
{
await foreach (var data in hubConnection.StreamAsync<IReadOnlyList<LogLine>>("WatchResourceLogs", resourceName, null))
{
// Wait here if paused, but respect cancellation
pauseEvent.Wait(cts.Token);

foreach (var (lineNumber, content, isErrorMessage) in data)
{
// Check if we got paused during processing, with cancellation support
pauseEvent.Wait(cts.Token);

// parse out timestamp
var parts = content.Split(' ', 2);
var timestamp = parts[0];
var compactTimestamp = DateTimeOffset.Parse(timestamp).ToLocalTime().ToString("HH:mm:ss.fff");

AnsiConsole.Markup($"[yellow][[{compactTimestamp}]][/] ");

if (isErrorMessage)
{
AnsiConsole.Write("[red]*[/] ");

// may contain embedded escape codes
AnsiConsole.WriteLine(parts.Length > 1 ? parts[1] : string.Empty);
}
else
{
// may contain embedded escape codes
AnsiConsole.WriteLine(parts.Length > 1 ? parts[1] : string.Empty);
}
}
}
}
finally
{
// shutdown key monitoring task
cts.Cancel();
}

public readonly record struct LogLine(int LineNumber, string Content, bool IsErrorMessage);


8 changes: 8 additions & 0 deletions Sample/SpiraLog/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"aspirlog": {
"commandName": "Project",
"commandLineArgs": "datagenerator"
}
}
}
20 changes: 20 additions & 0 deletions Sample/SpiraLog/SpiraLog.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
<PackageReference Include="Spectre.Console" Version="0.50.0" />
<PackageReference Include="Spectre.Console.Analyzer" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion Src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<EnablePackageValidation>true</EnablePackageValidation>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageVersion>1.0.2</PackageVersion>
<PackageVersion>1.1.0</PackageVersion>
<Authors>oisin</Authors>
</PropertyGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="9.2.1" />
<PackageReference Include="Aspire.Hosting" Version="9.3.0" />
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.2.25">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand Down
55 changes: 53 additions & 2 deletions Src/Nivot.Aspire.Hosting.ProjectCommander/ProjectCommanderHub.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,65 @@
using Aspire.Hosting.ApplicationModel;
using Microsoft.AspNetCore.Razor.Hosting;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;

namespace CommunityToolkit.Aspire.Hosting.ProjectCommander;

internal sealed class ProjectCommanderHub(ILogger logger) : Hub
internal sealed class ProjectCommanderHub(ILogger logger, ResourceLoggerService resourceLogger, DistributedApplicationModel appModel) : Hub
{
public async Task Identify(string resourceName)
public async Task Identify([ResourceName] string resourceName)
{
logger.LogInformation("{ResourceName} connected to Aspire Project Commander Hub", resourceName);

await Groups.AddToGroupAsync(Context.ConnectionId, resourceName);
}

public async IAsyncEnumerable<IReadOnlyList<LogLine>> WatchResourceLogs([ResourceName] string resourceName, int? take = null)
{
logger.LogInformation("Getting {LinesWanted} logs for resource {ResourceName}", take?.ToString() ?? "all", resourceName);

int taken = 0;

// resolve IResource from resource name
var resource = appModel.Resources.SingleOrDefault(r => r.Name == resourceName);
if (resource is null)
{
logger.LogWarning("Resource {ResourceName} not found", resourceName);
yield break; // No matching resource found, exit
}

await foreach (var logs in resourceLogger.WatchAsync(resource)
.WithCancellation(Context.ConnectionAborted)
.ConfigureAwait(false))
{
if (take is null)
{
// No limit, return all logs
yield return logs.ToList();
}
else if (taken < take.Value)
{
// Calculate how many more logs we can take
int remaining = take.Value - taken;

if (logs.Count <= remaining)
{
// Return entire batch
yield return logs.ToList();
taken += logs.Count;
}
else
{
// Return partial batch to reach exactly 'take' logs
yield return logs.Take(remaining).ToList();
taken = take.Value;
break;
}
}
else
{
break;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ internal sealed class ProjectCommanderHubLifecycleHook(
public async Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
{
var hubResource = appModel.Resources.OfType<ProjectCommanderHubResource>().Single();

var logger = loggerService.GetLogger(hubResource);
hubResource.SetLogger(logger);
hubResource.SetLogger(loggerService);
hubResource.SetModel(appModel);

await notificationService.PublishUpdateAsync(hubResource, state => state with
{
Expand Down
Loading