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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
var highlightedCommand = _highlightedCommands[i];

<FluentButton Appearance="ButtonAppearance.Transparent" IconOnly="true" data-tooltip="@(!string.IsNullOrEmpty(highlightedCommand.GetDisplayDescription()) ? highlightedCommand.GetDisplayDescription() : highlightedCommand.GetDisplayName())" aria-label="@(!string.IsNullOrEmpty(highlightedCommand.GetDisplayDescription()) ? highlightedCommand.GetDisplayDescription() : highlightedCommand.GetDisplayName())" OnClick="@(() => CommandSelected.InvokeAsync(highlightedCommand))" Disabled="@(DashboardClient.IsReadOnly || highlightedCommand.State == CommandViewModelState.Disabled || IsCommandExecuting(Resource, highlightedCommand))">
<FluentIcon Value="@IconResolver.ResolveHighlightedCommandIcon(highlightedCommand.IconName, highlightedCommand.IconVariant)" Width="16px" Color="Color.Primary" />
<FluentIcon Value="@IconResolver.ResolveHighlightedCommandIcon(highlightedCommand.IconName, highlightedCommand.IconVariant)" Width="16px" Color="Color.Custom" CustomColor="currentColor" />
</FluentButton>
}
</div>
Expand All @@ -29,7 +29,7 @@
var outputAction = ResourceMenuBuilder.CreateViewOutputMenuItem(Resource, ResourceByName);
}
<FluentButton Appearance="ButtonAppearance.Transparent" IconOnly="true" data-tooltip="@outputAction.Text" aria-label="@outputAction.Text" OnClick="@(outputAction.OnClick!)">
<FluentIcon Value="@outputAction.Icon" Width="16px" Color="Color.Primary" />
<FluentIcon Value="@outputAction.Icon" Width="16px" Color="Color.Custom" CustomColor="currentColor" />
</FluentButton>
</div>

Expand Down
28 changes: 28 additions & 0 deletions src/Aspire.Dashboard/Components/Controls/SpanLogPreview.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@namespace Aspire.Dashboard.Components
@using Aspire.Dashboard.Utils

@* The rich preview the trace page's log markers show in their tooltip. The page renders one, filled with the marker
the pointer or keyboard is on, rather than one per marker: a span can have thousands of logs, and each preview is a
small table. The shared tooltip clones it when it opens and follows its changes while it's open. *@
<div id="@ElementId" class="log-tooltip" hidden>
@if (_logEntry is { } logEntry)
{
<div class="log-tooltip-title-container">
<span class="log-tooltip-title">@(logEntry.EventName ?? Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsEntryDetails)])</span> <span class="log-tooltip-subtitle">@logEntry.ScopeName</span><br />
</div>
<table class="log-tooltip-table">
<tr>
<td class="label-column">@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsLevelColumnHeader)]</td>
<td>@logEntry.Severity</td>
</tr>
<tr>
<td class="label-column">@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsTimestampColumnHeader)]</td>
<td>@FormatHelpers.FormatTimeWithOptionalDate(TimeProvider, logEntry.TimeStamp, MillisecondsDisplay.Truncated)</td>
</tr>
<tr>
<td class="label-column">@Loc[nameof(Dashboard.Resources.StructuredLogs.StructuredLogsMessageColumnHeader)]</td>
<td>@FormatHelpers.TruncateText(logEntry.Message, maxLength: 150)</td>
</tr>
</table>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Dashboard.Model;
using Aspire.Dashboard.Otlp.Storage;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Localization;

namespace Aspire.Dashboard.Components;

