From a474190d419e08c0c13e8e7faa69bf7ed3af30bc Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:48:56 +0330 Subject: [PATCH 01/11] Restore global edge reconciliation UI --- .../Admin/Resources/Edges/Pages/ListEdges.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/app/Filament/Admin/Resources/Edges/Pages/ListEdges.php b/core/app/Filament/Admin/Resources/Edges/Pages/ListEdges.php index 3736358..290bbea 100644 --- a/core/app/Filament/Admin/Resources/Edges/Pages/ListEdges.php +++ b/core/app/Filament/Admin/Resources/Edges/Pages/ListEdges.php @@ -3,7 +3,10 @@ namespace App\Filament\Admin\Resources\Edges\Pages; use App\Filament\Admin\Resources\Edges\EdgeResource; +use App\Http\Controllers\Admin\EdgeOperationsController; +use Filament\Actions\Action; use Filament\Actions\CreateAction; +use Filament\Notifications\Notification; use Filament\Resources\Pages\ListRecords; class ListEdges extends ListRecords @@ -12,6 +15,19 @@ class ListEdges extends ListRecords protected function getHeaderActions(): array { - return [CreateAction::make()]; + return [ + Action::make('reconcileAllDomains') + ->label('Reconcile all domains') + ->icon('heroicon-o-arrow-path') + ->color('warning') + ->requiresConfirmation() + ->action(function (): void { + $response = app(EdgeOperationsController::class)->reconcile(request()); + $operation = $response->getData(true)['data']; + Notification::make()->info()->title('Global edge reconciliation queued') + ->body("Operation {$operation['operation_id']} will coalesce domain deployments in bounded chunks.")->send(); + }), + CreateAction::make(), + ]; } } From 1d79414209253547f6d3d8eb8d311a46d5c93214 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:49:09 +0330 Subject: [PATCH 02/11] Restore domain navigation grouping --- core/app/Filament/Domain/Resources/Domains/DomainResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/app/Filament/Domain/Resources/Domains/DomainResource.php b/core/app/Filament/Domain/Resources/Domains/DomainResource.php index b398a47..1bbfe8c 100644 --- a/core/app/Filament/Domain/Resources/Domains/DomainResource.php +++ b/core/app/Filament/Domain/Resources/Domains/DomainResource.php @@ -35,7 +35,7 @@ class DomainResource extends Resource public static function getNavigationGroup(): string|\UnitEnum|null { - return Filament::getCurrentPanel()?->getId() === 'admin' ? 'Customers' : null; + return Filament::getCurrentPanel()?->getId() === 'admin' ? 'Customers' : 'Domains'; } public static function form(Schema $schema): Schema From 75ff664ea6053c89a822efd988be42dc8aa11cc2 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:51:39 +0330 Subject: [PATCH 03/11] Keep edge deployment automatic --- .../Domain/Resources/Domains/Pages/ViewDomain.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php b/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php index 1bf1bf9..e920044 100644 --- a/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php +++ b/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php @@ -7,7 +7,6 @@ use App\Filament\Domain\Resources\Domains\DomainResource; use App\Http\Controllers\CacheController; use App\Http\Controllers\DnsDeploymentController; -use App\Http\Controllers\ProxyController; use App\Jobs\EnsureManagedCertificates; use App\Jobs\ImportDnsZone; use App\Jobs\ReconcileDnsZone; @@ -67,14 +66,6 @@ protected function getHeaderActions(): array Notification::make()->info()->title('DNS reconciliation queued') ->body("Operation {$operation['id']} will preserve the previous valid zone until activation succeeds.")->send(); }), - Action::make('deployEdge')->label('Reconcile edge delivery')->icon('heroicon-o-cloud-arrow-up') - ->visible(fn (): bool => $this->record->dnsRecords()->where('mode', 'proxied')->exists()) - ->action(function (): void { - $response = app(ProxyController::class)->deploy(request(), $this->record); - $operation = $response->getData(true)['data']; - Notification::make()->info()->title('Edge reconciliation queued') - ->body("Operation {$operation['operation_id']} will deploy the latest desired revision.")->send(); - }), Action::make('tlsMode')->label('TLS mode')->icon('heroicon-o-lock-closed')->schema([ Select::make('mode')->options(['managed' => 'Managed', 'custom' => 'Custom', 'disabled' => 'Disabled'])->required(), ])->fillForm(fn (): array => ['mode' => $this->record->tls_mode]) @@ -422,7 +413,7 @@ static function () use ($zone): void { ->icon('heroicon-o-globe-alt') ->color('gray') ->button(), - ActionGroup::make($group(['deployEdge', 'proxyDefaults', 'rollbackProxy', 'moveEdgePool'])) + ActionGroup::make($group(['proxyDefaults', 'rollbackProxy', 'moveEdgePool'])) ->label('Delivery') ->icon('heroicon-o-cloud') ->button(), From a550bdf94bf77596959d32aac673e4c3e6a37e92 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:52:28 +0330 Subject: [PATCH 04/11] Restore domain panel navigation groups --- core/app/Providers/Filament/DomainPanelProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/core/app/Providers/Filament/DomainPanelProvider.php b/core/app/Providers/Filament/DomainPanelProvider.php index f82064f..c98bec6 100644 --- a/core/app/Providers/Filament/DomainPanelProvider.php +++ b/core/app/Providers/Filament/DomainPanelProvider.php @@ -35,6 +35,7 @@ public function panel(Panel $panel): Panel ->readOnlyRelationManagersOnResourceViewPagesByDefault(false) ->colors(['primary' => Color::Blue]) ->sidebarCollapsibleOnDesktop() + ->navigationGroups(['Domains', 'Observe', 'Account']) ->discoverResources(in: app_path('Filament/Domain/Resources'), for: 'App\\Filament\\Domain\\Resources') ->discoverPages(in: app_path('Filament/Domain/Pages'), for: 'App\\Filament\\Domain\\Pages') ->pages([DomainDashboard::class, ApiTokens::class]) From fd82de9e66c58227e55ccf0339395f7f44a5615b Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:53:03 +0330 Subject: [PATCH 05/11] Restore shared interface styles --- core/resources/css/filament/shared/theme.css | 136 +++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/core/resources/css/filament/shared/theme.css b/core/resources/css/filament/shared/theme.css index 5970589..14e2155 100644 --- a/core/resources/css/filament/shared/theme.css +++ b/core/resources/css/filament/shared/theme.css @@ -251,6 +251,137 @@ text-align: center; } +.cdn-empty-title { + color: rgb(51 65 85); + font-size: 0.875rem; + font-weight: 650; +} + +.dark .cdn-empty-title { + color: rgb(226 232 240); +} + +.cdn-empty-description { + margin-top: 0.2rem; + font-size: 0.8125rem; + line-height: 1.25rem; +} + +.cdn-empty-actions { + display: flex; + justify-content: center; + margin-top: 1rem; +} + +.cdn-list-row { + display: flex; + min-width: 0; + align-items: center; + justify-content: space-between; + gap: 1rem; + border-radius: 0.65rem; + padding: 0.7rem 0.75rem; +} + +.cdn-list-row:nth-child(odd) { + background: rgb(248 250 252); +} + +.dark .cdn-list-row:nth-child(odd) { + background: rgb(30 41 59 / 0.55); +} + +.cdn-row-aside { + display: flex; + flex: none; + align-items: center; + gap: 0.5rem; +} + +.cdn-form-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + border-top: 1px solid rgb(226 232 240); + padding-top: 1.25rem; +} + +.dark .cdn-form-actions { + border-top-color: rgb(51 65 85); +} + +.cdn-inline-form { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.cdn-field-label { + display: block; + margin-bottom: 0.4rem; + color: rgb(51 65 85); + font-size: 0.8125rem; + font-weight: 650; +} + +.dark .cdn-field-label { + color: rgb(226 232 240); +} + +.cdn-field-error { + margin-top: 0.35rem; + color: rgb(220 38 38); + font-size: 0.8125rem; +} + +.cdn-secret-value { + display: block; + overflow-wrap: anywhere; + border-radius: 0.65rem; + background: rgb(248 250 252); + padding: 0.85rem; + user-select: all; +} + +.cdn-table-wrap { + max-width: 100%; + overflow-x: auto; +} + +.cdn-data-table { + width: 100%; + border-collapse: collapse; + text-align: start; + font-size: 0.875rem; +} + +.cdn-data-table thead { + color: rgb(100 116 139); + font-size: 0.75rem; + text-transform: uppercase; +} + +.cdn-data-table th, +.cdn-data-table td { + padding: 0.65rem 0.75rem; +} + +.cdn-data-table tbody tr + tr { + border-top: 1px solid rgb(226 232 240); +} + +.dark .cdn-data-table thead { + color: rgb(148 163 184); +} + +.dark .cdn-data-table tbody tr + tr { + border-top-color: rgb(255 255 255 / 0.1); +} + +.dark .cdn-secret-value { + background: rgb(30 41 59); +} + .dark .cdn-empty-state { border-color: rgb(71 85 105); color: rgb(148 163 184); @@ -288,6 +419,11 @@ .cdn-stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + + .cdn-inline-form { + flex-direction: row; + align-items: end; + } } @media (min-width: 64rem) { From 744cf4e608814e1df7ac1066d65285683d95b620 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:53:24 +0330 Subject: [PATCH 06/11] Restore shared dashboard components --- .../views/filament/admin/pages/dashboard.blade.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/core/resources/views/filament/admin/pages/dashboard.blade.php b/core/resources/views/filament/admin/pages/dashboard.blade.php index e862cad..da98a75 100644 --- a/core/resources/views/filament/admin/pages/dashboard.blade.php +++ b/core/resources/views/filament/admin/pages/dashboard.blade.php @@ -2,11 +2,7 @@
@@ -19,9 +15,9 @@
{{ $lane['label'] }}
{{ $lane['key'] }} · {{ $lane['oldest'] }}
- + {{ $lane['depth'] === null ? 'Unavailable' : number_format($lane['depth']) }} - + @endforeach @@ -35,10 +31,10 @@
{{ str($entry->action)->replace(['.', '_'], ' ')->headline() }}
{{ $entry->actor?->email ?? 'System' }} · {{ $entry->created_at?->diffForHumans() }}
- #{{ $entry->id }} + #{{ $entry->id }} @empty -
No audit activity has been recorded yet.
+ @endforelse From d6500871d1d9466dd896a0c548386b983729fb91 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:54:30 +0330 Subject: [PATCH 07/11] Restore API token interaction states --- .../shared/pages/api-tokens.blade.php | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/core/resources/views/filament/shared/pages/api-tokens.blade.php b/core/resources/views/filament/shared/pages/api-tokens.blade.php index 5183f2f..2249442 100644 --- a/core/resources/views/filament/shared/pages/api-tokens.blade.php +++ b/core/resources/views/filament/shared/pages/api-tokens.blade.php @@ -1,18 +1,24 @@ @if ($plainTextToken) - - {{ $plainTextToken }} + + {{ $plainTextToken }} @endif -
- - - - Create + +
+ + + + + @error('name') @enderror +
+ + Create token + Creating… +
- @error('name')

{{ $message }}

@enderror
@@ -28,10 +34,10 @@ Created {{ $token->created_at?->diffForHumans() }} - Revoke + Revoke @empty -

No API tokens.

+ @endforelse
From 000ff7cbf870807eb0ca3eac2ba0e08e64a45945 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:54:49 +0330 Subject: [PATCH 08/11] Restore shared form actions component --- .../views/components/ui/form-actions.blade.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 core/resources/views/components/ui/form-actions.blade.php diff --git a/core/resources/views/components/ui/form-actions.blade.php b/core/resources/views/components/ui/form-actions.blade.php new file mode 100644 index 0000000..218ecc7 --- /dev/null +++ b/core/resources/views/components/ui/form-actions.blade.php @@ -0,0 +1,18 @@ +@props([ + 'submit' => 'save', + 'label' => 'Save changes', + 'loadingLabel' => 'Saving…', + 'color' => 'primary', +]) + +
class('cdn-form-actions') }}> + + {{ $label }} + {{ $loadingLabel }} + +
From f00332a95d7b38f02c589f37383d211ef2ed5122 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:55:17 +0330 Subject: [PATCH 09/11] Correct frontend operation coverage --- core/tests/Feature/FilamentWorkflowTest.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/tests/Feature/FilamentWorkflowTest.php b/core/tests/Feature/FilamentWorkflowTest.php index 0a27ced..cda7f18 100644 --- a/core/tests/Feature/FilamentWorkflowTest.php +++ b/core/tests/Feature/FilamentWorkflowTest.php @@ -269,7 +269,7 @@ public function test_administrator_can_queue_global_reconciliation_and_bounded_u Queue::assertPushed(BuildUsageRollups::class); } - public function test_domain_reconcile_actions_reuse_the_policy_aware_deployment_endpoints(): void + public function test_domain_dns_reconcile_action_reuses_the_policy_aware_endpoint(): void { Queue::fake(); $user = User::factory()->create(); @@ -297,12 +297,9 @@ public function test_domain_reconcile_actions_reuse_the_policy_aware_deployment_ $this->actingAs($user); Livewire::test(ViewDomain::class, ['record' => $domain->id]) - ->callAction('reconcileDns')->assertHasNoActionErrors() - ->callAction('deployEdge')->assertHasNoActionErrors(); + ->callAction('reconcileDns')->assertHasNoActionErrors(); $this->assertDatabaseHas('operations', ['type' => 'dns.zone_reconcile', 'actor_id' => $user->id]); - $this->assertDatabaseHas('operations', ['type' => 'edge.domain_reconcile', 'actor_id' => $user->id]); Queue::assertPushed(ReconcileDnsZone::class, fn (ReconcileDnsZone $job): bool => $job->domainId === $domain->id); - Queue::assertPushed(ReconcileEdgeDomain::class, fn (ReconcileEdgeDomain $job): bool => $job->domainId === $domain->id); } } From b816a212d6cd436b2823749f7ceff1bd368e3368 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:55:42 +0330 Subject: [PATCH 10/11] Restore frontend qualification steps --- docs/manual-browser-qualification.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/manual-browser-qualification.md b/docs/manual-browser-qualification.md index 47106e1..e134986 100644 --- a/docs/manual-browser-qualification.md +++ b/docs/manual-browser-qualification.md @@ -43,7 +43,7 @@ For every phase, check desktop and narrow mobile widths, browser-console errors, ### Administrator checkpoints -1. Sign in at `/admin`. Confirm the blue active-navigation treatment, collapsible desktop sidebar, readable responsive stat cards, **Control plane**, **Customers**, **Edge network**, and **Operations** groups, `Local Administrator`, and no missing-theme or console asset errors. The dashboard must show Domains, Users, DNS clusters, Serving edges, Work in progress, Failed operations, Queue lanes, Recent audit activity, and Common tasks without raw unstyled lists. +1. Open `/` first. Confirm the CDNFoundry landing page (not the Laravel starter screen) links to the domain workspace, administration, and `/api/health`, remains readable in light/dark mode, and has no missing assets. Sign in at `/admin`. Confirm the blue active-navigation treatment, collapsible desktop sidebar, readable responsive stat cards, **Control plane**, **Customers**, **Edge network**, **Operations**, **Observe**, and **Account** groups, `Local Administrator`, and no missing-theme or console asset errors. The dashboard must show Domains, Users, DNS clusters, Serving edges, Work in progress, Failed operations, Queue lanes, Recent audit activity, and Common tasks without raw unstyled lists. 2. Open **Users** and create: | Field | Value | @@ -63,7 +63,7 @@ For every phase, check desktop and narrow mobile widths, browser-console errors, ### Domain-user authorization checkpoints 1. Sign in at `/app` as `user@example.test` / `cdnfoundry-user-test`. -2. Confirm administrator navigation is absent. Confirm the dashboard shows only assigned-domain totals and recent domains, plus the three-step **Start serving a domain** guide; an unassigned domain name must not appear. +2. Confirm administrator navigation is absent and **Domains**, **Observe**, and **Account** are the only application groups. Confirm the dashboard shows only assigned-domain totals and recent domains, plus the three-step **Start serving a domain** guide; an unassigned domain name must not appear. 3. Repeat the personal token and profile checks; changes must affect only this user. 4. Directly request `/admin/users`, `/admin/dns-clusters`, `/admin/audit-logs`, and `/horizon`; all must be forbidden. @@ -148,6 +148,13 @@ dig @127.0.0.1 -p 1053 browser-test.example.test TXT Use PowerAdmin only to inspect derived state. Never edit desired state there. +As administrator, open **DNS clusters**, choose **Reconcile all zones**, confirm +the warning, and copy the resulting `dns.global_reconcile` operation ID. On one +active domain choose **Domain actions → Reconcile authoritative DNS** and confirm +the existing or new `dns.zone_reconcile` operation is shown without a duplicate +active operation. Repeat as its assigned domain user; an unassigned domain must +remain not found. + ### Phase 2 completion gate - Implementation: present. @@ -313,6 +320,10 @@ unchanged MX/TXT/CAA records. 4. Confirm the domain view header shows four compact action menus—**Domain actions**, **Delivery**, **Cache**, and **TLS**—without horizontal overflow at desktop or mobile widths. Confirm the page renders **Domain status**, **Edge delivery**, **Authoritative DNS deployment**, **Cache**, and **TLS** as one ordered stack of cards, with fields reducing to one column on a narrow viewport. Proxy defaults must appear as one readable summary (for example, `Enabled · HTTP/1.1 + HTTP/2 · HTTPS redirect off · 0 origin retries · Maintenance off`) rather than raw JSON or separate boolean/list fragments. Confirm proxied-host count, desired/active revision, placement/pools, failure, and recent validated revisions. The desired revision, active edge revision, retained rollback revisions, and each DNS cluster acknowledgement must show dates rather than bare revision numbers. 5. Send HTTP and HTTPS through both real edges. Confirm correct origin selection, Host, SNI, IPv4/IPv6 behavior, unknown-host/SNI rejection, and continued serving of the last valid revision after a deliberately invalid candidate. 6. Move the domain shared → quarantine → dedicated. For each move record the target-ready acknowledgement, target DNS answer, non-null drain deadline, source-removal artifact, final acknowledgement, and active pool. A failed/rejected target must leave source DNS and traffic active. +7. As administrator, open **Edges**, choose **Reconcile all domains**, confirm the + warning, and verify one `edge.global_reconcile` operation processes domains + in bounded chunks. This is maintenance reconciliation; the per-domain UI must + still have no manual deploy action because normal saves deploy automatically. Saving **Proxy defaults** alone does not turn a DNS-only record into a proxied hostname. Confirm its notice says that no hostname will be deployed until an A, @@ -582,10 +593,13 @@ two Vector batch intervals. Record exact UTC generation times and byte counts. `/admin/telemetry/usage.csv`; both must be forbidden. Confirm no page button navigates to a token-protected `/api/admin/...` URL. 4. Inspect the latest 20 **Finalized usage** rows and open **Global usage CSV**. - Rebuild the generated - complete UTC interval through the documented administrator API/action using - one `Idempotency-Key`; replay it and record the same operation/result. Export - again and confirm the selected domain row and contract version are unchanged. + Choose **Rebuild usage**, optionally select the disposable domain, enter a + complete UTC-hour range no longer than 31 days, confirm, and copy the + `usage.rebuild` operation ID. Confirm an end before the start and a range over + 31 days are rejected. Separately rebuild the same interval through the API + with one `Idempotency-Key`; replay it and record the same operation/result. + Export again and confirm the selected domain row and contract version are + unchanged. 5. Stop only ClickHouse with `docker compose -f compose.dev.yml stop clickhouse`. Refresh both analytics pages: each must render a clear analytics-unavailable message while its panel/navigation stays usable. During the interruption, From 64d80c759817b131fd554888be3b254ae4f3a2e8 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:56:07 +0330 Subject: [PATCH 11/11] Bound PHPUnit memory for frontend suites --- core/phpunit.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/phpunit.xml b/core/phpunit.xml index 571c445..294bc4a 100644 --- a/core/phpunit.xml +++ b/core/phpunit.xml @@ -19,6 +19,7 @@ +