Skip to content

Conversation

@HarlequinSin
Copy link

@HarlequinSin HarlequinSin commented Dec 28, 2025

There are some pretty sweeping changes here, but they can be summarized in to the following:

  1. Notification changes to reflect if DNS records updated in cloudflare
  2. Extracting CloudFlare methods into seperate file/class
  3. SRV record type toggle

Let me know if there are any issues (honestly never made a PR before and this is my first time with PHP)

Summary by CodeRabbit

  • New Features

    • SRV record support for service-based DNS entries and SRV target configuration on nodes
    • Improved Cloudflare DNS operations (reliable zone lookup, upsert/delete flows, richer error feedback)
    • New, more comprehensive node edit interface with advanced settings and diagnostics
    • UI: toggle control for SRV record mode in subdomain management
  • Bug Fixes / UX

    • Clearer, localized notifications and reduced noisy success toasts
  • Chores

    • Version bumped to 1.1.0

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Adds SRV record support and Cloudflare API integration: DB migrations for srv_target, a new CloudflareService, SRV-aware model logic, enum for service types, Filament UI updates (toggles, node editor), translations, and plugin/version adjustments.

Changes

Cohort / File(s) Summary
Database Migrations
subdomains/database/migrations/004_add_srv_target_to_cloudflare_domains.php, subdomains/database/migrations/005_add_srv_target_to_nodes.php
Add nullable srv_target column to cloudflare_domains, then migration to move srv_target to nodes (with corresponding down methods).
Cloudflare API Service
subdomains/src/Services/CloudflareService.php
New service exposing getZoneId(), upsertDnsRecord(), deleteDnsRecord(), and parseCloudflareHttpResponse() with SRV payload handling, validation, error logging, and normalized responses.
Model - Subdomain
subdomains/src/Models/Subdomain.php
Introduces srv_record (fillable/cast/append), accessors/mutators, consolidated saving hook to upsertOnCloudflare(), SRV/A/AAAA upsert logic via CloudflareService, enhanced validation/error notifications, and removal of legacy create/update HTTP methods.
Model - CloudflareDomain
subdomains/src/Models/CloudflareDomain.php
Replaces inline HTTP zone fetch with CloudflareService usage on create, emits notifications on success/failure, documents srv_target property, and removes old fetch method.
Enum - ServiceRecordType
subdomains/src/Enums/ServiceRecordType.php
New string-backed enum (implements HasLabel) with minecraft = '_minecraft._tcp', helpers for label, tag-based detection (fromServer, fromTags, isSupported), and service/protocol extraction.
Filament UI - Subdomains & Servers
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php, subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php, subdomains/src/Filament/Admin/Resources/CloudflareDomains/Pages/ManageCloudflareDomains.php
Replace hidden record_type with visible Toggle::srv_record; add record_type table column with conditional icon/helper; disable success notifications on some Create/Edit/Delete actions.
Filament Admin - Node Edit Page
subdomains/src/Filament/Admin/Resources/Nodes/Pages/EditNode.php
New overridden EditNode form: multi-tab node editor with srv_target field (regex validation), auto-deploy/token features, diagnostics actions, and hooks to dispatch UpdateServerSubdomains behavior.
Provider & Plugin
subdomains/src/Providers/SubdomainsPluginProvider.php, subdomains/plugin.json, subdomains/src/SubdomainsPlugin.php
Register custom EditNode page via NodeResource::registerCustomPages, bump plugin version to 1.1.0, and localize settings-saved notification key.
Translations
subdomains/lang/en/strings.php
Add multiple keys for SRV support and Cloudflare notifications (labels, help text, errors, and many notification messages like missing zone/port/target, record updates, deletions, fetch failures, settings saved).

Sequence Diagram(s)

sequenceDiagram
    participant User as Filament User
    participant Filament as Filament UI
    participant Subdomain as Subdomain Model
    participant Service as CloudflareService
    participant Cloudflare as Cloudflare API

    User->>Filament: Submit create/edit subdomain (srv_record toggle)
    Filament->>Subdomain: Persist attributes (including srv_record)
    Subdomain->>Subdomain: Trigger saving hook -> upsertOnCloudflare()
    alt srv_record = true (SRV)
        Subdomain->>Service: upsertDnsRecord(zoneId, name, "SRV", srv_target, recordId, port)
    else (A/AAAA)
        Subdomain->>Service: upsertDnsRecord(zoneId, name, "A/AAAA", ip, recordId)
    end
    Service->>Cloudflare: HTTP request (create/update/delete)
    Cloudflare-->>Service: API response
    Service-->>Subdomain: Normalized result { success,id,status,body }
    alt success
        Subdomain->>Filament: Emit success notification / update model fields
    else failure
        Subdomain->>Filament: Emit error/danger notification / log warning
    end
Loading
sequenceDiagram
    participant Admin as Admin User
    participant EditForm as EditNode Form
    participant Node as Node Model
    participant Job as UpdateServerSubdomains Job
    participant Subdomain as Subdomain Model

    Admin->>EditForm: Update srv_target and save node
    EditForm->>Node: Persist srv_target (dehydrate/afterState hooks)
    EditForm->>Admin: Show localized "settings saved" notification
    EditForm->>Job: Dispatch UpdateServerSubdomains for node
    Job->>Subdomain: Iterate servers -> trigger upsertOnCloudflare()
    Subdomain->>CloudflareService: upsertDnsRecord(...) per subdomain
    CloudflareService->>Cloudflare: API calls
    Cloudflare-->>CloudflareService: responses
    CloudflareService-->>Subdomain: results -> notifications/logging
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 I hopped through migrations, toggles, and code,

SRV targets planted where records once strode.
Cloudflare whispers now handled with care,
Nodes and subdomains aligned in the air.
A tiny rabbit cheers — DNS blooms everywhere.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.45% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'SRV records support with endpoint notifications' accurately reflects the main changes: adding SRV record type support and implementing endpoint notifications for DNS record updates.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (3)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

120-124: Consider using a relationship or callback optimization.

The CloudflareDomain::find($get('domain_id')) query runs on every form render/interaction. While functional, this could be optimized by leveraging the relationship already defined on the model, or by using ->live() on the domain_id field to control when the lookup occurs.