public sealed partial class SpanLogPreview : ComponentBase
{
/// <summary>
/// The id of the preview's element, which each log marker names in its <c>data-tooltip-content</c> attribute.
/// </summary>
public const string ElementId = "span-log-preview";

private LogSummary? _logEntry;

[Inject]
public required BrowserTimeProvider TimeProvider { get; init; }

[Inject]
public required IStringLocalizer<Resources.StructuredLogs> Loc { get; init; }

/// <summary>
/// Fills the preview with a log marker's entry, when the pointer or keyboard reaches the marker.
/// </summary>
/// <remarks>
/// The marker's handler targets this component, not the page, so only the preview renders again, not the trace's
/// whole waterfall.
/// </remarks>
public void Show(LogSummary logEntry)
{
if (_logEntry?.InternalId == logEntry.InternalId)
{
return;
}

_logEntry = logEntry;
StateHasChanged();
}

internal LogSummary? LogEntry => _logEntry;
}
13 changes: 8 additions & 5 deletions src/Aspire.Dashboard/Components/Layout/ResourcesLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@
data-pane-location="@_lastLocation"
data-pane-interactions-ready="@(_isOrderingRegistered ? "true" : "false")"
data-home-pending="@(_homeNavigation is not null ? "true" : null)"
data-escape-home="@(CanEscapeGoHome ? "true" : null)"
style="@GetLayoutStyle()"
@ref="_layoutElement">
@* A single header keeps section subscriptions stable across viewport changes. *@
Expand Down Expand Up @@ -505,16 +506,19 @@
<FluentIcon Value="@(new Icons.Regular.Size16.Open())" Color="Color.Primary" />
</a>
}
@* Resolved icons are shared. Set the component color rather than mutating the cached icon with WithColor.
Every action icon takes its button's text color, labeled or not, so an icon reads as part of its button
and dims with it when the button is disabled. *@
@foreach (var command in showLabels ? GetHighlightedCommands(resource) : _isPaneFullCanvas ? GetFullPageRowCommands(resource) : GetSidebarCommands(resource))
{
var commandTitle = !string.IsNullOrEmpty(command.GetDisplayDescription()) ? command.GetDisplayDescription() : command.GetDisplayName();
if (showLabels)
{
<FluentButton Appearance="ButtonAppearance.Default"
data-tooltip="@commandTitle"
IconStart="@IconResolver.ResolveHighlightedCommandIcon(command.IconName, command.IconVariant).WithColor(Color.Primary)"
OnClick="@handlers.Command(command.Name)"
Disabled="@IsCommandDisabled(resource, command)">
<FluentIcon Value="@IconResolver.ResolveHighlightedCommandIcon(command.IconName, command.IconVariant)" Color="Color.Custom" CustomColor="currentColor" Slot="start" />
@command.GetDisplayName()
</FluentButton>
}
Expand All @@ -523,21 +527,20 @@
<FluentButton Appearance="ButtonAppearance.Transparent"
Class="@($"{GetResourceRowActionClass(actionIndex++)} resource-row-highlighted resource-row-command-slot-{highlightedIndex++}")"
IconOnly="true"
IconStart="@(IconResolver.ResolveHighlightedCommandIcon(command.IconName, command.IconVariant).WithColor("currentColor"))"
data-tooltip="@commandTitle"
data-tooltip-positioning="above"
aria-label="@($"{command.GetDisplayName()} {resourceName}")"
OnClick="@handlers.Command(command.Name)"
Disabled="@IsCommandDisabled(resource, command)" />
Disabled="@IsCommandDisabled(resource, command)">
<FluentIcon Value="@IconResolver.ResolveHighlightedCommandIcon(command.IconName, command.IconVariant)" Color="Color.Custom" CustomColor="currentColor" />
</FluentButton>
}
}
@* Every row has its menu, parameters included: it holds what the row's buttons don't, such as Delete
parameter, and a row without it looks broken beside the others. *@
<AspireMenuButton ButtonAppearance="@(showLabels ? ButtonAppearance.Default : ButtonAppearance.Transparent)"
ButtonClass="@(showLabels ? null : "resource-row-action")"
Icon="@(new Icons.Regular.Size16.MoreHorizontal())"
IconColor="Color.Custom"
IconCustomColor="currentColor"
ItemsProvider="@handlers.MenuItems"
data-tooltip-positioning="@(!showLabels ? "above" : null)"
Title="@string.Format(CultureInfo.CurrentCulture, Loc[nameof(Dashboard.Resources.Layout.ResourcePaneResourceActions)], resourceName)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,14 @@ protected override async Task OnParametersSetAsync()
}
}

