diff --git a/src/Aspire.Dashboard/Components/Controls/AspireMenu.razor b/src/Aspire.Dashboard/Components/Controls/AspireMenu.razor
index 7010070475f..380585c500e 100644
--- a/src/Aspire.Dashboard/Components/Controls/AspireMenu.razor
+++ b/src/Aspire.Dashboard/Components/Controls/AspireMenu.razor
@@ -6,9 +6,16 @@
+
+@code {
+ private RenderFragment RenderMenuItem(MenuButtonItem item)
+ {
+ if (item.IsDivider)
{
-
+ return @;
}
else
{
@@ -17,15 +24,30 @@
{ "title", !string.IsNullOrEmpty(item.Tooltip) ? item.Tooltip : item.Text ?? string.Empty }
};
-
- @item.Text
+ return @
@if (item.Icon != null)
{
}
-
+ ;
}
}
-
+
+ private RenderFragment? RenderNestedItems(MenuButtonItem item)
+ {
+ if (item.NestedMenuItems is null || item.NestedMenuItems.Count == 0)
+ {
+ return null;
+ }
+
+ // div is required because a single element has to be returned.
+ return @
+ @foreach (var nestedItem in item.NestedMenuItems)
+ {
+ @RenderMenuItem(nestedItem)
+ }
+
;
+ }
+}
diff --git a/src/Aspire.Dashboard/Model/MenuButtonItem.cs b/src/Aspire.Dashboard/Model/MenuButtonItem.cs
index 57b254733af..237f3f792ac 100644
--- a/src/Aspire.Dashboard/Model/MenuButtonItem.cs
+++ b/src/Aspire.Dashboard/Model/MenuButtonItem.cs
@@ -8,6 +8,7 @@ namespace Aspire.Dashboard.Model;
public class MenuButtonItem
{
public bool IsDivider { get; set; }
+ public List? NestedMenuItems { get; set; }
public string? Text { get; set; }
public string? Tooltip { get; set; }
public Icon? Icon { get; set; }
diff --git a/src/Aspire.Dashboard/Model/ResourceMenuItems.cs b/src/Aspire.Dashboard/Model/ResourceMenuItems.cs
index d39148fe436..134a002c910 100644
--- a/src/Aspire.Dashboard/Model/ResourceMenuItems.cs
+++ b/src/Aspire.Dashboard/Model/ResourceMenuItems.cs
@@ -19,6 +19,8 @@ public static class ResourceMenuItems
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();
+ private static readonly Icon s_toolboxIcon = new Icons.Regular.Size16.Toolbox();
+ private static readonly Icon s_linkMultipleIcon = new Icons.Regular.Size16.LinkMultiple();
public static void AddMenuItems(
List menuItems,
@@ -56,6 +58,75 @@ public static void AddMenuItems(
});
}
+ AddTelemetryMenuItems(menuItems, resource, navigationManager, telemetryRepository, getResourceName, loc);
+
+ AddCommandMenuItems(menuItems, resource, loc, commandsLoc, commandSelected, isCommandExecuting);
+
+ if (showUrls)
+ {
+ AddUrlMenuItems(menuItems, resource, loc);
+ }
+ }
+
+ private static void AddUrlMenuItems(List menuItems, ResourceViewModel resource, IStringLocalizer loc)
+ {
+ var urls = ResourceUrlHelpers.GetUrls(resource, includeInternalUrls: false, includeNonEndpointUrls: true)
+ .Where(u => !string.IsNullOrEmpty(u.Url))
+ .ToList();
+
+ if (urls.Count == 0)
+ {
+ return;
+ }
+
+ menuItems.Add(new MenuButtonItem { IsDivider = true });
+
+ if (urls.Count > 5)
+ {
+ var urlItems = new List();
+
+ foreach (var url in urls)
+ {
+ urlItems.Add(CreateUrlMenuItem(url));
+ }
+
+ menuItems.Add(new MenuButtonItem
+ {
+ Text = loc[nameof(Resources.Resources.ResourceActionUrlsText)],
+ Tooltip = "", // No tooltip for the commands menu item.
+ Icon = s_linkMultipleIcon,
+ NestedMenuItems = urlItems
+ });
+ }
+ else
+ {
+ foreach (var url in urls)
+ {
+ menuItems.Add(CreateUrlMenuItem(url));
+ }
+ }
+ }
+
+ private static MenuButtonItem CreateUrlMenuItem(DisplayedUrl url)
+ {
+ // 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.
+ return new MenuButtonItem
+ {
+ Text = url.Text,
+ Tooltip = url.Url,
+ Icon = s_linkIcon,
+ AdditionalAttributes = new Dictionary
+ {
+ ["data-openbutton"] = "true",
+ ["data-url"] = url.Url!,
+ ["data-target"] = "_blank"
+ }
+ };
+ }
+
+ private static void AddTelemetryMenuItems(List menuItems, ResourceViewModel resource, NavigationManager navigationManager, TelemetryRepository telemetryRepository, Func getResourceName, IStringLocalizer loc)
+ {
// Show telemetry menu items if there is telemetry for the resource.
var telemetryApplication = telemetryRepository.GetApplicationByCompositeName(resource.Name);
if (telemetryApplication != null)
@@ -104,58 +175,69 @@ public static void AddMenuItems(
});
}
}
+ }
+ private static void AddCommandMenuItems(List menuItems, ResourceViewModel resource, IStringLocalizer loc, IStringLocalizer commandsLoc, EventCallback commandSelected, Func isCommandExecuting)
+ {
var menuCommands = resource.Commands
- .Where(c => c.State != CommandViewModelState.Hidden)
- .OrderBy(c => !c.IsHighlighted)
- .ToList();
- if (menuCommands.Count > 0)
+ .Where(c => c.State != CommandViewModelState.Hidden)
+ .ToList();
+
+ if (menuCommands.Count == 0)
{
- menuItems.Add(new MenuButtonItem { IsDivider = true });
+ return;
+ }
- foreach (var command in menuCommands)
- {
- var icon = (!string.IsNullOrEmpty(command.IconName) && IconResolver.ResolveIconName(command.IconName, IconSize.Size16, command.IconVariant) is { } i) ? i : null;
+ var highlightedMenuCommands = menuCommands.Where(c => c.IsHighlighted).ToList();
+ var otherMenuCommands = menuCommands.Where(c => !c.IsHighlighted).ToList();
- menuItems.Add(new MenuButtonItem
- {
- Text = command.GetDisplayName(commandsLoc),
- Tooltip = command.GetDisplayDescription(commandsLoc),
- Icon = icon,
- OnClick = () => commandSelected.InvokeAsync(command),
- IsDisabled = command.State == CommandViewModelState.Disabled || isCommandExecuting(resource, command)
- });
- }
+ menuItems.Add(new MenuButtonItem { IsDivider = true });
+
+ // Always show the highlighted commands first and not in a sub-menu.
+ foreach (var highlightedCommand in highlightedMenuCommands)
+ {
+ menuItems.Add(CreateMenuItem(highlightedCommand));
}
- if (showUrls)
+ // If there are more than 5 commands, we group them under a "Commands" menu item. This is done to avoid the menu going off the end of the screen.
+ // A scenario where this could happen is viewing the menu for a resource and the resource is in the middle of the screen.
+ if (highlightedMenuCommands.Count + otherMenuCommands.Count > 5)
{
- var urls = ResourceUrlHelpers.GetUrls(resource, includeInternalUrls: false, includeNonEndpointUrls: true)
- .Where(u => !string.IsNullOrEmpty(u.Url))
- .ToList();
+ var commands = new List();
- if (urls.Count > 0)
+ foreach (var command in otherMenuCommands)
{
- menuItems.Add(new MenuButtonItem { IsDivider = true });
+ commands.Add(CreateMenuItem(command));
}
- foreach (var url in urls)
+ menuItems.Add(new MenuButtonItem
{
- // 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
- {
- ["data-openbutton"] = "true",
- ["data-url"] = url.Url!,
- ["data-target"] = "_blank"
- }
- });
+ Text = loc[nameof(Resources.Resources.ResourceActionCommandsText)],
+ Tooltip = "", // No tooltip for the commands menu item.
+ Icon = s_toolboxIcon,
+ NestedMenuItems = commands
+ });
+ }
+ else
+ {
+ foreach (var command in otherMenuCommands)
+ {
+ menuItems.Add(CreateMenuItem(command));
}
}
+
+ MenuButtonItem CreateMenuItem(CommandViewModel command)
+ {
+ var icon = (!string.IsNullOrEmpty(command.IconName) && IconResolver.ResolveIconName(command.IconName, IconSize.Size16, command.IconVariant) is { } i) ? i : null;
+
+ return new MenuButtonItem
+ {
+ Text = command.GetDisplayName(commandsLoc),
+ Tooltip = command.GetDisplayDescription(commandsLoc),
+ Icon = icon,
+ OnClick = () => commandSelected.InvokeAsync(command),
+ IsDisabled = command.State == CommandViewModelState.Disabled || isCommandExecuting(resource, command)
+ };
+ }
}
}
diff --git a/src/Aspire.Dashboard/Model/ResourceViewModel.cs b/src/Aspire.Dashboard/Model/ResourceViewModel.cs
index 3ea4a7fb858..4355a53aac0 100644
--- a/src/Aspire.Dashboard/Model/ResourceViewModel.cs
+++ b/src/Aspire.Dashboard/Model/ResourceViewModel.cs
@@ -260,14 +260,14 @@ public string GetDisplayName(IStringLocalizer loc)
};
}
- public string GetDisplayDescription(IStringLocalizer loc)
+ public string? GetDisplayDescription(IStringLocalizer loc)
{
return Name switch
{
KnownResourceCommands.StartCommand => loc[nameof(Commands.StartCommandDisplayDescription)],
KnownResourceCommands.StopCommand => loc[nameof(Commands.StopCommandDisplayDescription)],
KnownResourceCommands.RestartCommand => loc[nameof(Commands.RestartCommandDisplayDescription)],
- _ => DisplayDescription
+ _ => DisplayDescription is { Length : > 0 } ? DisplayDescription : null
};
}
}
diff --git a/src/Aspire.Dashboard/Resources/Resources.Designer.cs b/src/Aspire.Dashboard/Resources/Resources.Designer.cs
index c90a3a1c089..8bd1d5199e6 100644
--- a/src/Aspire.Dashboard/Resources/Resources.Designer.cs
+++ b/src/Aspire.Dashboard/Resources/Resources.Designer.cs
@@ -1,6 +1,7 @@
//------------------------------------------------------------------------------
//
// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -59,6 +60,15 @@ internal Resources() {
}
}
+ ///
+ /// Looks up a localized string similar to Commands.
+ ///
+ public static string ResourceActionCommandsText {
+ get {
+ return ResourceManager.GetString("ResourceActionCommandsText", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Console logs.
///
@@ -95,6 +105,15 @@ public static string ResourceActionTracesText {
}
}
+ ///
+ /// Looks up a localized string similar to URLs.
+ ///
+ public static string ResourceActionUrlsText {
+ get {
+ return ResourceManager.GetString("ResourceActionUrlsText", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Collapse child resources.
///
diff --git a/src/Aspire.Dashboard/Resources/Resources.resx b/src/Aspire.Dashboard/Resources/Resources.resx
index 678381c0eb2..2fde50b2a68 100644
--- a/src/Aspire.Dashboard/Resources/Resources.resx
+++ b/src/Aspire.Dashboard/Resources/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -288,4 +288,10 @@
Showing <strong>{0} resources</strong>
{0} is a number. This is raw markup, so <strong> and </strong> should not be modified
-
+
+ Commands
+
+
+ URLs
+
+
\ No newline at end of file
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf
index 3f4eedf292d..32886c30ba7 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.cs.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Protokoly konzoly
@@ -22,6 +27,11 @@
Trasování
+
+ URLs
+ URLs
+
+
Collapse child resources
Sbalit podřízené prostředky
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf
index d3c6950d0d6..4d81c1010dc 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.de.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Konsolenprotokolle
@@ -22,6 +27,11 @@
Überwachungen
+
+ URLs
+ URLs
+
+
Collapse child resources
Untergeordnete Ressourcen reduzieren
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf
index 43c21225643..29c44620695 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.es.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Registros de consola
@@ -22,6 +27,11 @@
Seguimientos
+
+ URLs
+ URLs
+
+
Collapse child resources
Contraer recursos secundarios
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf
index d9403d5be57..2c68143a1b5 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.fr.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Journaux de console
@@ -22,6 +27,11 @@
Traces
+
+ URLs
+ URLs
+
+
Collapse child resources
Réduire les ressources enfants
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf
index ca67cae9e5a..bb10ef73583 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.it.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Log della console
@@ -22,6 +27,11 @@
Tracce
+
+ URLs
+ URLs
+
+
Collapse child resources
Comprimi risorse figlio
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf
index 9bc9043f038..f5413e69958 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ja.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
コンソール ログ
@@ -22,6 +27,11 @@
トレース
+
+ URLs
+ URLs
+
+
Collapse child resources
子リソースを折りたたむ
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf
index f5e5463e6b1..8f9226fd4df 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ko.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
콘솔 로그
@@ -22,6 +27,11 @@
추적
+
+ URLs
+ URLs
+
+
Collapse child resources
자식 리소스 축소
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf
index caf59e82f6c..27393feb9c2 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.pl.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Dzienniki konsoli
@@ -22,6 +27,11 @@
Ślady
+
+ URLs
+ URLs
+
+
Collapse child resources
Zwiń zasoby podrzędne
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf
index 318bb1bbcdd..a8ade367808 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.pt-BR.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Logs do console
@@ -22,6 +27,11 @@
Rastreamentos
+
+ URLs
+ URLs
+
+
Collapse child resources
Recolher recursos filho
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf
index 58bf21b277a..396fe69debb 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.ru.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Журналы консоли
@@ -22,6 +27,11 @@
Трассировки
+
+ URLs
+ URLs
+
+
Collapse child resources
Свернуть дочерние ресурсы
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf
index fdf53b94a5a..24bb30206ae 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.tr.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
Konsol günlükleri
@@ -22,6 +27,11 @@
İzlemeler
+
+ URLs
+ URLs
+
+
Collapse child resources
Alt kaynakları daralt
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf
index e4b57b5e48b..d154fdf4410 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hans.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
控制台日志
@@ -22,6 +27,11 @@
跟踪
+
+ URLs
+ URLs
+
+
Collapse child resources
折叠子资源
diff --git a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf
index 131ee3921ac..364d343299c 100644
--- a/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf
+++ b/src/Aspire.Dashboard/Resources/xlf/Resources.zh-Hant.xlf
@@ -2,6 +2,11 @@
+
+ Commands
+ Commands
+
+
Console logs
主控台記錄
@@ -22,6 +27,11 @@
追蹤
+
+ URLs
+ URLs
+
+
Collapse child resources
折疊子資源