🔎 Suggested optimization
 Toggle::make('srv_record')
     ->label(trans('subdomains::strings.srv_record'))
     ->helperText(trans('subdomains::strings.srv_record_help'))
-    ->disabled(fn (callable $get) => !$get('domain_id') || empty(CloudflareDomain::find($get('domain_id'))->srv_target ?? null))
+    ->disabled(fn (callable $get, ?Subdomain $record) => 
+        !$get('domain_id') || 
+        empty($record?->domain?->srv_target ?? CloudflareDomain::find($get('domain_id'))?->srv_target)
+    )
     ->default(false),
subdomains/src/Services/CloudflareService.php (1)

69-82: Consider making the SRV service prefix configurable or explicitly document the Minecraft-specific scope.

The _minecraft._tcp prefix is hardcoded at line 70 with no mechanism to customize it. Given that the plugin documentation describes it as supporting "game servers" generically, this architectural decision limits the plugin's reusability for other services that use SRV records (e.g., SIP, XMPP, other games). Either explicitly scope the plugin to Minecraft-only in documentation and naming, or refactor to accept the service prefix as a parameter or configuration option.

subdomains/src/Models/Subdomain.php (1)

10-10: Remove unused import.

The Http facade is imported but never used in this file. The code now uses CloudflareService for all HTTP interactions.

🔎 Proposed fix
-use Illuminate\Support\Facades\Http;
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3f26b9 and 89f45b8.

📒 Files selected for processing (9)
  • subdomains/database/migrations/004_add_srv_target_to_cloudflare_domains.php
  • subdomains/lang/en/strings.php
  • subdomains/src/Filament/Admin/Resources/CloudflareDomains/CloudflareDomainResource.php
  • subdomains/src/Filament/Admin/Resources/CloudflareDomains/Pages/ManageCloudflareDomains.php
  • subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/CloudflareDomain.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • Subdomain (25-218)
  • domain (58-61)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (17-55)
subdomains/src/Models/CloudflareDomain.php (1)
subdomains/src/Services/CloudflareService.php (2)
  • CloudflareService (9-161)
  • getZoneId (11-39)
🪛 GitHub Actions: Pint
subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php

[error] 1-1: pint --test: no_unused_imports violation detected.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php

[error] 1-1: pint --test: no_unused_imports violation detected.

subdomains/src/Services/CloudflareService.php

[error] 1-1: pint --test: class_attributes_separation violation detected.

subdomains/src/Models/CloudflareDomain.php

[error] 1-1: pint --test: no_unused_imports violation detected.


[error] 1-1: pint --test: ordered_imports violation detected.

subdomains/src/Models/Subdomain.php

[error] 1-1: pint --test: class_attributes_separation violation detected.


[error] 1-1: pint --test: statement_separation violation detected.

🔇 Additional comments (12)
subdomains/src/Filament/Admin/Resources/CloudflareDomains/Pages/ManageCloudflareDomains.php (1)

16-18: LGTM — notification delegation to model layer.

Disabling successNotification(null) here avoids duplicate notifications since the CloudflareDomain model now emits its own success/failure notifications during the created event. This is a reasonable approach for consolidated feedback.

subdomains/src/Filament/Admin/Resources/CloudflareDomains/CloudflareDomainResource.php (1)

56-57: LGTM — SRV target field implementation.

The new srv_target field is well-integrated with appropriate labels, helper text, and a solid hostname validation regex that follows RFC conventions for domain names.

Also applies to: 82-86

subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php (1)

87-90: LGTM — SRV record toggle implementation.

The toggle replaces the previous hidden record_type field with a user-controllable option, which aligns with the PR's goal of enabling SRV record support.

subdomains/lang/en/strings.php (1)

17-37: LGTM — comprehensive language strings.

The new translation keys provide clear, consistent messaging for SRV record functionality and Cloudflare notifications.

subdomains/database/migrations/004_add_srv_target_to_cloudflare_domains.php (1)

7-21: LGTM — clean migration.

The migration correctly adds a nullable srv_target column and provides proper rollback support.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

83-86: LGTM — ToggleColumn with appropriate guards.

The toggle is properly disabled when the domain is missing or lacks an srv_target, with contextual tooltips to inform users why it's unavailable.

subdomains/src/Services/CloudflareService.php (2)

41-120: LGTM — well-structured upsert logic.

The method properly validates inputs, handles both SRV and A/AAAA record types, and provides comprehensive error handling with structured response arrays. The separation between create (POST) and update (PUT) based on recordId presence follows Cloudflare API conventions.


11-39: LGTM — solid service layer implementation.

The getZoneId and deleteDnsRecord methods have appropriate input validation, exception handling, and logging. The structured return format enables consistent error handling in calling code.

Also applies to: 139-160

subdomains/src/Models/Subdomain.php (4)

27-38: LGTM!

The srv_record field is properly added to fillable and cast as boolean.


44-51: Consider whether Cloudflare failures should halt the save.

The upsertOnCloudflare() method returns early on failure but the model save proceeds regardless. This could leave the database record and Cloudflare DNS out of sync. If this is intentional (e.g., to allow offline editing), consider documenting this behavior; otherwise, consider throwing an exception to abort the save on Cloudflare failures.


73-76: LGTM!

Clean accessor implementation for the virtual srv_record attribute.


78-89: LGTM!

Proper null-safe checking for server allocation and correct IP version detection logic.

- Fixed potential potential NPE
- Corrected some notifications
- Removed inbuilt notifications in favor of service notifications
- Fixed Style/unused class issues
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
subdomains/src/Models/CloudflareDomain.php (1)

28-47: Use translation keys for notification messages.

Lines 34 and 39 contain hardcoded English strings. The past review comment indicated that translation keys zone_request_failed and zone_request_succeeded are already defined in strings.php. Please use these keys instead of hardcoded strings for consistency and i18n support.

subdomains/src/Services/CloudflareService.php (1)

136-138: Fix formatting to resolve pipeline failure.

The pipeline is failing due to braces_position and blank_line violations. Based on past review comments, there may be spacing issues between methods. Ensure consistent spacing (single blank line) between method declarations per the project's PHP-CS-Fixer rules.

subdomains/src/Models/Subdomain.php (1)

190-211: Critical: Fix delete notification logic.

