ResourceBreadCrumbComponent.razor 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. @using RackPeek.Domain.Persistence
  2. @using RackPeek.Domain.Resources
  3. @inject IResourceCollection Repo
  4. <div class="text-sm text-zinc-300 flex items-stretch gap-3">
  5. @for (var i = 0; i < Levels.Count; i++)
  6. {
  7. if (i > 0)
  8. {
  9. <div class="flex items-center text-zinc-500">/</div>
  10. }
  11. <div class="flex">
  12. <CrumbLevel Items="Levels[i]" />
  13. </div>
  14. }
  15. </div>
  16. @code {
  17. [Parameter, EditorRequired]
  18. public ResourceType ResourceType { get; set; }
  19. public string Kind { get; set; }
  20. [Parameter, EditorRequired]
  21. public string ResourceName { get; set; } = default!;
  22. private List<List<Breadcrumb>> Levels { get; } = new();
  23. protected override async Task OnParametersSetAsync()
  24. {
  25. Kind = await Repo.GetKind(ResourceName) ?? "";
  26. Levels.Clear();
  27. switch (ResourceType)
  28. {
  29. case ResourceType.Hardware:
  30. AddLevel(new Breadcrumb(
  31. ResourceName,
  32. Kind,
  33. Resource.GetResourceUrl("hardware", ResourceName)));
  34. break;
  35. case ResourceType.System:
  36. await BuildFromNode(ResourceName, "system");
  37. break;
  38. case ResourceType.Service:
  39. await BuildFromNode(ResourceName, "service");
  40. break;
  41. }
  42. }
  43. private async Task BuildFromNode(string name, string kind)
  44. {
  45. var resource = await Repo.GetByNameAsync(name);
  46. var parents = resource?.RunsOn ?? Enumerable.Empty<string>();
  47. var byDistance = await BuildAncestorGraph(parents);
  48. RenderLevels(byDistance);
  49. AddLevel(new Breadcrumb(
  50. name,
  51. kind,
  52. Resource.GetResourceUrl(kind, name)));
  53. }
  54. private void RenderLevels(Dictionary<int, List<(string Name, string Kind)>> byDistance)
  55. {
  56. foreach (var dist in byDistance.Keys.OrderByDescending(x => x))
  57. {
  58. var items = byDistance[dist];
  59. var hardware = items
  60. .Where(x => Resource.IsHardware(x.Kind))
  61. .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
  62. .Select(x => new Breadcrumb(
  63. x.Name,
  64. x.Kind,
  65. Resource.GetResourceUrl(x.Kind, x.Name)));
  66. var systems = items
  67. .Where(x => x.Kind == "system")
  68. .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
  69. .Select(x => new Breadcrumb(
  70. x.Name,
  71. x.Kind,
  72. Resource.GetResourceUrl(x.Kind, x.Name)));
  73. AddLevel(hardware);
  74. AddLevel(systems);
  75. }
  76. }
  77. private async Task<Dictionary<int, List<(string Name, string Kind)>>>
  78. BuildAncestorGraph(IEnumerable<string> startingNodes)
  79. {
  80. var byDistance = new Dictionary<int, List<(string, string)>>();
  81. var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  82. var queue = new Queue<(string Name, int Dist)>();
  83. foreach (var node in startingNodes
  84. .Where(x => !string.IsNullOrWhiteSpace(x))
  85. .Distinct(StringComparer.OrdinalIgnoreCase))
  86. {
  87. queue.Enqueue((node, 1));
  88. }
  89. while (queue.Count > 0)
  90. {
  91. var (name, dist) = queue.Dequeue();
  92. if (!visited.Add(name))
  93. continue;
  94. var kind = await Repo.GetKind(name);
  95. if (kind is null)
  96. continue;
  97. kind = kind.Trim().ToLowerInvariant();
  98. if (!byDistance.TryGetValue(dist, out var list))
  99. {
  100. list = new List<(string, string)>();
  101. byDistance[dist] = list;
  102. }
  103. list.Add((name, kind));
  104. if (kind == "system")
  105. {
  106. var res = await Repo.GetByNameAsync(name);
  107. foreach (var parent in (res?.RunsOn ?? Enumerable.Empty<string>())
  108. .Where(x => !string.IsNullOrWhiteSpace(x))
  109. .Distinct(StringComparer.OrdinalIgnoreCase))
  110. {
  111. queue.Enqueue((parent, dist + 1));
  112. }
  113. }
  114. }
  115. return byDistance;
  116. }
  117. private void AddLevel(params Breadcrumb[] items)
  118. => AddLevel((IEnumerable<Breadcrumb>)items);
  119. private void AddLevel(IEnumerable<Breadcrumb> items)
  120. {
  121. var list = items.ToList();
  122. if (list.Count > 0)
  123. Levels.Add(list);
  124. }
  125. public record Breadcrumb(string Label, string Kind, string Href);
  126. }