Преглед изворни кода

Fixed breadcrumbs / runs on navigation for system to system

Tim Jones пре 1 месец
родитељ
комит
57df0fea3e

+ 24 - 0
RackPeek.Domain/Resources/Resource.cs

@@ -13,6 +13,30 @@ namespace RackPeek.Domain.Resources;
 
 public abstract class Resource
 {
+    private static readonly string[] HardwareTypes =
+        ["server", "switch", "firewall", "router", "accesspoint", "desktop", "laptop", "ups"];
+    
+    public static bool IsHardware(string kind) => HardwareTypes.Contains(kind.Trim().ToLower());
+        
+    public static string GetResourceUrl(string kind, string name)
+    {
+        var encoded = Uri.EscapeDataString(name);
+
+        kind = kind.Trim().ToLower();
+        if (IsHardware(kind))
+        {
+            return $"resources/hardware/{encoded}";
+        }else if (kind == "system")
+        {
+            return $"resources/systems/{encoded}";
+        }else if (kind == "service")
+        {
+            return $"resources/services/{encoded}";
+        }
+
+        return "#";
+    }
+    
     private static readonly Dictionary<string, string> KindToPluralDictionary = new()
     {
         { "hardware", "hardware" },

+ 1 - 1
RackPeek.Domain/RpkConstants.cs

@@ -2,5 +2,5 @@ namespace RackPeek.Domain;
 
 public static class RpkConstants
 {
-    public const string Version = "v1.0.0";
+    public const string Version = "v1.1.0";
 }

+ 1 - 1
RackPeek/RackPeek.csproj

@@ -5,7 +5,7 @@
         <TargetFramework>net10.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
-        <AssemblyVersion>1.0.0</AssemblyVersion>
+        <AssemblyVersion>1.1.0</AssemblyVersion>
     </PropertyGroup>
 
     <ItemGroup>

+ 91 - 47
Shared.Rcl/Components/ResourceBreadCrumbComponent.razor

@@ -1,4 +1,5 @@
 @using RackPeek.Domain.Persistence
+@using RackPeek.Domain.Resources
 @inject IResourceCollection Repo
 
 <div class="text-sm text-zinc-300 flex items-stretch gap-3">
@@ -16,9 +17,12 @@
 </div>
 
 @code {
-    [Parameter] [EditorRequired] public ResourceType ResourceType { get; set; }
-    [Parameter] [EditorRequired] public string ResourceName { get; set; } = default!;
-    
+    [Parameter, EditorRequired] 
+    public ResourceType ResourceType { get; set; }
+
+    [Parameter, EditorRequired] 
+    public string ResourceName { get; set; } = default!;
+
     private List<List<Breadcrumb>> Levels { get; } = new();
 
     protected override async Task OnParametersSetAsync()
@@ -28,77 +32,117 @@
         switch (ResourceType)
         {
             case ResourceType.Hardware:
-                AddLevel(new Breadcrumb(ResourceName, $"resources/hardware/{Uri.EscapeDataString(ResourceName)}"));
+                AddLevel(new Breadcrumb(
+                    ResourceName,
+                    Resource.GetResourceUrl("hardware", ResourceName)));
                 break;
 
             case ResourceType.System:
-                await BuildSystem(ResourceName);
+                await BuildFromNode(ResourceName, "system");
                 break;
 
             case ResourceType.Service:
-                await BuildService(ResourceName);
+                await BuildFromNode(ResourceName, "service");
                 break;
         }
     }
 
-    private void AddLevel(params Breadcrumb[] items)
-        => Levels.Add(items.ToList());
-
-    private void AddLevel(IEnumerable<Breadcrumb> items)
+    private async Task BuildFromNode(string name, string kind)
     {
-        var list = items.ToList();
-        if (list.Count > 0)
-            Levels.Add(list);
-    }
+        var resource = await Repo.GetByNameAsync(name);
+        var parents = resource?.RunsOn ?? Enumerable.Empty<string>();
 
-    private async Task BuildSystem(string systemName)
-    {
-        var system = await Repo.GetByNameAsync(systemName);
+        var byDistance = await BuildAncestorGraph(parents);
 
-        var hwParents = (system?.RunsOn ?? new List<string>())
-            .Where(x => !string.IsNullOrWhiteSpace(x))
-            .Distinct(StringComparer.OrdinalIgnoreCase)
-            .Select(hw => new Breadcrumb(hw, $"resources/hardware/{Uri.EscapeDataString(hw)}"))
-            .OrderBy(b => b.Label, StringComparer.OrdinalIgnoreCase);
+        RenderLevels(byDistance);
 
-        AddLevel(hwParents);
+        AddLevel(new Breadcrumb(
+            name,
+            Resource.GetResourceUrl(kind, name)));
+    }
 
-        AddLevel(new Breadcrumb(systemName, $"resources/systems/{Uri.EscapeDataString(systemName)}"));
+    private void RenderLevels(Dictionary<int, List<(string Name, string Kind)>> byDistance)
+    {
+        foreach (var dist in byDistance.Keys.OrderByDescending(x => x))
+        {
+            var items = byDistance[dist];
+
+            var hardware = items
+                .Where(x => Resource.IsHardware(x.Kind))
+                .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
+                .Select(x => new Breadcrumb(
+                    x.Name,
+                    Resource.GetResourceUrl(x.Kind, x.Name)));
+
+            var systems = items
+                .Where(x => x.Kind == "system")
+                .OrderBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
+                .Select(x => new Breadcrumb(
+                    x.Name,
+                    Resource.GetResourceUrl(x.Kind, x.Name)));
+
+            AddLevel(hardware);
+            AddLevel(systems);
+        }
     }
 
-    private async Task BuildService(string serviceName)
+    private async Task<Dictionary<int, List<(string Name, string Kind)>>> 
+        BuildAncestorGraph(IEnumerable<string> startingNodes)
     {
-        var service = await Repo.GetByNameAsync(serviceName);
+        var byDistance = new Dictionary<int, List<(string, string)>>();
+        var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+        var queue = new Queue<(string Name, int Dist)>();
 
-        var systemParents = (service?.RunsOn ?? new List<string>())
-            .Where(x => !string.IsNullOrWhiteSpace(x))
-            .Distinct(StringComparer.OrdinalIgnoreCase)
-            .ToList();
-        
-        var hwSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+        foreach (var node in startingNodes
+                     .Where(x => !string.IsNullOrWhiteSpace(x))
+                     .Distinct(StringComparer.OrdinalIgnoreCase))
+        {
+            queue.Enqueue((node, 1));
+        }
 
-        foreach (var sysName in systemParents)
+        while (queue.Count > 0)
         {
-            var sys = await Repo.GetByNameAsync(sysName);
-            if (sys?.RunsOn is { Count: > 0 })
+            var (name, dist) = queue.Dequeue();
+            if (!visited.Add(name))
+                continue;
+
+            var kind = await Repo.GetKind(name);
+            if (kind is null)
+                continue;
+
+            kind = kind.Trim().ToLowerInvariant();
+
+            if (!byDistance.TryGetValue(dist, out var list))
             {
-                foreach (var hw in sys.RunsOn.Where(x => !string.IsNullOrWhiteSpace(x)))
-                    hwSet.Add(hw);
+                list = new List<(string, string)>();
+                byDistance[dist] = list;
             }
-        }
 
-        var hwParents = hwSet
-            .Select(hw => new Breadcrumb(hw, $"resources/hardware/{Uri.EscapeDataString(hw)}"))
-            .OrderBy(b => b.Label, StringComparer.OrdinalIgnoreCase);
+            list.Add((name, kind));
 
-        var sysCrumbs = systemParents
-            .Select(sys => new Breadcrumb(sys, $"resources/systems/{Uri.EscapeDataString(sys)}"))
-            .OrderBy(b => b.Label, StringComparer.OrdinalIgnoreCase);
+            if (kind == "system")
+            {
+                var res = await Repo.GetByNameAsync(name);
+                foreach (var parent in (res?.RunsOn ?? Enumerable.Empty<string>())
+                         .Where(x => !string.IsNullOrWhiteSpace(x))
+                         .Distinct(StringComparer.OrdinalIgnoreCase))
+                {
+                    queue.Enqueue((parent, dist + 1));
+                }
+            }
+        }
 
-        AddLevel(hwParents);
-        AddLevel(sysCrumbs);
+        return byDistance;
+    }
 
-        AddLevel(new Breadcrumb(serviceName, $"resources/services/{Uri.EscapeDataString(serviceName)}"));
+    private void AddLevel(params Breadcrumb[] items)
+        => AddLevel((IEnumerable<Breadcrumb>)items);
+
+    private void AddLevel(IEnumerable<Breadcrumb> items)
+    {
+        var list = items.ToList();
+        if (list.Count > 0)
+            Levels.Add(list);
     }
 
     public record Breadcrumb(string Label, string Href);

+ 1 - 4
Shared.Rcl/Hardware/HardwareDetailsPage.razor

@@ -94,15 +94,12 @@
             </div>
         }
 
