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 @@ -73,7 +73,8 @@ protected override void OnParametersSet()
OnViewDetails,
CommandSelected,
IsCommandExecuting,
showConsoleLogsItem: true);
showConsoleLogsItem: true,
showUrls: false);

// If display is desktop then we display highlighted commands next to the ... button.
if (ViewportInformation.IsDesktop)
Expand Down
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ private void UpdateMenuButtons()
},
ExecuteResourceCommandAsync,
(resource, command) => DashboardCommandExecutor.IsExecuting(resource.Name, command.Name),
showConsoleLogsItem: false);
showConsoleLogsItem: false,
showUrls: true);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Aspire.Dashboard/Components/Pages/Resources.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,8 @@ private async Task ShowContextMenuAsync(ResourceViewModel resource, int clientX,
(buttonId) => ShowResourceDetailsAsync(resource, buttonId),
(command) => ExecuteResourceCommandAsync(resource, command),
(resource, command) => DashboardCommandExecutor.IsExecuting(resource.Name, command.Name),
showConsoleLogsItem: true);
showConsoleLogsItem: true,
showUrls: true);

// The previous context menu should always be closed by this point but complete just in case.
_contextMenuClosedTcs?.TrySetResult();
Expand Down
34 changes: 33 additions & 1 deletion src/Aspire.Dashboard/Model/ResourceMenuItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static class ResourceMenuItems
private static readonly Icon s_structuredLogsIcon = new Icons.Regular.Size16.SlideTextSparkle();
private static readonly Icon s_tracesIcon = new Icons.Regular.Size16.GanttChart();
private static readonly Icon s_metricsIcon = new Icons.Regular.Size16.ChartMultiple();
private static readonly Icon s_linkIcon = new Icons.Regular.Size16.Link();

public static void AddMenuItems(
List<MenuButtonItem> menuItems,
Expand All @@ -30,7 +31,8 @@ public static void AddMenuItems(
Func<string?, Task> onViewDetails,
Func<CommandViewModel, Task> commandSelected,
Func<ResourceViewModel, CommandViewModel, bool> isCommandExecuting,
bool showConsoleLogsItem)
bool showConsoleLogsItem,
bool showUrls)
{
menuItems.Add(new MenuButtonItem
{
Expand Down Expand Up @@ -115,5 +117,35 @@ public static void AddMenuItems(
});
}
}

if (showUrls)
{
var urls = ResourceUrlHelpers.GetUrls(resource, includeInternalUrls: false, includeNonEndpointUrls: true)
.Where(u => !string.IsNullOrEmpty(u.Url))
.ToList();

if (urls.Count > 0)
{
menuItems.Add(new MenuButtonItem { IsDivider = true });
}

foreach (var url in urls)
{
// Opens the URL in a new window when clicked.
// It's important that this is done in the onclick event so the browser popup allows it.
menuItems.Add(new MenuButtonItem
{
Text = url.Text,
Tooltip = url.Url,
Icon = s_linkIcon,
AdditionalAttributes = new Dictionary<string, object>
{
["data-openbutton"] = "true",
["data-url"] = url.Url!,
["data-target"] = "_blank"
}
});
}
}
}
}
19 changes: 15 additions & 4 deletions src/Aspire.Dashboard/wwwroot/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@ function getFluentMenuItemForTarget(element) {
return null;
}

// Register a global click event listener to handle copy button clicks.
// Register a global click event listener to handle copy/open button clicks.
// Required because an "onclick" attribute is denied by CSP.
document.addEventListener("click", function (e) {
// The copy 'button' could either be a button or a menu item.
const targetElement = isElementTagName(e.target, "fluent-button") ? e.target : getFluentMenuItemForTarget(e.target)
if (targetElement && targetElement.getAttribute("data-copybutton")) {
buttonCopyTextToClipboard(targetElement);
const targetElement = isElementTagName(e.target, "fluent-button") ? e.target : getFluentMenuItemForTarget(e.target);
if (targetElement) {
if (targetElement.getAttribute("data-copybutton")) {
buttonCopyTextToClipboard(targetElement);
} else if (targetElement.getAttribute("data-openbutton")) {
buttonOpenLink(targetElement);
}
e.stopPropagation();
}
});
Expand Down Expand Up @@ -114,6 +118,13 @@ function isScrolledToBottom(container) {
return difference < marginOfError;
}

window.buttonOpenLink = function (element) {
const url = element.getAttribute("data-url");
const target = element.getAttribute("data-target");

window.open(url, target, "noopener,noreferrer");
}

window.buttonCopyTextToClipboard = function(element) {
const text = element.getAttribute("data-text");
const precopy = element.getAttribute("data-precopy");
Expand Down