Multiple issues with the delete notifications:

  1. Success notification uses wrong severity: Lines 198-202 send a danger() notification on success when it should be success().
  2. Typo: Line 200 has "successed" which should be "succeeded".
  3. Unconditional failure notification: Lines 205-209 always execute, sending a failure notification even after a successful delete. This results in users seeing both success and failure notifications.

Add an else branch or early return after the success case.

🔎 Proposed fix
         if (!empty($result['success'])) {
+            Log::info('Deleted Cloudflare record for Subdomain ID ' . $this->id, ['result' => $result]);
+
             Notification::make()
-                ->danger()
-                ->title('Cloudflare: Delete successed')
+                ->success()
+                ->title('Cloudflare: Delete succeeded')
                 ->body('Successfully deleted Cloudflare record for ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . '.')
                 ->send();
+        } else {
+            Log::warning('Failed to delete Cloudflare record for Subdomain ID ' . $this->id, ['result' => $result]);
+
+            Notification::make()
+                ->danger()
+                ->title('Cloudflare: Delete failed')
+                ->body('Failed to delete Cloudflare record for ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . '. See logs for details. Errors: ' . json_encode($result['errors'] ?? $result['body'] ?? []))
+                ->send();
         }
-
-        Notification::make()
-            ->danger()
-            ->title('Cloudflare: Delete failed')
-            ->body('Failed to delete Cloudflare record for ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . '. See logs for details. Errors: ' . json_encode($result['errors'] ?? $result['body'] ?? []))
-            ->send();
🧹 Nitpick comments (1)
subdomains/src/Services/CloudflareService.php (1)

69-82: Hardcoded Minecraft SRV naming limits flexibility.

Line 70 hardcodes the SRV record name as _minecraft._tcp.%s, which restricts this implementation to Minecraft servers only. If the plugin is intended solely for Minecraft, this should be documented. Otherwise, consider making the service and protocol configurable.

🔎 Potential refactor to support configurable service/protocol
     public function upsertDnsRecord(
         string $zoneId,
         string $name,
         string $recordType,
         string $target,
         ?string $recordId = null,
         ?int $port = null,
+        ?string $service = '_minecraft',
+        ?string $protocol = '_tcp',
     ): array
     {
         // ...
         if ($recordType === 'SRV') {
             // ...
             $payload = [
-                'name' => sprintf('_minecraft._tcp.%s', $name),
+                'name' => sprintf('%s.%s.%s', $service, $protocol, $name),
                 // ...
             ];
         }
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89f45b8 and b42370d.

📒 Files selected for processing (5)
  • subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/CloudflareDomain.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • subdomains/src/Filament/Admin/Resources/Servers/RelationManagers/SubdomainRelationManager.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Models/CloudflareDomain.php (1)
subdomains/src/Services/CloudflareService.php (2)
  • CloudflareService (9-160)
  • getZoneId (11-39)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-160)
  • upsertDnsRecord (41-120)
  • deleteDnsRecord (138-159)
subdomains/src/Models/CloudflareDomain.php (1)
  • boot (24-48)
🪛 GitHub Actions: Pint
subdomains/src/Services/CloudflareService.php

[error] 1-1: pint --test failed: braces_position, blank_line issues detected in CloudflareService.php. Command failed with exit code 1.

subdomains/src/Models/Subdomain.php

[error] 1-1: pint --test failed: no_unused_imports, ordered_imports issues detected in Subdomain.php. Command failed with exit code 1.

🔇 Additional comments (7)
subdomains/src/Services/CloudflareService.php (3)

11-39: LGTM!

The getZoneId method properly validates inputs, handles exceptions, and returns the first matching zone ID. Error logging is appropriate.


94-120: LGTM!

The upsert logic correctly differentiates between create (POST) and update (PUT) based on the presence of $recordId. Error handling and logging are appropriate.


138-159: LGTM!

The deleteDnsRecord method properly validates inputs, handles the DELETE request, and includes appropriate error handling and logging.

subdomains/src/Models/Subdomain.php (4)

44-58: LGTM!

The lifecycle hooks are well-structured. Using saving to derive record_type from srv_record before persistence, then saved to trigger the Cloudflare upsert, ensures the model state is consistent before making external API calls.


75-91: LGTM!

The accessor/mutator pattern for srv_record is clean. The setRecordType method properly determines the record type (SRV, A, or AAAA) based on the boolean flag and IP version, with appropriate null checks.


110-149: LGTM!

The SRV record upsert logic properly validates the port, calls the service with the domain's srv_target, and uses updateQuietly on line 130 to avoid triggering lifecycle hooks recursively. Notifications provide clear feedback on success and failure.


151-187: LGTM!

The A/AAAA record upsert logic includes proper null checks for $this->server and $this->server->allocation (addressing the past review comment), validates IP addresses, and provides appropriate notifications. The use of updateQuietly prevents recursive hook invocation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
subdomains/src/Models/Subdomain.php (1)

1-211: Fix Pint violation: Remove whitespace from blank lines.

The pipeline failure indicates the no_whitespace_in_blank_line rule is triggered. Ensure all blank lines throughout the file contain no spaces or tabs.

Run Pint locally to identify and auto-fix the whitespace:

./vendor/bin/pint subdomains/src/Models/Subdomain.php
🧹 Nitpick comments (1)
subdomains/src/Models/Subdomain.php (1)

92-187: Optional: Consider extracting duplicate success-handling logic.

The cloudflare_id update (lines 128-130, 166-168) and success notification (lines 132-136, 170-174) are duplicated across the SRV and A/AAAA paths. Extracting a small helper method would reduce repetition.

🔎 Example refactor to reduce duplication
private function handleUpsertSuccess(string $recordId): void
{
    if ($this->cloudflare_id !== $recordId) {
        $this->updateQuietly(['cloudflare_id' => $recordId]);
    }
    
    Notification::make()
        ->success()
        ->title('Cloudflare: Record updated')
        ->body('Successfully updated ' . $this->name . '.' . ($this->domain->name ?? 'unknown') . ' to ' . $this->record_type)
        ->send();
}

Then replace lines 128-136 and 166-174 with:

$this->handleUpsertSuccess($result['id']);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b42370d and ea4e602.

📒 Files selected for processing (2)
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Services/CloudflareService.php
🧰 Additional context used
🧬 Code graph analysis (1)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-160)
  • upsertDnsRecord (41-120)
  • deleteDnsRecord (138-159)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (23-36)
🪛 GitHub Actions: Pint
subdomains/src/Models/Subdomain.php

[error] 1-1: pint --test failed with exit code 1. Rule 'no_whitespace_in_blank_line' triggered.

🔇 Additional comments (4)
subdomains/src/Models/Subdomain.php (4)

6-7: LGTM: Imports support new CloudflareService integration.

The new imports (CloudflareService, Notification, Log) are properly used throughout the upsert and delete flows.

Also applies to: 12-12


26-37: LGTM: srv_record field properly configured.

The fillable array and boolean cast correctly support the new SRV record toggle feature.


39-57: LGTM: Lifecycle hooks properly refactored.

The consolidation from created/updated to saving/saved is cleaner. The srv_record virtual attribute is correctly processed and unset before persistence, and Cloudflare operations are properly triggered.


74-90: LGTM: srv_record accessor/mutator logic is sound.

The accessor provides a clean boolean interface, and the mutator correctly derives record_type with proper null-safe checks for the server allocation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
subdomains/src/Services/CloudflareService.php (1)

122-136: Refine success detection to match Cloudflare API semantics.

Line 127's logic $body['success'] === true || count($body['result']) > 0 treats success and result as interchangeable indicators, which doesn't align with the Cloudflare API's response structure where success is an explicit boolean flag and result is the data payload.

Issues:

  1. The OR condition returns success=true if only result has data, even when $body['success'] is false
  2. count() on a non-array result will error
  3. Line 131 extracts id from result without verifying result exists or is an array

The stricter pattern at line 30 (!empty($body['result']) && count($body['result']) > 0) is more defensive.

🔎 Proposed fix
     protected function parseCloudflareHttpResponse(Response $response): array
     {
         $status = $response->status();
         $body = $response->json() ?? [];
 
-        $success = $response->successful() && ($body['success'] === true || count($body['result']) > 0);
+        $success = $response->successful() && $body['success'] === true;
 
         return [
             'success' => $success,
-            'id' => $body['result']['id'] ?? null,
+            'id' => is_array($body['result'] ?? null) ? ($body['result']['id'] ?? null) : null,
             'errors' => $body['errors'] ?? [],
             'status' => $status,
             'body' => $body,
         ];
     }
🧹 Nitpick comments (1)
subdomains/src/Services/CloudflareService.php (1)

69-82: Consider making the SRV naming convention configurable.

Line 70 hardcodes the Minecraft-specific SRV naming convention (_minecraft._tcp.%s). While this works for the current use case, it limits the service's reusability for other SRV record types (e.g., _http._tcp, _xmpp._tcp).

Consider adding optional parameters for the service and protocol, or documenting that this service is Minecraft-specific.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea4e602 and 9804937.

📒 Files selected for processing (6)
  • subdomains/lang/en/strings.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/CloudflareDomain.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
  • subdomains/src/SubdomainsPlugin.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Models/CloudflareDomain.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • Subdomain (24-212)
  • domain (59-62)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (16-54)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-160)
  • upsertDnsRecord (41-120)
  • deleteDnsRecord (138-159)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (23-36)
🪛 GitHub Actions: Pint
subdomains/src/Services/CloudflareService.php

[warning] 1-1: Pint lint issue: braces_position, blank_line...

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php

[error] 89-89: Parse error: syntax error, unexpected token '->', expecting ']' on line 89.

subdomains/src/Models/Subdomain.php

[warning] 1-1: Pint lint issue: blank_line_before_statement, no_whitespace...

🔇 Additional comments (9)
subdomains/src/SubdomainsPlugin.php (1)

50-50: LGTM! Proper internationalization.

The notification title is now properly localized using the translation key from the strings file.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

82-85: LGTM! Well-implemented ToggleColumn with proper validation.

The SRV record toggle includes helpful tooltips and correctly disables when the domain is missing an SRV target, preventing invalid configurations.

subdomains/src/Services/CloudflareService.php (2)

11-39: LGTM! Robust zone ID fetching with proper error handling.

The method includes appropriate validation, exception handling, and logging for both request failures and API errors.


138-159: LGTM! Consistent error handling pattern.

The delete method follows the same robust error handling pattern as the other methods, with proper validation, logging, and exception handling.

subdomains/lang/en/strings.php (1)

17-65: LGTM! Comprehensive and clear translation keys.

The new translation keys provide clear, user-friendly messages for SRV record functionality and Cloudflare operations. The notification messages include helpful context with placeholder variables.

subdomains/src/Models/Subdomain.php (4)

39-57: LGTM! Cleaner lifecycle hook implementation.

The refactored hooks consolidate create/update logic into saving/saved events, which is more maintainable than separate created/updated handlers. The srv_record attribute conversion before persistence is well-placed.


74-90: LGTM! Well-implemented record type logic.

The accessor and setter properly handle the SRV/A/AAAA record type determination with appropriate null safety checks for the server relationship.


92-187: LGTM! Comprehensive upsert implementation with excellent error handling.

The method properly:

  • Validates zone ID availability
  • Handles SRV and A/AAAA record types with appropriate validation
  • Provides clear user notifications for both success and failure cases
  • Updates the cloudflare_id when it changes
  • Uses the translation keys for user-facing messages

The delegation of SRV target validation to CloudflareService is appropriate.


189-211: LGTM! Correct notification flow with early return.

The delete method properly handles success and failure cases with the return statement on line 202 ensuring only one notification is sent. The notification types and translation keys are appropriate for delete operations.

@HarlequinSin HarlequinSin marked this pull request as draft December 28, 2025 23:39
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
subdomains/lang/en/strings.php (1)

2-2: Fix the whitespace on the blank line to resolve the Pint violation.

The pipeline failure indicates that line 2 contains whitespace characters (spaces or tabs) on what should be a completely blank line. Laravel Pint requires blank lines to be entirely empty.

🔎 Proposed fix

Remove any spaces or tabs from line 2 so it is completely empty:

 <?php
-
+
 return [

(Ensure line 2 has zero characters, not even spaces or tabs.)

🧹 Nitpick comments (2)
subdomains/src/Models/Subdomain.php (2)

79-90: Add type hint for the $value parameter.

The method logic correctly determines record_type based on the boolean value and IP version. However, the $value parameter lacks a type hint.

🔎 Proposed improvement
-    public function setRecordType($value): void
+    public function setRecordType(bool $value): void
     {

92-199: Consider decomposing this method for improved maintainability.

The method is comprehensive and handles all validation and notification flows correctly. However, at 107 lines, it could benefit from extraction into smaller methods (e.g., validateAndUpsertSrvRecord(), validateAndUpsertIpRecord()) to improve readability and testability.

💡 Example decomposition approach
protected function upsertOnCloudflare(): void
{
    $service = app(CloudflareService::class);
    $zoneId = $this->validateZoneId();
    
    if (!$zoneId) {
        return;
    }

    if ($this->record_type === 'SRV') {
        $this->upsertSrvRecord($service, $zoneId);
    } else {
        $this->upsertIpRecord($service, $zoneId);
    }
}

protected function validateZoneId(): ?string
{
    // lines 96-107
}

protected function upsertSrvRecord(CloudflareService $service, string $zoneId): void
{
    // lines 110-160
}

protected function upsertIpRecord(CloudflareService $service, string $zoneId): void
{
    // lines 162-198
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bfd8df and e781f6a.

📒 Files selected for processing (2)
  • subdomains/lang/en/strings.php
  • subdomains/src/Models/Subdomain.php
🧰 Additional context used
🧬 Code graph analysis (1)
subdomains/src/Models/Subdomain.php (3)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-162)
  • upsertDnsRecord (43-120)
  • deleteDnsRecord (138-161)
subdomains/src/Models/CloudflareDomain.php (1)
  • boot (24-48)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
  • boot (23-36)
🪛 GitHub Actions: Pint
subdomains/lang/en/strings.php

[error] 1-1: Laravel Pint: no_whitespace_in_blank_line

🔇 Additional comments (4)
subdomains/src/Models/Subdomain.php (4)

32-37: LGTM: Virtual attribute handling is well-designed.

The srv_record attribute is correctly configured as fillable and cast to boolean, enabling clean form handling. The saving hook (lines 43-48) properly removes it before database persistence after deriving the actual record_type.


43-52: LGTM: Lifecycle hooks follow Laravel best practices.

The saving hook correctly transforms the virtual srv_record attribute into the persistent record_type, and the saved hook ensures Cloudflare synchronization happens after database persistence. Using array_key_exists properly detects when the attribute was explicitly set.


74-77: LGTM: Accessor provides clean boolean interface.

The getSrvRecordAttribute() accessor correctly derives the boolean srv_record value from the stored record_type, completing the virtual attribute pattern.


201-224: LGTM: Delete handling is clean and correct.

The method properly validates prerequisites, delegates to CloudflareService, and sends appropriate success or failure notifications. The past issue with duplicate notifications has been resolved.

@Boy132
Copy link
Member

Boy132 commented Dec 29, 2025

So, if I understand correctly you can assign a SRV target to a cloudflare domain? And that will be used to create a minecraft SRV record?

The two things that bother me here:

  1. The SRV target should be tied to the server/node (probably node FQDN?) and not the cloudflare domain. Otherwise this system is useless in multi-node setups.
  2. It's hardcoded for minecraft.

@HarlequinSin
Copy link
Author

HarlequinSin commented Dec 29, 2025

Yes 100% on both points at this time:

  1. When I originally set this up I wanted to add the srv target to the allocations, but couldnt modify the allocations table. I then later saw a comment that we cant modify existing pages.
  2. Will be changing that to work off egg tags

@Boy132
Copy link
Member

Boy132 commented Dec 30, 2025

Then we should change that.

  1. The SRV target should be configurable per node.
  2. The service name should be entered by the user. And/or use egg tags.

Copy link
Member

@Boy132 Boy132 left a comment

Choose a reason for hiding this comment

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

  1. The SRV target should be configurable per node.
  2. The service name should be entered by the user. And/or use egg tags.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
subdomains/src/Models/Subdomain.php (1)

69-72: Add null check for $this->domain to prevent potential NPE.

getLabel() accesses $this->domain->name without checking if the domain relationship is loaded. If the domain is null or not loaded, this will throw a null pointer exception.

🔎 Proposed fix
     public function getLabel(): string|Htmlable|null
     {
-        return $this->name . '.' . $this->domain->name;
+        return $this->name . '.' . ($this->domain->name ?? 'unknown');
     }
🧹 Nitpick comments (2)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

123-128: Consider caching the domain lookup for better performance.

The disabled() callback queries the database via CloudflareDomain::find() on each form render. While acceptable for admin UIs, you could optimize by using the form's relationship data if available.

🔎 Optional optimization using relationship
                 Toggle::make('srv_record')
                     ->label(trans('subdomains::strings.srv_record'))
                     ->helperText(trans('subdomains::strings.srv_record_help'))
                     ->reactive()
-                    ->disabled(fn (callable $get) => (empty($get('domain_id')) || empty(CloudflareDomain::find($get('domain_id'))?->srv_target)))
+                    ->disabled(function (callable $get) {
+                        $domainId = $get('domain_id');
+                        if (empty($domainId)) {
+                            return true;
+                        }
+                        return empty(CloudflareDomain::find($domainId)?->srv_target);
+                    })
                     ->default(false),

Alternatively, if the relationship is already eager-loaded, access it through the record context.

subdomains/src/Models/Subdomain.php (1)

220-223: Remove unnecessary return statement.

The return on line 222 is at the end of the method and serves no purpose since execution would naturally end there anyway.

🔎 Proposed fix
             } else {
                 Notification::make()
                     ->danger()
                     ->title(trans('subdomains::strings.notifications.cloudflare_delete_failed_title'))
                     ->body(trans('subdomains::strings.notifications.cloudflare_delete_failed', ['subdomain' => $this->name . '.' . ($this->domain->name ?? 'unknown'), 'errors' => json_encode($result['errors'] ?? $result['body'] ?? [])]))
                     ->send();
             }
-
-            return;
         }
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e781f6a and 6b31686.

📒 Files selected for processing (4)
  • subdomains/database/migrations/005_add_srv_target_to_nodes.php
  • subdomains/lang/en/strings.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/Subdomain.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/lang/en/strings.php
🧰 Additional context used
🧬 Code graph analysis (2)
subdomains/src/Models/Subdomain.php (1)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-162)
  • upsertDnsRecord (43-120)
  • deleteDnsRecord (138-161)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • Subdomain (24-225)
  • domain (59-62)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (16-54)
🪛 GitHub Actions: Pint
subdomains/database/migrations/005_add_srv_target_to_nodes.php

[error] 1-1: Laravel Pint style check failed in 005_add_srv_target_to_nodes.php. See the Pint output for details.

🔇 Additional comments (3)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

82-85: Well-implemented SRV toggle with proper guards.

The ToggleColumn correctly disables itself when the related domain is missing srv_target, preventing invalid configurations. The tooltip provides helpful context to users.

subdomains/src/Models/Subdomain.php (1)

39-57: Clean lifecycle hook implementation with CloudflareService integration.

The refactored boot() method properly handles the srv_record virtual attribute conversion in the saving hook and delegates Cloudflare operations to the dedicated service. The use of updateQuietly() in upsertOnCloudflare() correctly avoids recursive event triggers.

subdomains/database/migrations/005_add_srv_target_to_nodes.php (1)

1-6: Verify specific Pint violations before making changes.

The migration file appears properly formatted according to Laravel Pint standards (consistent indentation, proper spacing, valid method signatures). The file does not require declare(strict_types=1) as Laravel migrations typically don't mandate it. If the pipeline indicates a Pint failure, run ./vendor/bin/pint --test to identify the specific rule violation(s) before applying fixes.

@HarlequinSin HarlequinSin marked this pull request as draft January 1, 2026 08:53
@HarlequinSin
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
subdomains/src/Models/Subdomain.php (1)

91-106: Add null check for $this->domain before accessing properties.

Line 95 accesses $this->domain->cloudflare_id without first verifying that $this->domain is not null. If the relationship isn't loaded or doesn't exist, this will throw a null property access error.

🔎 Proposed fix
     protected function upsertOnCloudflare(): void
     {
         $registrar = app(CloudflareService::class);

+        if (!$this->domain) {
+            Log::warning('Domain relationship not loaded for subdomain', ['subdomain_id' => $this->id]);
+            return;
+        }
+
         $zoneId = $this->domain->cloudflare_id;
🧹 Nitpick comments (5)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (1)

124-128: Consider eager-loading the node relationship.

The SRV toggle implementation correctly prevents invalid configurations. However, Filament::getTenant()->node->srv_target may trigger N+1 queries if the node relationship isn't eager-loaded on the tenant (Server). Consider adding ->with('node') to the tenant query if not already present.

subdomains/src/Enums/ServiceRecordType.php (1)

49-61: Add bounds checking for robust parsing.

The service() and protocol() methods assume the enum value always contains exactly two dot-separated parts. While this works for the current minecraft case, future enum cases with different formats could cause undefined array key errors.

🔎 Proposed fix with bounds checking
 public function service(): string
 {
     $parts = explode('.', $this->value);
+    
+    if (count($parts) < 2) {
+        throw new \InvalidArgumentException("Invalid service record format: {$this->value}");
+    }
     
     return $parts[0];
 }
 
 public function protocol(): string
 {
     $parts = explode('.', $this->value);
+    
+    if (count($parts) < 2) {
+        throw new \InvalidArgumentException("Invalid service record format: {$this->value}");
+    }
     
     return $parts[1];
 }
subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php (2)

44-45: Missing Throwable import for docblock reference.

The docblock at line 44 references @throws Throwable, but Throwable is not imported. While this won't cause a runtime error (it's just documentation), it's inconsistent. Either import Throwable or use the fully qualified name \Throwable.

🔎 Proposed fix
+use Throwable;
 
 class EditNode extends BaseEditNode
 {
     /**
-     * @throws Throwable
+     * @throws \Throwable
      */

368-397: Side effects in dehydrateStateUsing callback may cause unexpected behavior.

The dehydrateStateUsing callback performs multiple side effects:

  1. Directly saves the model with forceFill()->save()
  2. Sends a notification
  3. Dispatches jobs for all linked servers

This callback runs during form dehydration, which may execute multiple times or at unexpected moments. Consider moving this logic to a dedicated afterSave hook or action handler for more predictable behavior.

Additionally, line 390 has a minor log message issue - "Updating subdomains for Node ID " should include the ID in the message string rather than just in the context array for better log readability.

🔎 Suggested improvement for log message
-                                    Log::info('Updating subdomains for Node ID ', ['node_id' => $this->record->id, 'serverIds' => $serverIds]);
+                                    Log::info('Updating subdomains for Node ID ' . $this->record->id, ['node_id' => $this->record->id, 'serverIds' => $serverIds]);
subdomains/src/Models/Subdomain.php (1)

233-234: Unnecessary return statement.

The return statement at line 233 is redundant since it's at the end of the method and there's no code after the if block.

🔎 Proposed fix
             } else {
                 Notification::make()
                     ->danger()
                     ->title(trans('subdomains::strings.notifications.cloudflare_delete_failed_title'))
                     ->body(trans('subdomains::strings.notifications.cloudflare_delete_failed', ['subdomain' => $this->name . '.' . ($this->domain->name ?? 'unknown'), 'errors' => json_encode($result['errors'] ?? $result['body'] ?? [])]))
                     ->send();
             }
-
-            return;
         }
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b31686 and 079563f.

📒 Files selected for processing (9)
  • subdomains/database/migrations/005_add_srv_target_to_nodes.php
  • subdomains/lang/en/strings.php
  • subdomains/src/Enums/ServiceRecordType.php
  • subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php
  • subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php
  • subdomains/src/Models/CloudflareDomain.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Providers/SubdomainsPluginProvider.php
  • subdomains/src/Services/CloudflareService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Models/CloudflareDomain.php
🧰 Additional context used
🧬 Code graph analysis (3)
subdomains/src/Enums/ServiceRecordType.php (1)
subdomains/src/Models/Subdomain.php (1)
  • server (62-65)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)
subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php (1)
  • EditNode (41-790)
subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (2)
subdomains/src/Models/Subdomain.php (2)
  • Subdomain (25-236)
  • server (62-65)
subdomains/src/Models/CloudflareDomain.php (1)
  • CloudflareDomain (16-53)