-<!---
-TODO: Figure out how lists work
         <div class="m-4">
             <AddResourceComponent TResource="SystemResource"
                                   Placeholder="System name"
                                   OnCreated="NavigateToNewResource"
-                                  RunsOn="@HardwareName"/>
+                                  RunsOn="@(new List<string>(){HardwareName})"/>
         </div>
---->
     }
 </div>
 

+ 11 - 11
Shared.Rcl/Systems/SystemCardComponent.razor

@@ -1,4 +1,5 @@
 @using RackPeek.Domain.Persistence
+@using RackPeek.Domain.Resources
 @using RackPeek.Domain.Resources.SubResources
 @using RackPeek.Domain.Resources.SystemResources
 @using RackPeek.Domain.Resources.SystemResources.UseCases
@@ -179,14 +180,16 @@
             {
                 @foreach (var parent in System.RunsOn)
                 {
-                    var kind = _parentKinds.TryGetValue(parent, out var k) ? k : null;
+                    var kind = (_parentKinds.GetValueOrDefault(parent) ?? "").Trim().ToLower();
 
-                    var url = kind switch
+                    string url = "#";
+                    if (Resource.IsHardware(kind))
                     {
-                        "Hardware" => $"resources/hardware/{Uri.EscapeDataString(parent)}",
-                        "System" => $"resources/systems/{Uri.EscapeDataString(parent)}",
-                        _ => "#"
-                    };
+                        url = $"resources/hardware/{Uri.EscapeDataString(parent)}";
+                    }else if (kind == "system")
+                    {
+                        url = $"resources/systems/{Uri.EscapeDataString(parent)}";
+                    }
 
                     <NavLink href="@url"
                              class="text-emerald-400 text-sm pr-4">
@@ -344,13 +347,10 @@
     {
         _parentKinds.Clear();
 
-        if (System.RunsOn == null)
-            return;
-
         foreach (var parent in System.RunsOn)
         {
-            var kind = await ResourceCollection.GetKind(parent);
-            _parentKinds[parent] = kind;
+            var kind = (await ResourceCollection.GetKind(parent))?.ToLower();
+            _parentKinds[parent] = kind ?? "";
         }
     }
     

+ 13 - 0
Shared.Rcl/Systems/SystemsDetailsPage.razor

@@ -47,6 +47,13 @@
                                   Placeholder="Service name"
                                   OnCreated="NavigateToNewResource"
                                   RunsOn="@(new List<string>{SystemName})"/>
+            
+        </div>
+        <div class="m-4">
+            <AddResourceComponent TResource="SystemResource"
+                                  Placeholder="System name"
+                                  OnCreated="NavigateToNewSystemResource"
+                                  RunsOn="@(new List<string>{SystemName})"/>
         </div>
     }
 </div>
@@ -94,5 +101,11 @@
         Nav.NavigateTo($"resources/services/{Uri.EscapeDataString(serverName)}");
         return Task.CompletedTask;
     }
+    
+    private Task NavigateToNewSystemResource(string serverName)
+    {
+        Nav.NavigateTo($"resources/systems/{Uri.EscapeDataString(serverName)}");
+        return Task.CompletedTask;
+    }
 
 }