Просмотр исходного кода

Merge pull request #191 from Timmoth/#189-baremetal-system-type

Fixed #181
Tim Jones 1 месяц назад
Родитель
Сommit
412f41803f

+ 24 - 15
.github/workflows/test-webui.yml

@@ -6,34 +6,44 @@ on:
 
 jobs:
   build:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-24.04  # pin for consistency (22.04 is also fine)
     steps:
       - name: Checkout
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Setup .NET
-        uses: actions/setup-dotnet@v3
+        uses: actions/setup-dotnet@v4
         with:
           dotnet-version: 10.0.x
 
-      # Restore all projects
+      - name: Cache NuGet
+        uses: actions/cache@v4
+        with:
+          path: ~/.nuget/packages
+          key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/packages.lock.json') }}
+          restore-keys: |
+            ${{ runner.os }}-nuget-
+
+      - name: Cache Playwright browsers (Chromium)
+        uses: actions/cache@v4
+        with:
+          path: ~/.cache/ms-playwright
+          key: ${{ runner.os }}-pw-chromium-${{ hashFiles('**/*.csproj') }}
+          restore-keys: |
+            ${{ runner.os }}-pw-chromium-
+
       - name: Restore
         run: dotnet restore
 
-      # Build solution
       - name: Build
         run: dotnet build --no-restore --configuration Release
 
-      # Install Playwright CLI
-      - name: Install Playwright CLI
-        run: dotnet tool install --global Microsoft.Playwright.CLI
-
-      # Install browser binaries + Linux deps
-      - name: Install Playwright Browsers
+      # Prefer the Playwright script that comes with the NuGet package (no global tool install)
+      - name: Install Playwright Browsers (Chromium only)
+        shell: bash
         run: |
-          playwright install --with-deps
+          pwsh Tests.E2e/bin/Release/net*/playwright.ps1 install --with-deps chromium
 
-      # Build Docker image used by Testcontainers
       - name: Build Docker Image
         run: |
           docker build \
@@ -41,6 +51,5 @@ jobs:
             -f RackPeek.Web/Dockerfile \
             .
 
-      # Run E2E tests
       - name: Run E2E Tests
-        run: dotnet test Tests.E2e --configuration Release --verbosity normal
+        run: dotnet test Tests.E2e --configuration Release --verbosity normal

+ 85 - 23
Shared.Rcl/Services/ServiceCardComponent.razor

@@ -104,10 +104,24 @@
             }
             else if (Service.Network?.Port.HasValue == true)
             {
-                <div class="text-zinc-300"
-                     data-testid="service-port-value">
-                    @Service.Network.Port
-                </div>
+                var href = GetBrowsableHref();
+
+                if (href is not null)
+                {
+                    <a href="@href"
+                       data-testid="service-port-value"
+                       target="_blank"
+                       rel="noopener noreferrer"
+                       class="text-emerald-400 hover:text-emerald-300 hover:underline transition">
+                        @Service.Network.Port
+                    </a>
+                }
+                else
+                {
+                    <div class="text-zinc-300" data-testid="service-port-value">
+                        @Service.Network.Port
+                    </div>
+                }
             }
         </div>
 
@@ -334,32 +348,59 @@
     async Task HandleParentSelected(string? name)
     {
         SelectedParentName = name;
-        await UpdateUseCase.ExecuteAsync(
-            Service.Name,
-            Service.Network?.Ip,
-            Service.Network?.Port,
-            Service.Network?.Protocol,
-            Service.Network?.Url,
-            new List<string>{name},
-            Service.Notes);
+
+        var runsOn = (_isEditing ? _edit.RunsOn : Service.RunsOn) ?? new List<string>();
+        runsOn = runsOn.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
+
+        if (!string.IsNullOrWhiteSpace(name) && !runsOn.Contains(name))
+            runsOn.Add(name);
+
+        var ip       = _isEditing ? _edit.Ip       : Service.Network?.Ip;
+        var port     = _isEditing ? _edit.Port     : Service.Network?.Port;
+        var protocol = _isEditing ? _edit.Protocol : Service.Network?.Protocol;
+        var url      = _isEditing ? _edit.Url      : Service.Network?.Url;
+        var notes    = _isEditing ? _edit.Notes    : Service.Notes;
+
+        await UpdateUseCase.ExecuteAsync(Service.Name, ip, port, protocol, url, runsOn, notes);
+
+        // Refresh service from backend (optional, but if you do it, DO NOT nuke the edit buffer)
         Service = await GetByNameUseCase.ExecuteAsync(Service.Name);
-        _edit = ServiceEditModel.From(Service);
+
+        if (_isEditing)
+        {
+            // keep whatever the user typed; just sync RunsOn
+            _edit.RunsOn = runsOn;
+        }
+        else
+        {
+            _edit = ServiceEditModel.From(Service);
+        }
     }
 
     async Task HandleParentDeleted(string? name)
     {
+        if (string.IsNullOrWhiteSpace(name))
+            return;
+
         SelectedParentName = name;
-        Service.RunsOn.Remove(SelectedParentName);
-        await UpdateUseCase.ExecuteAsync(
-            Service.Name,
-            Service.Network?.Ip,
-            Service.Network?.Port,
-            Service.Network?.Protocol,
-            Service.Network?.Url,
-            Service.RunsOn,
-            Service.Notes);
+
+        var runsOn = (_isEditing ? _edit.RunsOn : Service.RunsOn) ?? new List<string>();
+        runsOn = runsOn.Where(x => !string.IsNullOrWhiteSpace(x) && x != name).ToList();
+
+        var ip       = _isEditing ? _edit.Ip       : Service.Network?.Ip;
+        var port     = _isEditing ? _edit.Port     : Service.Network?.Port;
+        var protocol = _isEditing ? _edit.Protocol : Service.Network?.Protocol;
+        var url      = _isEditing ? _edit.Url      : Service.Network?.Url;
+        var notes    = _isEditing ? _edit.Notes    : Service.Notes;
+
+        await UpdateUseCase.ExecuteAsync(Service.Name, ip, port, protocol, url, runsOn, notes);
+
         Service = await GetByNameUseCase.ExecuteAsync(Service.Name);
-        _edit = ServiceEditModel.From(Service);
+
+        if (_isEditing)
+            _edit.RunsOn = runsOn;
+        else
+            _edit = ServiceEditModel.From(Service);
     }
 
 }
@@ -401,4 +442,25 @@
         Nav.NavigateTo($"resources/services/{Uri.EscapeDataString(newName)}");
     }
 
+    private string? GetBrowsableHref()
+    {
+        var ip = Service.Network?.Ip;
+        var port = Service.Network?.Port;
+
+        if (string.IsNullOrWhiteSpace(ip) || port is null)
+            return null;
+
+        var proto = Service.Network?.Protocol?.Trim().ToLowerInvariant();
+
+        var scheme = proto switch
+        {
+            "https" => "https",
+            "http" => "http",
+            _ => "http"
+        };
+
+        // Build a correct absolute URL
+        var ub = new UriBuilder(scheme, ip) { Port = port.Value };
+        return ub.Uri.ToString();
+    }
 }

+ 79 - 38
Shared.Rcl/Systems/SystemCardComponent.razor

