Add notification center, command response message, and markdown support to Dashboard#15906
Add notification center, command response message, and markdown support to Dashboard#15906
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15906Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15906" |
There was a problem hiding this comment.
Pull request overview
This PR adds a notification center to the Aspire Dashboard UI and introduces a new Message field/property for resource command responses across the hosting service, backchannel, Dashboard client, and CLI, keeping compatibility with the deprecated ErrorMessage.
Changes:
- Added a notifications panel (bell icon + dialog) and wiring to surface resource command progress/results via
FluentMessageBarProvider. - Extended command result plumbing end-to-end with
Message(proto + hosting DTOs + Dashboard/CLI consumers), deprecatingErrorMessage. - Updated built-in command implementations and tests to emit/expect the new message behavior and added localized success strings.
Reviewed changes
Copilot reviewed 88 out of 92 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/WithHttpCommandTests.cs | Updates test assertions to validate ExecuteCommandResult.Message. |
| tests/Aspire.Hosting.Tests/ResourceCommandServiceTests.cs | Updates tests for Message and new CommandResults.Success(message, result, format) overload. |
| tests/Aspire.Dashboard.Components.Tests/Shared/FluentUISetupHelpers.cs | Registers INotificationService for dashboard component tests. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.zh-Hant.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.zh-Hans.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.tr.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.ru.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.pt-BR.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.pl.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.ko.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.ja.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.it.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.fr.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.es.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.de.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/xlf/CommandStrings.cs.xlf | Adds new localized command success string entries. |
| src/Aspire.Hosting/Resources/CommandStrings.resx | Adds new command success message resources. |
| src/Aspire.Hosting/Resources/CommandStrings.Designer.cs | Regenerates strongly-typed accessors for added resources. |
| src/Aspire.Hosting/ResourceBuilderExtensions.cs | Updates HTTP command failure result to use Message. |
| src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs | Returns success messages for set/delete parameter commands. |
| src/Aspire.Hosting/Dashboard/proto/dashboard_service.proto | Adds message field and deprecates error_message. |
| src/Aspire.Hosting/Dashboard/DashboardServiceData.cs | Switches to returning resolved message with ErrorMessage fallback. |
| src/Aspire.Hosting/Dashboard/DashboardService.cs | Populates proto Message (and deprecated ErrorMessage) on responses. |
| src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs | Adds Message to backchannel response DTO and obsoletes ErrorMessage. |
| src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs | Maps command execution results into backchannel response (incl. Message). |
| src/Aspire.Hosting/ApplicationModel/ResourceCommandService.cs | Uses Message for failures/logging and command-not-found errors. |
| src/Aspire.Hosting/ApplicationModel/ResourceCommandAnnotation.cs | Adds Message to ExecuteCommandResult and updates CommandResults factories. |
| src/Aspire.Hosting/ApplicationModel/CommandsConfigurationExtensions.cs | Adds localized success messages for lifecycle/rebuild commands and updates failure text to Message. |
| src/Aspire.Dashboard/Utils/DashboardUIHelpers.cs | Introduces a dedicated message bar section for notifications. |
| src/Aspire.Dashboard/ServiceClient/Partials.cs | Maps deprecated error_message to message for backward compatibility. |
| src/Aspire.Dashboard/ServiceClient/DashboardClient.cs | Ensures Message is set in local failure responses. |
| src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf | Updates toast title string formats and adds “View response”. |
| src/Aspire.Dashboard/Resources/xlf/Layout.zh-Hant.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.zh-Hans.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.tr.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.ru.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.pt-BR.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.pl.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.ko.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.ja.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.it.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.fr.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.es.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.de.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Layout.cs.xlf | Adds strings for notification center entry points and dialog title. |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.zh-Hant.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.zh-Hans.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.tr.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.ru.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.pt-BR.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.pl.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.ko.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.ja.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.it.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.fr.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.es.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.de.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/xlf/Dialogs.cs.xlf | Adds notification center dialog strings (“Dismiss all”, “No notifications”). |
| src/Aspire.Dashboard/Resources/Resources.resx | Updates toast title formats and adds “View response” resource. |
| src/Aspire.Dashboard/Resources/Resources.Designer.cs | Regenerates strongly-typed accessors for updated resources. |
| src/Aspire.Dashboard/Resources/Layout.resx | Adds notification center strings. |
| src/Aspire.Dashboard/Resources/Layout.Designer.cs | Regenerates strongly-typed accessors for added layout strings. |
| src/Aspire.Dashboard/Resources/Dialogs.resx | Adds notification center dialog strings. |
| src/Aspire.Dashboard/Resources/Dialogs.Designer.cs | Regenerates strongly-typed accessors for added dialog strings. |
| src/Aspire.Dashboard/Model/ResourceCommandResponseViewModel.cs | Adds Message property for command response view model. |
| src/Aspire.Dashboard/Model/NotificationService.cs | Adds singleton implementation tracking unread notification count. |
| src/Aspire.Dashboard/Model/INotificationService.cs | Adds public interface for unread notification count tracking. |
| src/Aspire.Dashboard/Model/DashboardCommandExecutor.cs | Emits notifications and adds “View response” actions for command results. |
| src/Aspire.Dashboard/DashboardWebApplication.cs | Registers INotificationService in dashboard DI. |
| src/Aspire.Dashboard/Components/Layout/NotificationsHeaderButton.razor.cs | Adds bell icon component logic + unread badge binding. |
| src/Aspire.Dashboard/Components/Layout/NotificationsHeaderButton.razor | Adds bell icon button markup + counter badge. |
| src/Aspire.Dashboard/Components/Layout/MobileNavMenu.razor.cs | Adds “Notifications” entry to the mobile nav menu. |
| src/Aspire.Dashboard/Components/Layout/MobileNavMenu.razor | Adds required LaunchNotificationsAsync parameter. |
| src/Aspire.Dashboard/Components/Layout/MainLayout.razor.cs | Adds notification center dialog launch logic. |
| src/Aspire.Dashboard/Components/Layout/MainLayout.razor | Wires header bell button and mobile nav notifications entry. |
| src/Aspire.Dashboard/Components/Dialogs/NotificationsDialog.razor.css | Adds styling for the notifications dialog layout. |
| src/Aspire.Dashboard/Components/Dialogs/NotificationsDialog.razor.cs | Tracks presence of notifications and implements “Dismiss all”. |
| src/Aspire.Dashboard/Components/Dialogs/NotificationsDialog.razor | Adds dialog UI and message bar provider for notifications section. |
| src/Aspire.Cli/Mcp/Tools/ExecuteResourceCommandTool.cs | Prefers Message with ErrorMessage fallback when reporting failures. |
| src/Aspire.Cli/Commands/ResourceCommandHelper.cs | Prefers Message with ErrorMessage fallback for CLI error output. |
Files not reviewed (4)
- src/Aspire.Dashboard/Resources/Dialogs.Designer.cs: Language not supported
- src/Aspire.Dashboard/Resources/Layout.Designer.cs: Language not supported
- src/Aspire.Dashboard/Resources/Resources.Designer.cs: Language not supported
- src/Aspire.Hosting/Resources/CommandStrings.Designer.cs: Language not supported
Comments suppressed due to low confidence (1)
src/Aspire.Dashboard/Model/DashboardCommandExecutor.cs:156
- If
dashboardClient.ExecuteResourceCommandAsync(...)throws (network failure, server error, etc.), the in-progress notification message bar (progressMessage) is never closed and the unread count was already incremented. Wrap the remote call in a try/catch/finally that closes the progress message (and ideally disposes theCancellationTokenSource) so the notification center doesn't accumulate stuck "starting" entries on failures.
var messageBarStartingTitle = string.Format(CultureInfo.InvariantCulture, loc[nameof(Dashboard.Resources.Resources.ResourceCommandStarting)], command.GetDisplayName());
var toastStartingTitle = $"{getResourceName(resource)} {messageBarStartingTitle}";
// Add a message bar to the notification center section for rendering via FluentMessageBarProvider.
var progressMessage = await messageService.ShowMessageBarAsync(options =>
{
options.Title = messageBarStartingTitle;
options.Intent = MessageIntent.Info;
options.Section = DashboardUIHelpers.NotificationSection;
options.AllowDismiss = true;
options.Timestamp = DateTime.Now;
}).ConfigureAwait(false);
notificationService.IncrementUnreadCount();
// When a resource command starts a toast is immediately shown.
// The toast is open for a certain amount of time and then automatically closed.
// When the resource command is finished the status is displayed in a toast.
// Either the open toast is updated and its time is exteneded, or the a new toast is shown with the finished status.
// Because of this logic we need to manage opening and closing the toasts manually.
var toastParameters = new ToastParameters<CommunicationToastContent>()
{
Id = Guid.NewGuid().ToString(),
Intent = ToastIntent.Progress,
Title = toastStartingTitle,
Content = new CommunicationToastContent(),
Timeout = 0 // App logic will handle closing the toast
};
// Track whether toast is closed by timeout or user action.
var toastClosed = false;
Action<string?> closeCallback = (id) =>
{
if (id == toastParameters.Id)
{
toastClosed = true;
}
};
ResourceCommandResponseViewModel response;
CancellationTokenSource closeToastCts;
try
{
toastService.OnClose += closeCallback;
// Show a toast immediately to indicate the command is starting.
toastService.ShowCommunicationToast(toastParameters);
closeToastCts = new CancellationTokenSource();
closeToastCts.Token.Register(() =>
{
toastService.CloseToast(toastParameters.Id);
});
closeToastCts.CancelAfter(DashboardUIHelpers.ToastTimeout);
response = await dashboardClient.ExecuteResourceCommandAsync(resource.Name, resource.ResourceType, command, CancellationToken.None).ConfigureAwait(false);
}
finally
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
|
joperezr
left a comment
There was a problem hiding this comment.
Nice feature addition! Left a few comments — the proto wire compatibility and the removed API overload (minor release) are the most important ones to address. The rest are smaller improvements around accessibility, theming, and resource cleanup.
src/Aspire.Dashboard/Components/Dialogs/NotificationEntryComponent.razor
Outdated
Show resolved
Hide resolved
src/Aspire.Dashboard/Components/Dialogs/NotificationEntryComponent.razor.css
Outdated
Show resolved
Hide resolved
src/Aspire.Dashboard/Components/Dialogs/NotificationEntryComponent.razor
Outdated
Show resolved
Hide resolved
|
@JamesNK assuming copilot filled in the pr template, but of course this will need to be documented in our docs so please log an issue in aspire.dev to track documenting this |
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
|
joperezr
left a comment
There was a problem hiding this comment.
All previous feedback has been addressed. LGTM!
|
|
||
| @* Timestamp *@ | ||
| <div class="notification-entry-time"> | ||
| @((TimeProvider.GetUtcNow() - Entry.Timestamp).ToTimeAgo()) |
There was a problem hiding this comment.
nit: The relative timestamp (ToTimeAgo()) is computed at render time and won't auto-refresh while the dialog stays open — so "5 minutes ago" will remain frozen until something triggers a re-render. I think this is fine as-is (notification centers are typically opened briefly, and it refreshes whenever new notifications arrive), but wanted to flag it in case others feel differently.
| serviceBuilder.WithHttpCommand("/nested-trace-spans", "Out of order nested spans", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" }); | ||
| serviceBuilder.WithHttpCommand("/exemplars-no-span", "Examplars with no span", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" }); | ||
| serviceBuilder.WithHttpCommand("/genai-trace", "Gen AI trace", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" }); | ||
| serviceBuilder.WithHttpCommand("/genai-langchain-trace", "Gen AI LangChain trace", commandOptions: new() { Method = HttpMethod.Get, IconName = "ContentViewGalleryLightning" }); |
There was a problem hiding this comment.
I'm not sure what to do about it, but I I don't like that every time I execute a value-less command there's a persistent notification. I don't want to have to click into the notification center to remove them each time.
There was a problem hiding this comment.
It's just a number displayed in the header. You don't have to dismiss it.
For an example of prior art, every time you start/restart/stop an AppService in Azure Portal, a notification is added for the operation.
| { | ||
| var connectionString = $"Server=localhost,1433;Database=StressDb;User Id=sa;Password={Guid.NewGuid():N};TrustServerCertificate=true"; | ||
| return Task.FromResult(CommandResults.Success(connectionString, CommandResultFormat.Text)); | ||
| return Task.FromResult(CommandResults.Success("Retrieved connection string.", new CommandResultData { Value = connectionString, DisplayImmediately = true })); |
There was a problem hiding this comment.
Same thing with this command, if the pop-up shows the command result, why do I also need a notification if the command succeeded?
There was a problem hiding this comment.
It's a history of what you've done. I think it would be unexpected if they weren't consistently added.
| .WithCommand( | ||
| name: "migrate-database", | ||
| displayName: "Migrate Database", | ||
| executeCommand: (c) => |
There was a problem hiding this comment.
Why is the experience not consistent with no pop-ups showing up for Migrate Database when there is one for Get Connection String? It feels a little incongruous
There was a problem hiding this comment.
They're test commands. It's for testing different scenarios.
|
|
||
| private static void DisplayCommandResult(IInteractionService interactionService, ExecuteResourceCommandResult result) | ||
| { | ||
| if (string.Equals(result.Format, "markdown", StringComparison.OrdinalIgnoreCase)) |
| { | ||
| <div class="notifications-dismiss"> | ||
| <FluentButton Appearance="Appearance.Lightweight" OnClick="@DismissAll"> | ||
| @Loc[nameof(Dialogs.NotificationCenterDismissAll)] |
There was a problem hiding this comment.
The UI follows what Azure Portal does for notifications and has the dismiss all button below the title and above the notifications.
We don't care as much about UI real estate compared to VS Code.
| TrapFocus = true, | ||
| Modal = true, | ||
| Alignment = HorizontalAlignment.Right, | ||
| Width = "350px", |
There was a problem hiding this comment.
should we make this controllable?
There was a problem hiding this comment.
Pardon? This is the same width as the settings dialog. Everything wraps so it should be fine.
However, I'll add a test with a very long message to double check it displays ok. Probably should truncate it and then display a tooltip.
| /// <summary> | ||
| /// Gets the number of notifications added since the dialog was last opened. | ||
| /// </summary> | ||
| int UnreadCount { get; } |
There was a problem hiding this comment.
why is this a property instead of a method? just curious, since it has a setter function
There was a problem hiding this comment.
It doesn't have a setter function?
It's a property because accessing the value is fast and has no side effects. i.e. it behaves like a readonly field. A property is better than a method in this situation.
…mponent - Make INotificationService a singleton that stores notification data (AddNotification, ReplaceNotification, RemoveNotification, ClearAll) - Remove dependency on IMessageService for notification center - Create NotificationEntryComponent styled like FluentMessageBar notification - Update NotificationsDialog to iterate over stored notifications - Update DashboardCommandExecutor to use notification service directly - Fix CancellationTokenSource disposal in toast auto-close logic
…e limit, accessibility, and CSS variables
…d handlers, fix test DI
0566d92 to
0a23e40
Compare
|
🎬 CLI E2E Test Recordings — 56 recordings uploaded (commit View recordings
📹 Recordings uploaded automatically from CI run #24117741289 |
📝 Documentation updates draftedDocumentation updates for the changes in this PR have been prepared for microsoft/aspire.dev. The docs workflow couldn't create the draft PR automatically (the Files to update
|

Description
Add a notification center to the Aspire Dashboard, enhance resource command responses with a
Messageproperty throughout the stack, and add markdown rendering support to the text visualizer.Notification Center
NotificationEntryComponentin a scrollable panel with a "Dismiss all" action.INotificationServicethread-safe singleton tracks notifications with an unread count badge on the bell icon, reset when the dialog is opened.TextVisualizerDialogwhen a command result is present.Command Response Message
optional string message = 3field to protoResourceCommandResponse, deprecatingerror_message(field 2).Messageproperty toExecuteCommandResult, markingErrorMessageas[Obsolete].Messageproperty toExecuteResourceCommandResponse(backchannel), markingErrorMessageas[Obsolete].CommandResultData/ResourceCommandResulttypes withValue,Format, andDisplayImmediatelyproperties.CommandResultFormat.Markdownenum value."Start" succeeded); the body/details carry the full message from the server.Messagewith fallback toErrorMessagefor backward compatibility.Markdown Support
CommandResultFormat.Markdownto hosting and dashboard.TextVisualizerDialogrenders markdown content usingMarkdownRendererwhen format is markdown.DisplayMarkdownnow accepts an optionalConsoleOutputparameter for output routing.Code Generation
CommandResultDatatype.Checklist
<remarks/>and<code/>elements on your triple slash comments?aspire.devissue: