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

+ 4 - 0
RackPeek.Domain/Persistence/IResourceCollection.cs

@@ -26,6 +26,10 @@ public interface IResourceCollection
     Task LoadAsync(); // required for WASM startup
     Task<IReadOnlyList<Resource>> GetByTagAsync(string name);
     public Task<Dictionary<string, int>> GetTagsAsync();
+    
+    Task<IReadOnlyList<(Resource, string)>> GetByLabelAsync(string name);
+    public Task<Dictionary<string, int>> GetLabelsAsync();
+    
 
     Task<IReadOnlyList<T>> GetAllOfTypeAsync<T>();
     Task<IReadOnlyList<Resource>> GetDependantsAsync(string name);

+ 35 - 0
RackPeek.Domain/Persistence/InMemoryResourceCollection.cs

@@ -96,6 +96,41 @@ public sealed class InMemoryResourceCollection(IEnumerable<Resource>? seed = nul
         }
     }
 
+    public Task<IReadOnlyList<(Resource, string)>> GetByLabelAsync(string name)
+    {
+        lock (_lock)
+        {
+            var result = _resources
+                .Select(r =>
+                {
+                    if (r.Labels != null && r.Labels.TryGetValue(name, out var value))
+                        return (found: true, pair: (r, value));
+
+                    return (found: false, pair: default);
+                })
+                .Where(x => x.found)
+                .Select(x => x.pair)
+                .ToList()
+                .AsReadOnly();
+
+            return Task.FromResult<IReadOnlyList<(Resource, string)>>(result);
+        }
+    }
+
+    public Task<Dictionary<string, int>> GetLabelsAsync()
+    {
+        lock (_lock)
+        {
+            var result = _resources
+                .SelectMany(r => r.Labels ?? Enumerable.Empty<KeyValuePair<string, string>>())
+                .Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key))
+                .GroupBy(kvp => kvp.Key)
+                .ToDictionary(g => g.Key, g => g.Count());
+
+            return Task.FromResult(result);
+        }
+    }
+
     public Task<IReadOnlyList<T>> GetAllOfTypeAsync<T>()
     {
         lock (_lock)

+ 19 - 0
RackPeek.Domain/Persistence/Yaml/YamlResourceCollection.cs

@@ -45,7 +45,26 @@ public sealed class YamlResourceCollection(
             r.Name.Equals(name, StringComparison.OrdinalIgnoreCase))?.Kind);
         
     }
+    public Task<IReadOnlyList<(Resource, string)>> GetByLabelAsync(string name)
+    {
+        var result = resourceCollection.Resources
+            .Where(r => r.Labels != null && r.Labels.TryGetValue(name, out _))
+            .Select(r => (r, r.Labels![name]))
+            .ToList()
+            .AsReadOnly();
 
+        return Task.FromResult<IReadOnlyList<(Resource, string)>>(result);
+    }
+    public Task<Dictionary<string, int>> GetLabelsAsync()
+    {
+        var result = resourceCollection.Resources
+            .SelectMany(r => r.Labels ?? Enumerable.Empty<KeyValuePair<string, string>>())
+            .Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key))
+            .GroupBy(kvp => kvp.Key)
+            .ToDictionary(g => g.Key, g => g.Count());
+
+        return Task.FromResult(result);
+    }
     public Task<Dictionary<string, int>> GetTagsAsync()
     {
         var result = resourceCollection.Resources

+ 8 - 3
RackPeek.Web.Viewer/Pages/Home.razor

@@ -89,9 +89,14 @@
     </div>
 </div>
  
-        <div class="space-y-10 mb-10">
-            <TagListComponent/>
-        </div>
+<div class="space-y-10 mb-10">
+    <TagListComponent/>
+</div>
+
+<div class="space-y-10 mb-10">
+    <LabelListComponent/>
+</div>
+
         <!-- Tree -->
         <div class="space-y-10">
 

+ 3 - 1
RackPeek.Web/Components/Pages/Home.razor

@@ -93,7 +93,9 @@
             <TagListComponent/>
         </div>
 
-
+        <div class="space-y-10 mb-10">
+            <LabelListComponent/>
+        </div>
         <!-- Tree -->
         <div class="space-y-10">
 

+ 50 - 0
Shared.Rcl/Components/LabelListComponent.razor

@@ -0,0 +1,50 @@
+@using RackPeek.Domain.Persistence
+@inject IResourceCollection LabelRepository
+
+<div>
+    <div class="text-xs text-zinc-500 uppercase tracking-wider mb-3">
+        Labels
+    </div>
+
+    @if (_labels is null)
+    {
+        <div class="text-zinc-500">loading labels…</div>
+    }
+    else if (_labels.Count == 0)
+    {
+        <div class="text-zinc-500">no labels found</div>
+    }
+    else
+    {
+        <ul class="space-y-3">
+            <li>
+                <div class="text-zinc-100">
+                    ├─ Labels (@_labels.Count)
+                </div>
+
+                <ul class="ml-4 mt-2 border-l border-zinc-800 pl-4 space-y-1">
+                    @foreach (var (label, count) in _labels
+                                  .OrderByDescending(x => x.Value)
+                                  .ThenBy(x => x.Key))
+                    {
+                        <li class="text-zinc-500 hover:text-emerald-300 transition">
+                            <NavLink href="@($"labels/{Uri.EscapeDataString(label)}")"
+                                     class="block">
+                                └─ @label (@count)
+                            </NavLink>
+                        </li>
+                    }
+                </ul>
+            </li>
+        </ul>
+    }
+</div>
+
+@code {
+    private Dictionary<string, int>? _labels;
+
+    protected override async Task OnInitializedAsync()
+    {
+        _labels = await LabelRepository.GetLabelsAsync();
+    }
+}

+ 84 - 0
Shared.Rcl/Components/LabelPage.razor

@@ -0,0 +1,84 @@
+@page "/labels/{LabelName}"
+@using RackPeek.Domain.Persistence
+@using RackPeek.Domain.Resources
+@using RackPeek.Domain.Resources.SystemResources
+
+@inject IResourceCollection ResourceRepository
+
+<PageTitle>Label: @LabelName</PageTitle>
+
+<div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
+
+    <!-- Header -->
+    <div class="space-y-2">
+        <h1 class="text-lg text-zinc-100">
+            Label: <span class="text-emerald-400">@LabelName</span>
+        </h1>
+    </div>
+
+    @if (_resources is null)
+    {
+        <div class="text-zinc-500">loading resources…</div>
+    }
+    else if (_resources.Count == 0)
+    {
+        <div class="text-zinc-500">no resources found for this label</div>
+    }
+    else
+    {
+        <div class="space-y-3">
+            @foreach (var (resource, value) in _resources.OrderBy(r => r.Item1.Name))
+            {
+                <div class="border border-zinc-800 rounded p-3 bg-zinc-900 hover:border-emerald-700 transition">
+                    <NavLink href="@GetResourceUrl(resource)"
+                             class="block hover:text-emerald-300">
+
+                        <div class="flex justify-between items-center">
+                            <div class="text-zinc-100">
+                                @resource.Name
+                            </div>
+
+                            <div class="text-xs text-zinc-500 uppercase tracking-wide">
+                                @resource.Kind
+                            </div>
+                        </div>
+
+                        <!-- Label value -->
+                        <div class="mt-2 text-xs text-emerald-400">
+                            Value: @value
+                        </div>
+
+                    </NavLink>
+                </div>
+            }
+        </div>
+    }
+
+</div>
+
+@code {
+    [Parameter] public string LabelName { get; set; } = string.Empty;
+
+    private IReadOnlyList<(Resource, string)>? _resources;
+
+    protected override async Task OnParametersSetAsync()
+    {
+        var decoded = Uri.UnescapeDataString(LabelName);
+        _resources = await ResourceRepository.GetByLabelAsync(decoded);
+    }
+
+    private string GetResourceUrl(Resource resource)
+    {
+        if (resource.Kind == SystemResource.KindLabel)
+        {
+            return $"resources/systems/{Uri.EscapeDataString(resource.Name)}";
+        }
+
+        if (resource.Kind == Service.KindLabel)
+        {
+            return $"resources/services/{Uri.EscapeDataString(resource.Name)}";
+        }
+
+        return $"resources/hardware/{Uri.EscapeDataString(resource.Name)}";
+    }
+}