Skip to content

[Feat] Server Networking (IP Management)#1141

Merged
RichardAnderson merged 4 commits into
vitodeploy:4.xfrom
RichardAnderson:feat/networking
Jun 4, 2026
Merged

[Feat] Server Networking (IP Management)#1141
RichardAnderson merged 4 commits into
vitodeploy:4.xfrom
RichardAnderson:feat/networking

Conversation

@RichardAnderson

@RichardAnderson RichardAnderson commented Jun 3, 2026

Copy link
Copy Markdown
Member

This pull request introduces a comprehensive set of changes to enable advanced management of server IP addresses, including CRUD operations, status tracking, and reconciliation of IPs with the server's actual network configuration. It adds new actions, enums, a controller, and a resource for handling server IP addresses, along with jobs to refresh and persist IPs.

image image

Summary by CodeRabbit

Release Notes

  • New Features
    • Added server network management interface for viewing, adding, and managing IP addresses
    • Add individual or range-based IP addresses to servers
    • Set primary IP addresses and manage IP configurations
    • Delete managed IP addresses from servers
    • Refresh IP addresses to synchronize current server state
    • Display comprehensive IP details including family, type, interface, and status information

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a complete server IP address management system for Vito, enabling administrators to discover, manage, and persist network interface configurations on remote servers via SSH and netplan.

Changes

Server Network IP Management

Layer / File(s) Summary
IP Address Domain Model and Schema
app/Enums/IpAddressFamily.php, app/Enums/IpAddressStatus.php, app/Enums/IpAddressType.php, app/Models/ServerIpAddress.php, app/Models/Server.php, database/migrations/2026_06_03_201231_create_server_ip_addresses_table.php, database/factories/ServerIpAddressFactory.php
Defines enum types for IP family/status/type lifecycle; creates ServerIpAddress model with classifyType() and familyFor() helper methods; adds server() relation and factory; establishes database schema with composite uniqueness and status index; extends Server with ipAddresses() relationship.
IP Discovery and Reconciliation from Server
app/Actions/ServerIp/RefreshServerIps.php, resources/views/ssh/network/list-network-addresses.blade.php, resources/views/ssh/network/netplan.blade.php, resources/views/ssh/network/apply-netplan.blade.php
Implements SSH-based discovery via RefreshServerIps action: partitions SSH output by managed marker, parses JSON network addresses, validates and normalizes IP data, and reconciles discovered IPs with database records. SSH templates execute ip -j addr show and parse output to classify addresses; netplan templates generate YAML configuration with interface-grouped addresses.
IP CRUD Operations and Authorization
app/Actions/ServerIp/ManageServerIp.php, app/Policies/ServerIpAddressPolicy.php
Implements create (with range expansion up to 256 IPs), delete (managed-only), and primary-assignment operations. Validates single vs range input, enforces per-server IP uniqueness, queues PersistServerIpsJob on changes, and dispatches SocketEvent after primary reassignment. Authorization policy gates all operations on project-level permissions and server readiness.
Queue Jobs for IP Management
app/Jobs/ServerIp/RefreshServerIpsJob.php, app/Jobs/ServerIp/PersistServerIpsJob.php, app/Actions/Server/InstallServer.php
Adds RefreshServerIpsJob (dispatched post-install) and PersistServerIpsJob (triggered on IP changes) for background processing. Refresh uses UniqueJob locking and dispatches socket events; Persist generates netplan config from managed interfaces, applies via SSH, and finalizes status transitions. Both log failures via ServerLog.
HTTP API, Resources, and Table Configuration
app/Http/Controllers/ServerNetworkController.php, app/Http/Resources/ServerIpAddressResource.php, app/Tables/Servers/ServerIpAddressTable.php, app/Facades/SSH.php, app/Support/Testing/SSHFake.php
Implements five REST endpoints (index/refresh/store/primary/destroy) with authorization and flash messaging. ServerIpAddressResource serializes IP records with enum text/color values and timestamps. ServerIpAddressTable configures realtime sorting by primary status and IP address. Updates SSH facade and test double to support content retrieval for netplan configuration.
Frontend React Components and Navigation
resources/js/pages/server-network/index.tsx, resources/js/pages/server-network/components/form.tsx, resources/js/pages/server-network/components/delete.tsx, resources/js/pages/server-network/components/set-primary.tsx, resources/js/types/server-ip.d.ts, resources/js/components/dialogs/registry.ts, resources/js/layouts/server/layout.tsx
Implements ServerNetwork page with auto-refresh on first load, IP table with per-row actions. ServerIpForm dialog supports single/range input, custom prefix length, and interface selection. Delete and SetPrimary components render confirmation dialogs. Adds Network sidebar navigation item and registers dialog in registry.
Feature Tests for IP Management
tests/Feature/ServerNetworkTest.php
Comprehensive tests covering page rendering, single and range IP creation, default prefix lengths (32 for IPv4, 64 for IPv6), CIDR merging with existing static addresses, validation errors, managed vs discovered deletion, primary assignment, refresh with SSH mocking, and managed-marker detection.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • vitodeploy/vito#1140: Introduces the centralized dialog registry and dialog refactoring that this PR extends with the new serverIpForm dialog component.