// Whether Escape, when nothing else uses it, goes to the dashboard's home as the brand does (see
// registerResourceContextMenu in ResourcesLayout.razor.js). Every page beside the docked list can go there. Home
// itself can't, nor a narrow layout, where "/" redirects to the overview because there's no full-page list, nor a
// dashboard without the resource service, whose "/" redirects to the structured logs (see Home). While home is
// loading, Escape would only start the same navigation again.
internal bool CanEscapeGoHome =>
ViewportInformation.IsDesktop && DashboardClient.IsEnabled && !_isPaneFullCanvas && _homeNavigation is null;

internal void OnHomeNavigationStarting()
{
// Home is already showing, so the navigation won't change the location, and nothing would release a tab
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
margin: 6px 0 0;
position-try-fallbacks: flip-inline;
box-sizing: border-box;
width: min(var(--resource-pane-expanded-width, 290px), calc(100vw - 24px));
width: min(330px, calc(100vw - 24px));
max-width: none;
height: max-content;
max-height: stretch;
Expand Down Expand Up @@ -2784,26 +2784,31 @@
}
}

/* Compact sidebar rows show their actions only while the row is hovered or focused, or while one of its menus is
/* Compact sidebar rows show their actions only while the row is hovered or has keyboard-visible focus, or while one of its menus is
open, and then in place of the type and state columns. The rest of the time the action rail is hidden, so the
name gets its room. The swap keeps the name's width about the same: the 92px the rail and its gap gain is about
the 90px the columns give up.
An open menu is matched by aria-haspopup as well as aria-expanded: a parent's expand toggle also sets
aria-expanded="true", and that must not keep its actions showing. Only the attribute's presence is tested
because Fluent's menu changes the button's aria-haspopup from "menu" to "true" while the menu is open.
Mouse selection leaves the row link focused, so :focus-within would keep the actions visible after the pointer leaves.
Keep hidden actions tabbable so moving focus from the row link to an action reveals the rail without losing focus.
Touch screens can't hover, so (hover: hover) leaves their actions always visible. The expanded list (the full
canvas) is excluded and keeps its fixed, always-visible action slots. */
@media (hover: hover) {
.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:not(:hover, :focus-within, :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) {
.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:not(:hover, :has(:focus-visible), :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) {
grid-template-columns: minmax(0, 1fr);
column-gap: 0;
}

.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:not(:hover, :focus-within, :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .resource-row-actions {
display: none;
.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:not(:hover, :has(:focus-visible), :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .resource-row-actions {
position: absolute;
inset-inline-end: 0;
opacity: 0;
pointer-events: none;
}

.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:is(:hover, :focus-within, :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .resource-row-meta {
.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:is(:hover, :has(:focus-visible), :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .resource-row-meta {
display: none;
}

Expand All @@ -2827,13 +2832,22 @@
margin-inline-end: calc(var(--resource-row-error-offset) - var(--resource-row-meta-type-width) - 34px);
}

.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:is(:hover, :focus-within, :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .resource-row-error-link {
.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:is(:hover, :has(:focus-visible), :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .resource-row-error-link {
margin-inline-end: calc(var(--resource-row-error-offset) - 92px);
}

.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:is(:hover, :focus-within, :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .error-badge-spacer {
.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-row:is(:hover, :has(:focus-visible), :has(.resource-row-actions [aria-haspopup][aria-expanded="true"])) .error-badge-spacer {
margin-inline-end: calc(var(--resource-row-error-offset) - 100px);
}

/* At rest the rows' actions are hidden and their state dots form a column at the end of the list, so the group
header's chevron is centered over that column instead of floating just past it. The row link ends 12px before
the row and has 8px of end padding, so the 10px dot's center is 25px from the end; the header ends at the same
line, and the 20px chevron needs 15px after it to center there. The count badge before it moves with it.
Touch screens keep the actions showing, so they have no dot column there and the chevron keeps its place. */
.resources-layout:not(.pane-full-canvas) .resource-pane-list.density-compact .resource-tag-header > .resource-row-toggle {
margin-inline-end: 15px;
}
}

.resource-tree-guide,
Expand Down
Loading
Loading