🔇 Additional comments (19)
subdomains/src/Providers/SubdomainsPluginProvider.php (1)

5-5: LGTM!

The custom page registration for NodeResource follows the established pattern and correctly wires the EditNode page for node editing functionality.

Also applies to: 9-9, 18-18

subdomains/database/migrations/005_add_srv_target_to_nodes.php (1)

9-18: LGTM!

The migration correctly moves srv_target from cloudflare_domains to nodes. Since both migrations 004 and 005 are in the same PR and haven't been deployed yet, there's no data loss concern.

subdomains/src/Filament/Server/Resources/Subdomains/SubdomainResource.php (3)

81-85: LGTM!

The record_type column provides excellent visual feedback when SRV records are misconfigured (missing srv_target), helping users identify and resolve configuration issues.


88-91: Success notifications suppressed.

The .successNotification(null) calls suppress Filament's default success messages. Ensure that custom notifications (likely from CloudflareService operations) are shown to users so they receive appropriate feedback on their actions.

Also applies to: 102-102


114-114: LGTM!

The domain suffix display improves UX by showing the full subdomain format. The direct query is acceptable in this form context.

subdomains/src/Enums/ServiceRecordType.php (2)

18-46: LGTM!

The tag-based detection logic correctly maps server egg tags to service record types. The implementation returns the first matching case, which is appropriate for the current single-service scenario.


13-16: LGTM!

The getLabel() implementation appropriately converts the enum name to a human-readable format.

subdomains/lang/en/strings.php (1)

8-27: LGTM!

The translation keys are comprehensive, well-organized, and provide clear user messaging for SRV record functionality and Cloudflare operations. Previous phrasing inconsistencies have been resolved.

Also applies to: 32-67

subdomains/src/Services/CloudflareService.php (3)

11-41: LGTM!

The getZoneId method has proper input validation, exception handling, and response parsing. The error handling and logging are appropriate.


43-120: LGTM with minor observation.

The upsertDnsRecord method is well-structured with clear separation between SRV and standard record handling. Exception handling and logging are comprehensive.

Note: The method signature accepts 6 parameters which is at the higher end. Consider grouping related parameters into a DTO/value object in a future refactor if the signature grows further.


138-161: LGTM!

The deleteDnsRecord method properly validates inputs, handles exceptions, and returns consistent response structures.

subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php (2)

61-105: LGTM!

The overview tab properly displays node system information with appropriate state derivation and responsive column layouts.


645-786: LGTM!

The diagnostics tab has comprehensive pull/upload/clear functionality with proper error handling, notifications, and state management.

subdomains/src/Models/Subdomain.php (6)

27-42: LGTM!

The fillables, casts, and appends are properly configured for the new srv_record attribute.


44-55: LGTM!

Using static::saved() instead of separate created and updated hooks correctly consolidates the upsert logic.


72-75: LGTM!

The getSrvRecordAttribute accessor correctly derives the boolean from the record_type field.


108-171: LGTM!

The SRV record handling is comprehensive with proper validation for port, service record type, and srv_target. Error notifications provide clear feedback to users.


173-210: LGTM!

The A/AAAA record handling properly validates the server allocation and IP address, with appropriate error handling and notifications.


212-235: LGTM!

The deleteOnCloudflare method correctly handles success/failure cases with proper conditional branching. The previous issues with duplicate notifications and missing else clause have been addressed.

@HarlequinSin
Copy link
Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
subdomains/src/Models/Subdomain.php (1)

136-149: Optional: Consider null-safe operators for defensive coding.

While line 136 validates that srv_target is not empty (ensuring node is not null), line 149 accesses $this->server->node->srv_target without null-safe operators. For consistency and defensive programming, consider:

$result = $registrar->upsertDnsRecord($zoneId, $recordName, 'SRV', $this->server->node->srv_target, $this->cloudflare_id, $port);

Could be:

$target = $this->server->node->srv_target; // already validated
$result = $registrar->upsertDnsRecord($zoneId, $recordName, 'SRV', $target, $this->cloudflare_id, $port);

This makes the validated value explicit and slightly more readable.

subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php (3)

618-629: Log caught exceptions for debugging.

The exception is caught but not logged, which makes troubleshooting node connection issues difficult. While the user receives a notification, administrators and developers have no trace of what went wrong.

🔎 Proposed fix
-                                            } catch (Exception) {
+                                            } catch (Exception $e) {
                                                 Notification::make()
                                                     ->title(trans('admin/node.error_connecting', ['node' => $node->name]))
                                                     ->body(trans('admin/node.error_connecting_description'))
                                                     ->color('warning')
                                                     ->icon('tabler-database')
                                                     ->warning()
                                                     ->send();
-
+                                                
+                                                report($e);
                                             }

690-693: Add timeout to external HTTP request.

The HTTP POST to logs.pelican.dev lacks a timeout configuration. If the external service is slow or unresponsive, this could cause the admin panel request to hang, degrading user experience.