Suggested reviewers

  • saeedvaziry

🐰 A server now knows its place in the network,
IPs dance through netplan with perfect correct,
From SSH to socket events, it's all connected,
The rabbit hops proudly—network protected!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[Feat] Server Networking (IP Management)' clearly and concisely summarizes the main change: introducing server IP management functionality.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “Server Network” feature area for managing server IP addresses, including discovery via SSH, persistence via netplan, and UI + realtime table updates.

Changes:

  • Introduces ServerIpAddress persistence (migration/model/factory), enums (family/type/status), policy, and API resource.
  • Implements server IP discovery/reconciliation (RefreshServerIps) and apply/persist jobs (PersistServerIpsJob, RefreshServerIpsJob) with netplan SSH templates.
  • Adds Inertia/React UI for listing IPs and performing actions (refresh, add, delete, set primary) including a registered dialog.

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Feature/ServerNetworkTest.php Feature coverage for network page, IP CRUD-ish actions, and refresh/reconcile behavior.
resources/views/ssh/network/netplan.blade.php Generates Vito-managed netplan YAML and embeds a managed-IP marker.
resources/views/ssh/network/list-network-addresses.blade.php SSH script to read ip -j addr plus managed marker from netplan file.
resources/views/ssh/network/apply-netplan.blade.php SSH script to apply/remove Vito netplan and run netplan generate/apply.
resources/js/types/server-ip.d.ts Frontend type for server IP address payloads.
resources/js/pages/server-network/index.tsx Network page UI with refresh + add dialog + IP table actions.
resources/js/pages/server-network/components/set-primary.tsx “Set as primary” confirm action wiring.
resources/js/pages/server-network/components/form.tsx Registered dialog form for adding a single IP or a range.
resources/js/pages/server-network/components/delete.tsx “Delete” confirm action wiring.
resources/js/layouts/server/layout.tsx Adds “Network” entry to the server sidebar navigation.
resources/js/components/dialogs/registry.ts Registers serverIpForm dialog in the global dialog registry.
database/migrations/2026_06_03_201231_create_server_ip_addresses_table.php Adds server_ip_addresses table and indexes/uniqueness.
database/factories/ServerIpAddressFactory.php Factory for generating server IP address records in tests.
app/Tables/Servers/ServerIpAddressTable.php InertiaTable definition for server IP address listing (columns + ordering + realtime).
app/Support/Testing/SSHFake.php Extends SSH fake to expose uploaded content for assertions.
app/Policies/ServerIpAddressPolicy.php Authorization rules for viewing/creating/updating/deleting server IP addresses.
app/Models/ServerIpAddress.php New model with enum casts + helpers for IP family/type classification.
app/Models/Server.php Adds ipAddresses() relation for server ↔ IP addresses.
app/Jobs/ServerIp/RefreshServerIpsJob.php Job wrapper to refresh IPs post-install / on-demand.
app/Jobs/ServerIp/PersistServerIpsJob.php Job to refresh, write netplan for managed IPs, apply netplan, and finalize statuses/deletes.
app/Http/Resources/ServerIpAddressResource.php API resource for consistent enum text/color exposure to frontend.
app/Http/Controllers/ServerNetworkController.php Inertia endpoints for network page, refresh, create, set-primary, delete.
app/Facades/SSH.php Updates facade docblock for new fake helper method.
app/Enums/IpAddressType.php Enum for IP type display (public/private/unknown).
app/Enums/IpAddressStatus.php Enum for IP status display (configuring/configured/deleting/failed).
app/Enums/IpAddressFamily.php Enum for IP family display (IPv4/IPv6).
app/Actions/ServerIp/RefreshServerIps.php Parses ip -j output and reconciles discovered IPs with DB, preserving “managed” markers.
app/Actions/ServerIp/ManageServerIp.php Validates and creates/deletes IPs, expands ranges, queues persist/apply, sets primary.
app/Actions/Server/InstallServer.php Dispatches a refresh IPs job after server installation completes.

