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 @@ @foreach (var item in Items) { - @if (item.IsDivider) + @RenderMenuItem(item) + } + + +@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 折疊子資源