🔎 Proposed fix
                                             try {
                                                 $response = Http::asMultipart()
+                                                    ->timeout(30)
                                                     ->attach('c', $get('log'))
                                                     ->attach('e', '14d')
                                                     ->post('https://logs.pelican.dev');

724-729: Log caught exceptions for debugging.

Similar to the earlier exception handling issue, this exception is caught but not logged, making it difficult to diagnose upload failures beyond the user-facing notification.

🔎 Proposed fix
-                                            } catch (\Exception $e) {
+                                            } catch (\Exception $e) {
                                                 Notification::make()
                                                     ->title(trans('admin/node.diagnostics.upload_failed'))
                                                     ->body($e->getMessage())
                                                     ->danger()
                                                     ->send();
+                                                
+                                                report($e);
                                             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 079563f and f4757fd.

📒 Files selected for processing (3)
  • subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php
  • subdomains/src/Models/Subdomain.php
  • subdomains/src/Services/CloudflareService.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • subdomains/src/Services/CloudflareService.php
🧰 Additional context used
🧬 Code graph analysis (1)
subdomains/src/Models/Subdomain.php (2)
subdomains/src/Services/CloudflareService.php (3)
  • CloudflareService (9-162)
  • upsertDnsRecord (43-120)
  • deleteDnsRecord (138-161)
subdomains/src/Enums/ServiceRecordType.php (3)
  • fromServer (30-35)
  • service (49-54)
  • protocol (56-61)
🔇 Additional comments (7)
subdomains/src/Models/Subdomain.php (6)

6-13: LGTM: Clean import additions.

All new imports are properly used throughout the refactored methods.


27-42: LGTM: Proper attribute configuration.

The srv_record virtual attribute is correctly configured with fillable, cast, and appends declarations.


44-55: LGTM: Consolidated lifecycle hooks.

Using the saved event instead of separate created and updated hooks is cleaner and ensures consistent Cloudflare synchronization.


72-89: LGTM: Correct accessor/mutator implementation.

The IPv6 detection now correctly uses filter_var with FILTER_VALIDATE_IP and FILTER_FLAG_IPV6, and null-safe operators prevent potential NPEs.


174-211: LGTM: Clean A/AAAA record handling.

The A/AAAA path correctly validates IP addresses (including wildcard addresses), properly calls upsertDnsRecord with 6 parameters, and provides clear success/failure notifications.


214-237: LGTM: Correct delete implementation.

The delete flow properly validates prerequisites, uses the CloudflareService, and sends appropriate conditional notifications. The previous duplicate notification issue has been resolved.

subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php (1)

367-377: Service name configuration is properly implemented via egg tags.

The ServiceRecordType enum correctly determines the service name from the server's egg tags using fromServer() and fromTags() methods. The system checks $server->egg->tags and matches them against enum cases, making the service name configurable per server rather than hardcoded. The srv_target field at the node level is appropriately placed and works with this design.

Comment on lines 378 to 388
->dehydrateStateUsing(function ($state) {
$this->record->forceFill(['srv_target' => $state])->save();

Notification::make()
->title(trans('subdomains::strings.notifications.srv_target_updated_title'))
->body(trans('subdomains::strings.notifications.srv_target_updated'))
->warning()
->send();

return $state;
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect use of dehydrateStateUsing for side effects.

The dehydrateStateUsing callback is performing direct database saves and sending notifications, which violates Filament's form lifecycle pattern:

  1. Premature persistence: Calling forceFill() and save() here bypasses form validation and saves the field before the rest of the form is processed. If the main form save fails, this field will still be persisted, causing data inconsistency.
  2. Bypassed protection: forceFill() bypasses mass assignment protection.
  3. Misleading notification: The notification is sent regardless of whether the overall form save succeeds, and uses ->warning() for what appears to be a success message.
  4. Wrong hook: dehydrateStateUsing is intended for state transformation only, not side effects.

The proper pattern is to let Filament's normal save cycle handle persistence and move the notification to mutateFormDataBeforeSave() or an after-save hook.

🔎 Proposed fix

Remove the side effects from dehydrateStateUsing:

 TextInput::make('srv_target')
     ->label(trans('subdomains::strings.srv_target'))
     ->placeholder('play.example.com')
     ->helperText(trans('subdomains::strings.srv_target_help'))
     ->rules(['nullable', 'string', 'regex:/^(?=.{1,253}$)(?!-)[A-Za-z0-9-]{1,63}(?<!-)(?:\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*$/'])
-    ->columnSpan([
-        'default' => 1,
-        'sm' => 1,
-        'md' => 1,
-        'lg' => 3,
-    ])
-    ->dehydrateStateUsing(function ($state) {
-        $this->record->forceFill(['srv_target' => $state])->save();
-        
-        Notification::make()
-            ->title(trans('subdomains::strings.notifications.srv_target_updated_title'))
-            ->body(trans('subdomains::strings.notifications.srv_target_updated'))
-            ->warning()
-            ->send();
-        
-        return $state;
-    }),
+    ->columnSpan([
+        'default' => 1,
+        'sm' => 1,
+        'md' => 1,
+        'lg' => 3,
+    ]),

Then handle the notification in the parent class's save logic or add a mutateFormDataBeforeSave method to this class if custom notification is needed after successful save.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In subdomains/src/Filament/Admin/Nodes/Pages/EditNode.php around lines 378-388,
remove the direct database and notification side-effects from the
dehydrateStateUsing callback (do not call forceFill()/save() or send
Notification there); keep dehydrateStateUsing only for transforming the field
state (return the transformed $state). Persist the updated srv_target as part of
the normal form save flow instead (either rely on Filament's default save,
implement mutateFormDataBeforeSave to adjust incoming form data, or use an
afterSave/afterCreate hook to perform the save/update), and move the
notification to that after-save hook (use a success notification rather than
->warning()). Also stop using forceFill() so mass-assignment protections are
respected—use the model update/save path provided by the form lifecycle.

Comment on lines +147 to +149
$recordName = sprintf('%s.%s.%s', $serviceRecordType->service(), $serviceRecordType->protocol(), $this->name);

$result = $registrar->upsertDnsRecord($zoneId, $recordName, 'SRV', $this->server->node->srv_target, $this->cloudflare_id, $port, $serviceRecordType);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Remove extra parameter from upsertDnsRecord call.

Line 149 passes 7 arguments to upsertDnsRecord, but the method signature (from CloudflareService.php) only accepts 6 parameters:

public function upsertDnsRecord(string $zoneId, string $name, string $recordType, string $target, ?string $recordId = null, ?int $port = null): array

The 7th parameter $serviceRecordType will cause a runtime error. The service type information is already encoded in $recordName on line 147, so this parameter is unnecessary.

🔎 Proposed fix
         $recordName = sprintf('%s.%s.%s', $serviceRecordType->service(), $serviceRecordType->protocol(), $this->name);
 
-        $result = $registrar->upsertDnsRecord($zoneId, $recordName, 'SRV', $this->server->node->srv_target, $this->cloudflare_id, $port, $serviceRecordType);
+        $result = $registrar->upsertDnsRecord($zoneId, $recordName, 'SRV', $this->server->node->srv_target, $this->cloudflare_id, $port);
🤖 Prompt for AI Agents
In subdomains/src/Models/Subdomain.php around lines 147 to 149, the call to
$registrar->upsertDnsRecord passes seven arguments but the method signature in
CloudflareService accepts only six; remove the extra $serviceRecordType argument
(the service info is already included in $recordName) so the call uses: zoneId,
recordName, 'SRV', target, cloudflare_id, port.

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.

2 participants