@@ -180,24 +180,27 @@
 
             @if (_isEditing)
             {
-                  @if (System.RunsOn?.Count > 0)
-                  {
-                      @foreach(var parent in System.RunsOn)
-                      {
+                @if (_edit.RunsOn?.Count > 0)
+                {
+                    @foreach (var parent in _edit.RunsOn)
+                    {
                         <button
+                            type="button"
                             class="hover:text-emerald-400"
                             title="Edit Runs On"
                             @onclick="() => _selectParentOpen = true">
-                                  @($"{parent}")
+                            @parent
                         </button>
+
                         <button
+                            type="button"
                             class="text-red-400 hover:text-red-300 pr-4"
                             title="Remove"
-                            @onclick="() => HandleParentDeleted(parent)">
-                                  @($"")
+                            @onclick="async () => await HandleParentDeleted(parent)">
+                            ✕
                         </button>
-                      }
-                  }
+                    }
+                }
             }
             else if (System.RunsOn?.Count > 0)
             {
@@ -290,7 +293,7 @@
 
 
 <ConfirmModal
-    IsOpen="_confirmDeleteOpen"
+    IsOpen="@_confirmDeleteOpen"
     IsOpenChanged="v => _confirmDeleteOpen = v"
     Title="Delete system"
     ConfirmText="Delete"
@@ -298,7 +301,7 @@
     OnConfirm="DeleteServer"
     TestIdPrefix="system-delete">
     Are you sure you want to delete <strong>@System.Name</strong>?
-    <br/>
+    <br />
     This will detach all dependent systems.
 </ConfirmModal>
 
@@ -341,36 +344,74 @@
     bool _selectParentOpen;
     string? SelectedParentName;
 
-    async Task HandleParentSelected(string? name)
-    {
-        SelectedParentName = name;
-        await UpdateUseCase.ExecuteAsync(
-            System.Name,
-            System.Type,
-            System.Os,
-            System.Cores,
-            System.Ram,
-            new List<string>{name},
-            System.Notes);
-        System = await GetByNameUseCase.ExecuteAsync(System.Name);
+  async Task HandleParentSelected(string? name)
+{
+    SelectedParentName = name;
+
+    // Start from the edit buffer when editing, otherwise from the persisted model
+    var runsOn = (_isEditing ? _edit.RunsOn : System.RunsOn) ?? new List<string>();
+    runsOn = runsOn.Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
+
+    if (!string.IsNullOrWhiteSpace(name) && !runsOn.Contains(name))
+        runsOn.Add(name);
+
+    // IMPORTANT: use _edit values when editing so we don't wipe typed fields
+    var type  = _isEditing ? _edit.Type  : System.Type;
+    var os    = _isEditing ? _edit.Os    : System.Os;
+    var cores = _isEditing ? _edit.Cores : System.Cores;
+    var ram   = _isEditing ? _edit.Ram   : System.Ram;
+    var notes = _isEditing ? _edit.Notes : System.Notes;
+
+    await UpdateUseCase.ExecuteAsync(
+        System.Name,
+        type,
+        os,
+        cores,
+        ram,
+        runsOn,
+        notes);
+
+    System = await GetByNameUseCase.ExecuteAsync(System.Name);
+
+    if (_isEditing)
+        _edit.RunsOn = runsOn;
+    else
         _edit = SystemEditModel.From(System);
-    }
+}
 
-    async Task HandleParentDeleted(string? name)
-    {
-        SelectedParentName = name;
-        System.RunsOn.Remove(SelectedParentName);
-        await UpdateUseCase.ExecuteAsync(
-            System.Name,
-            System.Type,
-            System.Os,
-            System.Cores,
-            System.Ram,
-            System.RunsOn,
-            System.Notes);
-        System = await GetByNameUseCase.ExecuteAsync(System.Name);
+async Task HandleParentDeleted(string? name)
+{
+    if (string.IsNullOrWhiteSpace(name))
+        return;
+
+    SelectedParentName = name;
+
+    var runsOn = (_isEditing ? _edit.RunsOn : System.RunsOn) ?? new List<string>();
+    System.RunsOn = runsOn = runsOn.Where(x => !string.IsNullOrWhiteSpace(x) && x != name).ToList();
+
+    var type  = _isEditing ? _edit.Type  : System.Type;
+    var os    = _isEditing ? _edit.Os    : System.Os;
+    var cores = _isEditing ? _edit.Cores : System.Cores;
+    var ram   = _isEditing ? _edit.Ram   : System.Ram;
+    var notes = _isEditing ? _edit.Notes : System.Notes;
+
+    await UpdateUseCase.ExecuteAsync(
+        System.Name,
+        type,
+        os,
+        cores,
+        ram,
+        runsOn,
+        notes);
+
+    System = await GetByNameUseCase.ExecuteAsync(System.Name);
+
+    if (_isEditing)
+        _edit.RunsOn = runsOn;
+    else
         _edit = SystemEditModel.From(System);
-    }
+}
+  
 
 
     #region Drives

+ 9 - 1
Shared.Rcl/Systems/SystemEditModel.cs

@@ -5,7 +5,15 @@ namespace Shared.Rcl.Systems;
 public sealed class SystemEditModel
 {
     public string Name { get; init; } = default!;
-    public string? Type { get; set; }
+    private string? _type;
+    public string? Type
+    {
+        get => _type;
+        set => _type = string.IsNullOrWhiteSpace(value)
+            ? null
+            : value.Trim().ToLowerInvariant();
+    }
+    
     public string? Os { get; set; }
     public int? Cores { get; set; }
     public double? Ram { get; set; }