Comment thread resources/js/pages/server-network/index.tsx
Comment thread app/Support/Testing/SSHFake.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
tests/Feature/ServerNetworkTest.php (2)

17-324: ⚡ Quick win

Add deny-path coverage for the new network endpoints.

This suite exercises happy paths and validation, but it does not prove the middleware/policy boundaries on index, store, refresh, primary, and destroy. A couple of cross-project / unauthorized cases here would catch the highest-risk regressions on this surface.

As per coding guidelines: **/*.{php,ts,tsx}: Every endpoint (web and API) must be protected by appropriate middleware and PoliciesandAPI keys are project-scoped — enforce project boundaries in queries and policies`.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/ServerNetworkTest.php` around lines 17 - 324, Add deny-path
tests to ServerNetworkTest covering unauthorized/cross-project access for the
network endpoints: assert that an actor from another project (or a user without
permission) cannot access the index (route 'servers.network'), cannot
create/store IPs ('servers.network.ips.store'), cannot refresh
('servers.network.refresh'), cannot mark primary
('servers.network.ips.primary'), and cannot delete
('servers.network.ips.destroy'). For each endpoint, create tests that use a
different user/server pairing (or revoke permissions), call the route, and
assert the request is forbidden or returns the appropriate error/redirect (e.g.,
assertForbidden or assertSessionHasErrors/assertRedirect as used elsewhere).
Ensure these tests reference the same unique routes/methods
(ServerNetworkTest::test_see_network_page, test_add_ip_address,
test_refresh_pulls_ips_from_server, test_set_ip_as_primary_updates_server_ip,
test_delete_managed_ip_address) so they exercise middleware/policy enforcement
and project-scoped access control.

209-223: ⚡ Quick win

Remove the SSH fake requirement for the primary-assignment test (test_set_ip_as_primary_updates_server_ip)
ManageServerIp::setPrimary() only updates servers.ip/servers.local_ip and server_ip_addresses.is_primary in a DB transaction and then dispatches a SocketEventDTO; it does not enqueue PersistServerIpsJob or call SSH, so SSH::fake() isn’t needed for this flow.
Also consider adding deny-path coverage (unauthenticated/forbidden/other-project) for the servers.network.ips.primary endpoint.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/ServerNetworkTest.php` around lines 209 - 223, The test
test_set_ip_as_primary_updates_server_ip should not call SSH::fake() because
ManageServerIp::setPrimary only updates DB rows (servers.ip/servers.local_ip and
server_ip_addresses.is_primary) and dispatches a SocketEventDTO; remove any
SSH::fake() setup in this test and rely on the DB assertions and response check
after posting to the servers.network.ips.primary route; additionally add
complementary deny-path tests (e.g., unauthenticated, forbidden, other-project)
for the servers.network.ips.primary endpoint to cover access control.
resources/js/pages/server-network/components/form.tsx (1)

129-134: ⚡ Quick win

Use a numeric input for prefix_length.

This field is integer-only on the backend, but type="text" still lets values like /24 or abc through until the 422. Switching to a numeric input here prevents avoidable round-trips and gives a better mobile keyboard.

♻️ Proposed fix
                 <Input
-                  type="text"
+                  type="number"
+                  inputMode="numeric"
+                  min={1}
+                  max={128}
+                  step={1}
                   id="prefix_length"
                   placeholder="e.g. 24"
                   value={form.data.prefix_length}
                   onChange={(e) => form.setData('prefix_length', e.target.value)}
                 />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/js/pages/server-network/components/form.tsx` around lines 129 -
134, The prefix_length field uses a text input which allows non-numeric values;
change the Input for id "prefix_length" to be numeric so the browser enforces
integers and shows the numeric keyboard on mobile. Update the Input's type from
"text" to "number" and ensure the onChange handler for
form.setData('prefix_length', ...) converts the input appropriately (e.g., keep
using e.target.value but validate/parse to an integer before sending to the
backend or let the form store the numeric string if consistent with
form.data.prefix_length). Locate the Input component instance
(id="prefix_length", value={form.data.prefix_length},
onChange={form.setData('prefix_length', ...)}) and make the type change and any
minimal parsing/validation to keep form.data consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Actions/ServerIp/ManageServerIp.php`:
- Around line 30-33: The transaction currently sets $created only when inserting
new IP rows, and on retries a previously-committed insert can be skipped causing
$created to remain false and no persist job to be queued; modify
ManageServerIp::expandIps / the DB::transaction block so that after the
transaction (or by checking inside before returning) you detect any server IP
rows in CONFIGURING or DELETING state (not just newly-inserted ones) and call
queueApply(...) when such pending rows exist; specifically update the
transaction closure and the post-transaction logic around the $created flag (and
the similar block at the other occurrence at lines 58-60) to enqueue the persist
job whenever there are pending CONFIGURING/DELETING rows for the server,
ensuring retries still schedule queueApply even if the insert was already
committed.

In `@app/Actions/ServerIp/RefreshServerIps.php`:
- Around line 145-150: The fast-path in RefreshServerIps.php that checks
$row->is_managed only updates interface and is_primary and then continues, which
preserves stale managed states; change this branch to copy all discovered fields
from $entry into $row (e.g. any fields you treat as discovered like address,
netmask, mac, interface, is_primary — use the same keys from $entry) and then,
unless $row->state === 'DELETING', set $row->state = 'CONFIGURED' (or the
constant used for CONFIGURED in your model) before calling $row->save(); keep
the existing behavior of leaving DELETING untouched so deletions aren’t
interrupted and ensure ServerIpAddressResource will see the normalized
CONFIGURED state.

In `@app/Http/Controllers/ServerNetworkController.php`:
- Around line 45-49: The controller currently swallows SSHError in
ServerNetworkController::refresh() by wrapping
app(RefreshServerIps::class)->handle($server) in a try/catch and returning a
back()->with('error', ...). Remove the try/catch so the call becomes a direct
invocation of RefreshServerIps::handle($server) and allow SSHError to bubble to
the global exception handler; ensure no alternative error handling remains in
refresh() for SSHError and update any local comments accordingly.

In `@app/Models/ServerIpAddress.php`:
- Around line 62-68: In ServerIpAddress::classifyType() the current use of
filter_var with FILTER_FLAG_NO_RES_RANGE causes reserved-but-test addresses like
203.0.113.10 to be treated as non-public; update the logic to either remove
FILTER_FLAG_NO_RES_RANGE from the filter_var flags or replace the filter with an
explicit private-range check (e.g., validate IP with FILTER_FLAG_NO_PRIV_RANGE
for IPv4/IPv6 and then separately check RFC1918/ULA ranges) so that
classifyType() returns IpAddressType::PUBLIC for non-RFC1918/ULA addresses;
refer to the classifyType() method and IpAddressType constants when making the
change.

In `@app/Support/Testing/SSHFake.php`:
- Around line 163-165: The getUploadedContent() accessor can throw if
$uploadedContent is uninitialized; update the SSHFake class by initializing the
property $uploadedContent to an empty string (e.g. private string
$uploadedContent = '') so getUploadedContent() can safely return a string even
before upload() is called; alternatively, if you want nullable semantics, change
the property to ?string $uploadedContent = null and adjust getUploadedContent()
to return ?string (and any callers) — prefer initializing to '' to minimize
call-site changes.

In `@resources/js/pages/server-network/index.tsx`:
- Around line 59-64: The Docs link currently nests a Button (native <button>)
inside an <a target="_blank>, which creates invalid/unsafe interactive nesting
and also omits rel="noopener noreferrer"; fix by rendering the link as an anchor
styled like the Button instead of nesting — e.g. make the outer element the
anchor (use Button's prop to render as an anchor such as Button as="a" or
component="a" / pass href to Button) or replace the inner Button with
non-interactive markup, add href and rel="noopener noreferrer" and keep
target="_blank"; update the JSX where Button and BookOpenIcon are used for the
Docs link to remove nested interactive elements and include rel="noopener
noreferrer".

In `@resources/views/ssh/network/apply-netplan.blade.php`:
- Around line 6-7: Replace the current sequential invocation so that "sudo
netplan apply" only runs if "sudo netplan generate" succeeds: modify the
template where the two commands appear ("sudo netplan generate" and "sudo
netplan apply") to chain them with && instead of running unconditionally,
ensuring apply is skipped on generate failure.

In `@resources/views/ssh/network/netplan.blade.php`:
- Around line 1-12: The template currently emits raw values via {!! !!} for
$managedIps, $interface, and $address; replace those with escaped, quoted output
so untrusted data cannot break the generated YAML. Specifically, change the
managed-IPs line to render an escaped string of the imploded $managedIps (escape
each element before joining), render interface names as quoted, escaped keys
instead of raw tokens, and render each address as a quoted, escaped YAML scalar
in the addresses list; use Blade’s escaping helpers ({{ ... }} or e(...)) to
perform the escaping and ensure surrounding quotes are present so any special
characters are preserved as data, not YAML structure.

---

Nitpick comments:
In `@resources/js/pages/server-network/components/form.tsx`:
- Around line 129-134: The prefix_length field uses a text input which allows
non-numeric values; change the Input for id "prefix_length" to be numeric so the
browser enforces integers and shows the numeric keyboard on mobile. Update the
Input's type from "text" to "number" and ensure the onChange handler for
form.setData('prefix_length', ...) converts the input appropriately (e.g., keep
using e.target.value but validate/parse to an integer before sending to the
backend or let the form store the numeric string if consistent with
form.data.prefix_length). Locate the Input component instance
(id="prefix_length", value={form.data.prefix_length},
onChange={form.setData('prefix_length', ...)}) and make the type change and any
minimal parsing/validation to keep form.data consistent.

In `@tests/Feature/ServerNetworkTest.php`:
- Around line 17-324: Add deny-path tests to ServerNetworkTest covering
unauthorized/cross-project access for the network endpoints: assert that an
actor from another project (or a user without permission) cannot access the
index (route 'servers.network'), cannot create/store IPs
('servers.network.ips.store'), cannot refresh ('servers.network.refresh'),
cannot mark primary ('servers.network.ips.primary'), and cannot delete
('servers.network.ips.destroy'). For each endpoint, create tests that use a
different user/server pairing (or revoke permissions), call the route, and
assert the request is forbidden or returns the appropriate error/redirect (e.g.,
assertForbidden or assertSessionHasErrors/assertRedirect as used elsewhere).
Ensure these tests reference the same unique routes/methods
(ServerNetworkTest::test_see_network_page, test_add_ip_address,
test_refresh_pulls_ips_from_server, test_set_ip_as_primary_updates_server_ip,
test_delete_managed_ip_address) so they exercise middleware/policy enforcement
and project-scoped access control.
- Around line 209-223: The test test_set_ip_as_primary_updates_server_ip should
not call SSH::fake() because ManageServerIp::setPrimary only updates DB rows
(servers.ip/servers.local_ip and server_ip_addresses.is_primary) and dispatches
a SocketEventDTO; remove any SSH::fake() setup in this test and rely on the DB
assertions and response check after posting to the servers.network.ips.primary
route; additionally add complementary deny-path tests (e.g., unauthenticated,
forbidden, other-project) for the servers.network.ips.primary endpoint to cover
access control.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 99d3528b-a189-4ed5-9dd9-975bc7fd7fec

📥 Commits

Reviewing files that changed from the base of the PR and between 89f5266 and 764cb3b.

📒 Files selected for processing (29)
  • app/Actions/Server/InstallServer.php
  • app/Actions/ServerIp/ManageServerIp.php
  • app/Actions/ServerIp/RefreshServerIps.php
  • app/Enums/IpAddressFamily.php
  • app/Enums/IpAddressStatus.php
  • app/Enums/IpAddressType.php
  • app/Facades/SSH.php
  • app/Http/Controllers/ServerNetworkController.php
  • app/Http/Resources/ServerIpAddressResource.php
  • app/Jobs/ServerIp/PersistServerIpsJob.php
  • app/Jobs/ServerIp/RefreshServerIpsJob.php
  • app/Models/Server.php
  • app/Models/ServerIpAddress.php
  • app/Policies/ServerIpAddressPolicy.php
  • app/Support/Testing/SSHFake.php
  • app/Tables/Servers/ServerIpAddressTable.php
  • database/factories/ServerIpAddressFactory.php
  • database/migrations/2026_06_03_201231_create_server_ip_addresses_table.php
  • resources/js/components/dialogs/registry.ts
  • resources/js/layouts/server/layout.tsx
  • resources/js/pages/server-network/components/delete.tsx
  • resources/js/pages/server-network/components/form.tsx
  • resources/js/pages/server-network/components/set-primary.tsx
  • resources/js/pages/server-network/index.tsx
  • resources/js/types/server-ip.d.ts
  • resources/views/ssh/network/apply-netplan.blade.php
  • resources/views/ssh/network/list-network-addresses.blade.php
  • resources/views/ssh/network/netplan.blade.php
  • tests/Feature/ServerNetworkTest.php

Comment thread app/Actions/ServerIp/ManageServerIp.php
Comment thread app/Actions/ServerIp/RefreshServerIps.php
Comment thread app/Http/Controllers/ServerNetworkController.php Outdated
Comment thread app/Models/ServerIpAddress.php
Comment thread app/Support/Testing/SSHFake.php
Comment thread resources/js/pages/server-network/index.tsx
Comment thread resources/views/ssh/network/apply-netplan.blade.php
Comment thread resources/views/ssh/network/netplan.blade.php
@RichardAnderson RichardAnderson marked this pull request as ready for review June 3, 2026 21:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/Actions/ServerIp/RefreshServerIps.php (1)

145-165: ⚡ Quick win

Extract the common discovered-field sync into one helper.

Both existing-row branches now duplicate the same resync assignments for interface, prefix, family, type, primary, and dynamic, with only lifecycle fields differing. Pulling the shared writes into a small private method will reduce the chance that the managed and unmanaged paths drift again.

♻️ Proposed refactor
+    /**
+     * `@param` array{ip: string, prefix_length: int, family: string, interface: string, dynamic: bool} $entry
+     */
+    private function syncDiscoveredFields(ServerIpAddress $row, array $entry, bool $isPrimary): void
+    {
+        $row->interface = $entry['interface'];
+        $row->prefix_length = $entry['prefix_length'];
+        $row->family = IpAddressFamily::from($entry['family']);
+        $row->type = ServerIpAddress::classifyType($entry['ip']);
+        $row->is_primary = $isPrimary;
+        $row->is_dynamic = $entry['dynamic'];
+    }
+
     private function reconcile(Server $server, array $discovered, array $managedIps): void
     {
         DB::transaction(function () use ($server, $discovered, $managedIps): void {
@@
                 if ($row instanceof ServerIpAddress) {
                     if ($row->is_managed) {
-                        $row->interface = $entry['interface'];
-                        $row->prefix_length = $entry['prefix_length'];
-                        $row->family = IpAddressFamily::from($entry['family']);
-                        $row->type = ServerIpAddress::classifyType($entry['ip']);
-                        $row->is_primary = $isPrimary;
-                        $row->is_dynamic = $entry['dynamic'];
+                        $this->syncDiscoveredFields($row, $entry, $isPrimary);
                         $row->save();

                         continue;
                     }

-                    $row->interface = $entry['interface'];
-                    $row->prefix_length = $entry['prefix_length'];
-                    $row->family = IpAddressFamily::from($entry['family']);
-                    $row->type = ServerIpAddress::classifyType($entry['ip']);
+                    $this->syncDiscoveredFields($row, $entry, $isPrimary);
                     $row->status = IpAddressStatus::CONFIGURED;
-                    $row->is_primary = $isPrimary;
-                    $row->is_dynamic = $entry['dynamic'];
                     $row->is_managed = $isManaged;
                     $row->save();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Actions/ServerIp/RefreshServerIps.php` around lines 145 - 165, Extract
the duplicated discovered-field assignments into a private helper (e.g.,
syncDiscoveredFields) and call it from both branches in RefreshServerIps::handle
(or the method containing the shown diff); the helper should accept the
ServerIpAddress $row, the $entry array and $isPrimary flag and set
$row->interface, $row->prefix_length, $row->family = IpAddressFamily::from(...),
$row->type = ServerIpAddress::classifyType(...), $row->is_primary and
$row->is_dynamic (from $entry['dynamic']), then save or return the mutated $row;
leave lifecycle-specific fields (like $row->status and $row->is_managed) to be
set in the managed/unmanaged branches before or after calling the helper so the
two paths only differ in lifecycle handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/Actions/ServerIp/RefreshServerIps.php`:
- Around line 145-165: Extract the duplicated discovered-field assignments into
a private helper (e.g., syncDiscoveredFields) and call it from both branches in
RefreshServerIps::handle (or the method containing the shown diff); the helper
should accept the ServerIpAddress $row, the $entry array and $isPrimary flag and
set $row->interface, $row->prefix_length, $row->family =
IpAddressFamily::from(...), $row->type = ServerIpAddress::classifyType(...),
$row->is_primary and $row->is_dynamic (from $entry['dynamic']), then save or
return the mutated $row; leave lifecycle-specific fields (like $row->status and
$row->is_managed) to be set in the managed/unmanaged branches before or after
calling the helper so the two paths only differ in lifecycle handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 06ede716-0d3d-4399-84d2-024ed3f93976

📥 Commits

Reviewing files that changed from the base of the PR and between 764cb3b and 166ab70.

📒 Files selected for processing (4)
  • app/Actions/ServerIp/RefreshServerIps.php
  • app/Http/Controllers/ServerNetworkController.php
  • app/Support/Testing/SSHFake.php
  • tests/Feature/ServerNetworkTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/Support/Testing/SSHFake.php
  • app/Http/Controllers/ServerNetworkController.php

@RichardAnderson RichardAnderson merged commit a47047c into vitodeploy:4.x Jun 4, 2026
4 checks passed
@RichardAnderson RichardAnderson deleted the feat/networking branch June 4, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants