From ef91600b92b5e9f00571bc9f0068aab95923b15d Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:42:16 +0330 Subject: [PATCH 01/22] Add shared Blade interface primitives --- .../views/components/ui/stat-card.blade.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core/resources/views/components/ui/stat-card.blade.php diff --git a/core/resources/views/components/ui/stat-card.blade.php b/core/resources/views/components/ui/stat-card.blade.php new file mode 100644 index 0000000..ba0e35f --- /dev/null +++ b/core/resources/views/components/ui/stat-card.blade.php @@ -0,0 +1,21 @@ +@props([ + 'label', + 'value', + 'description' => null, + 'tone' => 'neutral', + 'href' => null, +]) + +@php($tag = $href ? 'a' : 'div') + +<{{ $tag }} + {{ $attributes->class('cdn-stat-card') }} + data-tone="{{ $tone }}" + @if ($href) href="{{ $href }}" aria-label="Open {{ $label }}" @endif +> +
{{ $label }}
+
{{ $value }}
+ @if (filled($description)) +
{{ $description }}
+ @endif + From 4c5332a6a22e69533c1eb0ea44c163169fc2ef47 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:42:26 +0330 Subject: [PATCH 02/22] Add shared Blade interface primitives --- core/resources/views/components/ui/status-pill.blade.php | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 core/resources/views/components/ui/status-pill.blade.php diff --git a/core/resources/views/components/ui/status-pill.blade.php b/core/resources/views/components/ui/status-pill.blade.php new file mode 100644 index 0000000..d3bcf28 --- /dev/null +++ b/core/resources/views/components/ui/status-pill.blade.php @@ -0,0 +1,5 @@ +@props(['tone' => 'neutral']) + +class('cdn-status-pill') }} data-tone="{{ $tone }}"> + {{ $slot }} + From fe3bcd798dc3cf1bb0e952e510cef25716a4292f Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:42:39 +0330 Subject: [PATCH 03/22] Add shared Blade interface primitives --- .../views/components/ui/empty-state.blade.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 core/resources/views/components/ui/empty-state.blade.php diff --git a/core/resources/views/components/ui/empty-state.blade.php b/core/resources/views/components/ui/empty-state.blade.php new file mode 100644 index 0000000..ba2d68c --- /dev/null +++ b/core/resources/views/components/ui/empty-state.blade.php @@ -0,0 +1,14 @@ +@props([ + 'title' => null, + 'description' => null, +]) + +
class('cdn-empty-state') }} role="status"> + @if (filled($title)) +
{{ $title }}
+ @endif +
{{ $description ?? $slot }}
+ @if (isset($actions)) +
{{ $actions }}
+ @endif +
From 4bb07e46ff4b8772295261dcc3821fa23fe50900 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:42:51 +0330 Subject: [PATCH 04/22] Add shared Blade interface primitives --- .../views/components/ui/list-row.blade.php | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 core/resources/views/components/ui/list-row.blade.php diff --git a/core/resources/views/components/ui/list-row.blade.php b/core/resources/views/components/ui/list-row.blade.php new file mode 100644 index 0000000..9498054 --- /dev/null +++ b/core/resources/views/components/ui/list-row.blade.php @@ -0,0 +1,20 @@ +@props([ + 'title', + 'meta' => null, + 'href' => null, +]) + +@php($tag = $href ? 'a' : 'div') + +<{{ $tag }} {{ $attributes->class('cdn-list-row') }} @if ($href) href="{{ $href }}" @endif> +
+
{{ $title }}
+ @if (filled($meta)) +
{{ $meta }}
+ @endif + {{ $slot }} +
+ @if (isset($aside)) +
{{ $aside }}
+ @endif + From d83fa0a054970142857d959a2f72b60aefa1232a Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:43:24 +0330 Subject: [PATCH 05/22] Replace the starter landing page --- core/resources/css/app.css | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/core/resources/css/app.css b/core/resources/css/app.css index 62b9686..dceab20 100644 --- a/core/resources/css/app.css +++ b/core/resources/css/app.css @@ -7,3 +7,47 @@ --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; } + +@layer base { + body { margin: 0; } +} + +@layer components { + .cdn-landing-body { min-height: 100vh; background: #f8fafc; color: #0f172a; font-family: var(--font-sans); } + .cdn-landing-shell { width: min(72rem, calc(100% - 2rem)); margin-inline: auto; } + .cdn-landing-header { display: flex; min-height: 5rem; align-items: center; justify-content: space-between; gap: 1rem; border-bottom: 1px solid #e2e8f0; } + .cdn-landing-brand { display: inline-flex; align-items: center; gap: .65rem; color: inherit; font-weight: 750; letter-spacing: -.02em; text-decoration: none; } + .cdn-landing-mark { display: grid; width: 2rem; height: 2rem; place-items: center; border-radius: .65rem; background: #2563eb; color: white; font-size: .875rem; } + .cdn-landing-link { color: #475569; font-size: .875rem; font-weight: 600; text-decoration: none; } + .cdn-landing-link:hover { color: #1d4ed8; } + .cdn-landing-hero { display: grid; align-items: center; gap: 3rem; padding-block: clamp(4rem, 10vw, 8rem); } + .cdn-landing-eyebrow { margin: 0 0 1rem; color: #2563eb; font-size: .75rem; font-weight: 750; letter-spacing: .1em; text-transform: uppercase; } + .cdn-landing-hero h1 { max-width: 48rem; margin: 0; font-size: clamp(2.4rem, 7vw, 4.5rem); font-weight: 750; letter-spacing: -.055em; line-height: 1.02; } + .cdn-landing-copy { max-width: 42rem; margin: 1.5rem 0 0; color: #475569; font-size: 1.05rem; line-height: 1.75; } + .cdn-landing-actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; } + .cdn-landing-button { display: inline-flex; min-height: 2.75rem; align-items: center; justify-content: center; border: 1px solid #2563eb; border-radius: .7rem; background: #2563eb; color: white; padding: .65rem 1rem; font-size: .875rem; font-weight: 700; text-decoration: none; } + .cdn-landing-button:hover { background: #1d4ed8; } + .cdn-landing-button-secondary { border-color: #cbd5e1; background: white; color: #1e293b; } + .cdn-landing-button-secondary:hover { border-color: #94a3b8; background: #f8fafc; } + .cdn-landing-panel { border: 1px solid #dbeafe; border-radius: 1.25rem; background: white; padding: 1.5rem; box-shadow: 0 24px 60px rgb(15 23 42 / .08); } + .cdn-landing-panel-label { color: #64748b; font-size: .75rem; font-weight: 750; letter-spacing: .08em; text-transform: uppercase; } + .cdn-landing-panel ul { display: grid; gap: .25rem; margin: 1rem 0 0; padding: 0; list-style: none; } + .cdn-landing-panel li { display: flex; gap: 1rem; align-items: center; border-radius: .75rem; padding: .9rem; } + .cdn-landing-panel li:nth-child(odd) { background: #f8fafc; } + .cdn-landing-panel li > span { color: #2563eb; font-size: .75rem; font-weight: 750; } + .cdn-landing-panel strong, .cdn-landing-panel small { display: block; } + .cdn-landing-panel small { margin-top: .2rem; color: #64748b; } +} + +@media (min-width: 56rem) { + .cdn-landing-hero { grid-template-columns: minmax(0, 1.45fr) minmax(18rem, .55fr); } +} + +@media (prefers-color-scheme: dark) { + .cdn-landing-body { background: #090e1a; color: #f8fafc; } + .cdn-landing-header { border-color: #1e293b; } + .cdn-landing-link, .cdn-landing-copy { color: #94a3b8; } + .cdn-landing-button-secondary, .cdn-landing-panel { border-color: #334155; background: #111827; color: #f8fafc; } + .cdn-landing-button-secondary:hover, .cdn-landing-panel li:nth-child(odd) { background: #1e293b; } + .cdn-landing-panel small, .cdn-landing-panel-label { color: #94a3b8; } +} From 0ef8cced41bf00518f8d28dd98bb0b7d82a8e45e Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:43:33 +0330 Subject: [PATCH 06/22] Replace the starter landing page --- core/resources/views/welcome.blade.php | 245 ++++--------------------- 1 file changed, 33 insertions(+), 212 deletions(-) diff --git a/core/resources/views/welcome.blade.php b/core/resources/views/welcome.blade.php index 22d6130..c7f6a62 100644 --- a/core/resources/views/welcome.blade.php +++ b/core/resources/views/welcome.blade.php @@ -3,220 +3,41 @@ - - {{ config('app.name', 'Laravel') }} - - - - @if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot'))) - @vite(['resources/css/app.css', 'resources/js/app.js']) - @else - - @endif + + + {{ config('app.name', 'CDNFoundry') }} + @vite(['resources/css/app.css', 'resources/js/app.js']) - -
- @if (Route::has('login')) - - @endif -
-
-
-
-

Let's get started

-

With so many options available to you,
we suggest you start with the following:

- - - -

- v{{ app()->version() }} - - View changelog - - - - -

+ +
+
+ + + CDNFoundry + + Service health +
+ +
+
+

Private CDN control plane

+

Operate DNS, edge delivery, TLS, cache, and security from one place.

+

Choose the workspace that matches your role. Runtime traffic and security decisions remain independent of this control plane.

+
-
- {{-- Laravel Logo --}} - - - - - - - - - - - {{-- 13 --}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- - @if (Route::has('login')) - - @endif + + +
From f77d4a1575e326b3e9b4fc9e098d03fa8e2e3d1e Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:44:22 +0330 Subject: [PATCH 07/22] Adopt the shared Blade interface primitives --- .../views/filament/domain/pages/dashboard.blade.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/core/resources/views/filament/domain/pages/dashboard.blade.php b/core/resources/views/filament/domain/pages/dashboard.blade.php index 6f86449..7eafdf8 100644 --- a/core/resources/views/filament/domain/pages/dashboard.blade.php +++ b/core/resources/views/filament/domain/pages/dashboard.blade.php @@ -2,11 +2,7 @@
@@ -19,12 +15,12 @@
{{ $domain->display_name ?: $domain->name }}
{{ $domain->name }} · {{ $domain->dns_records_count }} records · {{ $domain->proxied_records_count }} proxied
- + {{ str($domain->lifecycle_state->value)->replace('_', ' ')->headline() }} - + @empty -
No domains are assigned yet. Create your first domain to begin.
+ @endforelse
From 75e22fc6cd19f8b68fa73e3703884eb2291f77a8 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:45:00 +0330 Subject: [PATCH 08/22] Adopt the shared Blade interface primitives --- .../views/filament/admin/pages/platform-settings.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/resources/views/filament/admin/pages/platform-settings.blade.php b/core/resources/views/filament/admin/pages/platform-settings.blade.php index 70a4c8f..ff79d98 100644 --- a/core/resources/views/filament/admin/pages/platform-settings.blade.php +++ b/core/resources/views/filament/admin/pages/platform-settings.blade.php @@ -1,6 +1,6 @@
{{ $this->form }} - Validate and save platform settings +
From 82e97e250080b23d9fe94dbf7a0025b7685f123c Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:45:14 +0330 Subject: [PATCH 09/22] Adopt the shared Blade interface primitives --- .../filament/admin/pages/system-dns-identity.blade.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/resources/views/filament/admin/pages/system-dns-identity.blade.php b/core/resources/views/filament/admin/pages/system-dns-identity.blade.php index 7e97c4e..0fa6eef 100644 --- a/core/resources/views/filament/admin/pages/system-dns-identity.blade.php +++ b/core/resources/views/filament/admin/pages/system-dns-identity.blade.php @@ -1,13 +1,16 @@
{{ $this->form }} - Validate and preview + @if ($preview)

Review the normalized high-risk DNS identity payload before applying it.

{{ json_encode($preview, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}
- Confirm and queue update + + Confirm and queue update + Queuing update… +
@endif
From 9746995a135861936732a8f49fb839a9ed6da6f7 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:49:49 +0330 Subject: [PATCH 10/22] Expose supported runtime operations in Filament --- .../DnsClusters/Pages/ListDnsClusters.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/app/Filament/Admin/Resources/DnsClusters/Pages/ListDnsClusters.php b/core/app/Filament/Admin/Resources/DnsClusters/Pages/ListDnsClusters.php index e1d1c24..c030dbe 100644 --- a/core/app/Filament/Admin/Resources/DnsClusters/Pages/ListDnsClusters.php +++ b/core/app/Filament/Admin/Resources/DnsClusters/Pages/ListDnsClusters.php @@ -3,7 +3,10 @@ namespace App\Filament\Admin\Resources\DnsClusters\Pages; use App\Filament\Admin\Resources\DnsClusters\DnsClusterResource; +use App\Http\Controllers\Admin\DnsOperationController; +use Filament\Actions\Action; use Filament\Actions\CreateAction; +use Filament\Notifications\Notification; use Filament\Resources\Pages\ListRecords; class ListDnsClusters extends ListRecords @@ -12,6 +15,19 @@ class ListDnsClusters extends ListRecords protected function getHeaderActions(): array { - return [CreateAction::make()]; + return [ + Action::make('reconcileAllZones') + ->label('Reconcile all zones') + ->icon('heroicon-o-arrow-path') + ->color('warning') + ->requiresConfirmation() + ->action(function (): void { + $response = app(DnsOperationController::class)->reconcile(request()); + $operation = $response->getData(true)['data']; + Notification::make()->info()->title('Global DNS reconciliation queued') + ->body("Operation {$operation['id']} will process active zones in bounded chunks.")->send(); + }), + CreateAction::make(), + ]; } } From 7972d57aa521dad01d7099a1d807d2e23f6d1078 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:50:28 +0330 Subject: [PATCH 11/22] Expose supported runtime operations in Filament --- core/app/Filament/Admin/Pages/Telemetry.php | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/core/app/Filament/Admin/Pages/Telemetry.php b/core/app/Filament/Admin/Pages/Telemetry.php index b0d1d32..9c6eed7 100644 --- a/core/app/Filament/Admin/Pages/Telemetry.php +++ b/core/app/Filament/Admin/Pages/Telemetry.php @@ -2,9 +2,15 @@ namespace App\Filament\Admin\Pages; +use App\Http\Controllers\Admin\UsageController; +use App\Models\Domain; use App\Models\UsageRollup; use App\Support\AnalyticsStore; use Carbon\CarbonImmutable; +use Filament\Actions\Action; +use Filament\Forms\Components\DateTimePicker; +use Filament\Forms\Components\Select; +use Filament\Notifications\Notification; use Filament\Pages\Page; use Illuminate\Support\Facades\Http; use Throwable; @@ -21,6 +27,39 @@ class Telemetry extends Page protected string $view = 'filament.admin.pages.telemetry'; + protected function getHeaderActions(): array + { + return [ + Action::make('rebuildUsage') + ->label('Rebuild usage') + ->icon('heroicon-o-arrow-path') + ->color('warning') + ->schema([ + Select::make('domain_id')->label('Domain (optional)') + ->options(fn (): array => Domain::query()->orderBy('name')->limit(500)->pluck('name', 'id')->all()) + ->searchable(), + DateTimePicker::make('from')->label('From (UTC)')->timezone('UTC')->seconds(false)->required(), + DateTimePicker::make('to')->label('To (UTC)')->timezone('UTC')->seconds(false)->required()->after('from'), + ]) + ->fillForm(fn (): array => [ + 'from' => CarbonImmutable::now('UTC')->subHour()->startOfHour(), + 'to' => CarbonImmutable::now('UTC')->startOfHour(), + ]) + ->requiresConfirmation() + ->action(function (array $data): void { + request()->merge([ + 'domain_id' => $data['domain_id'] ?? null, + 'from' => CarbonImmutable::parse($data['from'], 'UTC')->startOfHour()->toIso8601String(), + 'to' => CarbonImmutable::parse($data['to'], 'UTC')->startOfHour()->toIso8601String(), + ]); + $response = app(UsageController::class)->rebuild(request()); + $operation = $response->getData(true)['data']; + Notification::make()->info()->title('Usage rebuild queued') + ->body("Operation {$operation['operation_id']} will rebuild complete UTC hours without changing serving behavior.")->send(); + }), + ]; + } + public function getStateProperty(): array { $store = app(AnalyticsStore::class); From e530f78b981c4c1d535053b48cd8295dd5c70399 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:50:45 +0330 Subject: [PATCH 12/22] Expose supported runtime operations in Filament --- .../Resources/Domains/Pages/ViewDomain.php | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php b/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php index f019194..1bf1bf9 100644 --- a/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php +++ b/core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php @@ -6,6 +6,8 @@ use App\Enums\DomainLifecycleState; 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; @@ -57,6 +59,22 @@ public function getSubheading(): ?string protected function getHeaderActions(): array { $actions = [ + Action::make('reconcileDns')->label('Reconcile authoritative DNS')->icon('heroicon-o-arrow-path') + ->visible(fn (): bool => $this->record->lifecycle_state === DomainLifecycleState::Active) + ->action(function (): void { + $response = app(DnsDeploymentController::class)->reconcile(request(), $this->record); + $operation = $response->getData(true)['data']; + 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]) @@ -399,12 +417,12 @@ static function () use ($zone): void { ->all(); return [ - ActionGroup::make($group(['verifyNameservers', 'forceVerifyNameservers', 'activate', 'disable', 'importZone', 'exportZone'])) + ActionGroup::make($group(['verifyNameservers', 'forceVerifyNameservers', 'reconcileDns', 'activate', 'disable', 'importZone', 'exportZone'])) ->label('Domain actions') ->icon('heroicon-o-globe-alt') ->color('gray') ->button(), - ActionGroup::make($group(['proxyDefaults', 'rollbackProxy', 'moveEdgePool'])) + ActionGroup::make($group(['deployEdge', 'proxyDefaults', 'rollbackProxy', 'moveEdgePool'])) ->label('Delivery') ->icon('heroicon-o-cloud') ->button(), From 4a0978db85c66498392266daaa4a99557c1bd92a Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:51:20 +0330 Subject: [PATCH 13/22] Organize panel navigation consistently --- core/app/Filament/Shared/Pages/ApiTokens.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/app/Filament/Shared/Pages/ApiTokens.php b/core/app/Filament/Shared/Pages/ApiTokens.php index eeee959..9815a17 100644 --- a/core/app/Filament/Shared/Pages/ApiTokens.php +++ b/core/app/Filament/Shared/Pages/ApiTokens.php @@ -14,6 +14,8 @@ class ApiTokens extends Page protected static ?string $navigationLabel = 'API tokens'; + protected static string|\UnitEnum|null $navigationGroup = 'Account'; + protected static ?string $slug = 'tokens'; protected string $view = 'filament.shared.pages.api-tokens'; From 23b803fbc70628dbab12edd1a840b2ef9a81b878 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:51:37 +0330 Subject: [PATCH 14/22] Organize panel navigation consistently --- core/app/Providers/Filament/AdminPanelProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/app/Providers/Filament/AdminPanelProvider.php b/core/app/Providers/Filament/AdminPanelProvider.php index 3fb159b..2297aa1 100644 --- a/core/app/Providers/Filament/AdminPanelProvider.php +++ b/core/app/Providers/Filament/AdminPanelProvider.php @@ -36,7 +36,7 @@ public function panel(Panel $panel): Panel ->colors(['primary' => Color::Blue]) ->sidebarCollapsibleOnDesktop() ->databaseNotifications() - ->navigationGroups(['Control plane', 'Customers', 'Edge network', 'Operations']) + ->navigationGroups(['Control plane', 'Customers', 'Edge network', 'Operations', 'Observe', 'Account']) ->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources') ->resources([DomainResource::class]) ->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages') From da61403c4531ed8ad14d6288bd2359f926e25583 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:52:02 +0330 Subject: [PATCH 15/22] Test frontend operation and permission coverage --- core/tests/Feature/FilamentWorkflowTest.php | 72 +++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/core/tests/Feature/FilamentWorkflowTest.php b/core/tests/Feature/FilamentWorkflowTest.php index 5077655..0a27ced 100644 --- a/core/tests/Feature/FilamentWorkflowTest.php +++ b/core/tests/Feature/FilamentWorkflowTest.php @@ -3,16 +3,24 @@ namespace Tests\Feature; use App\Enums\DomainLifecycleState; +use App\Filament\Admin\Pages\Telemetry; +use App\Filament\Admin\Resources\DnsClusters\Pages\ListDnsClusters; use App\Filament\Admin\Resources\Edges\Pages\EditEdge; +use App\Filament\Admin\Resources\Edges\Pages\ListEdges; use App\Filament\Admin\Resources\Edges\RelationManagers\CellsRelationManager; use App\Filament\Domain\Resources\Domains\Pages\ViewDomain; use App\Filament\Domain\Resources\Domains\RelationManagers\DnsRecordsRelationManager; +use App\Jobs\BuildUsageRollups; +use App\Jobs\ReconcileAllDnsZones; +use App\Jobs\ReconcileAllEdgeDomains; +use App\Jobs\ReconcileDnsZone; use App\Jobs\ReconcileEdgeDomain; use App\Models\Domain; use App\Models\Edge; use App\Models\EdgeArtifact; use App\Models\EdgeCell; use App\Models\EdgePool; +use App\Models\Operation; use App\Models\User; use App\Support\DnsRecordData; use Filament\Facades\Filament; @@ -233,4 +241,68 @@ public function test_disabling_from_the_domain_panel_automatically_queues_edge_r $this->assertSame(5, $domain->revision); Queue::assertPushed(ReconcileEdgeDomain::class, fn (ReconcileEdgeDomain $job): bool => $job->domainId === $domain->id); } + + public function test_administrator_can_queue_global_reconciliation_and_bounded_usage_rebuilds(): void + { + Queue::fake(); + $admin = User::factory()->admin()->create(); + $domain = Domain::query()->create(['name' => 'usage-ui.example.test', 'display_name' => 'Usage UI']); + Filament::setCurrentPanel(Filament::getPanel('admin')); + $this->actingAs($admin); + + Livewire::test(ListDnsClusters::class)->callAction('reconcileAllZones')->assertHasNoActionErrors(); + Livewire::test(ListEdges::class)->callAction('reconcileAllDomains')->assertHasNoActionErrors(); + Livewire::test(Telemetry::class)->callAction('rebuildUsage', data: [ + 'domain_id' => $domain->id, + 'from' => '2026-07-20 08:00:00', + 'to' => '2026-07-20 10:00:00', + ])->assertHasNoActionErrors(); + + $this->assertSame(1, Operation::query()->where('type', 'dns.global_reconcile')->count()); + $this->assertSame(1, Operation::query()->where('type', 'edge.global_reconcile')->count()); + $this->assertDatabaseHas('operations', [ + 'type' => 'usage.rebuild', + 'actor_id' => $admin->id, + ]); + Queue::assertPushed(ReconcileAllDnsZones::class); + Queue::assertPushed(ReconcileAllEdgeDomains::class); + Queue::assertPushed(BuildUsageRollups::class); + } + + public function test_domain_reconcile_actions_reuse_the_policy_aware_deployment_endpoints(): void + { + Queue::fake(); + $user = User::factory()->create(); + $domain = Domain::query()->create([ + 'name' => 'reconcile-ui.example.test', + 'display_name' => 'Reconcile UI', + 'lifecycle_state' => DomainLifecycleState::Active, + 'revision' => 3, + ]); + $domain->users()->attach($user); + $domain->dnsRecords()->create(DnsRecordData::validate([ + 'type' => 'A', + 'name' => 'www', + 'content' => '192.0.2.20', + 'ttl' => 300, + 'mode' => 'proxied', + 'origin' => [ + 'host' => '8.8.8.8', 'port' => 443, 'scheme' => 'https', + 'host_header' => 'www.reconcile-ui.example.test', 'sni' => 'www.reconcile-ui.example.test', + 'verify_tls' => true, 'connect_timeout_ms' => 2000, 'response_timeout_ms' => 30000, + 'retry_count' => 0, 'websocket' => false, 'health_check' => null, + ], + ], $domain->name)); + Filament::setCurrentPanel(Filament::getPanel('app')); + $this->actingAs($user); + + Livewire::test(ViewDomain::class, ['record' => $domain->id]) + ->callAction('reconcileDns')->assertHasNoActionErrors() + ->callAction('deployEdge')->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 27592f14d883372f26954526c87eaace18abc795 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:52:22 +0330 Subject: [PATCH 16/22] Test frontend operation and permission coverage --- core/tests/Feature/FilamentPanelAccessTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tests/Feature/FilamentPanelAccessTest.php b/core/tests/Feature/FilamentPanelAccessTest.php index b9928d8..d888274 100644 --- a/core/tests/Feature/FilamentPanelAccessTest.php +++ b/core/tests/Feature/FilamentPanelAccessTest.php @@ -77,7 +77,7 @@ public function test_administrator_operational_pages_render_and_domain_users_can $admin = User::factory()->admin()->create(); $user = User::factory()->create(); - foreach (['/admin/users', '/admin/domains', '/admin/dns-clusters', '/admin/edges', '/admin/edge-pools', '/admin/operations', '/admin/audit-logs', '/admin/system-dns-identity', '/admin/platform-settings', '/admin/tokens', '/admin/profile'] as $path) { + foreach (['/admin/users', '/admin/domains', '/admin/dns-clusters', '/admin/edges', '/admin/edge-pools', '/admin/operations', '/admin/audit-logs', '/admin/system-dns-identity', '/admin/platform-settings', '/admin/telemetry', '/admin/tokens', '/admin/profile'] as $path) { $this->actingAs($admin)->get($path)->assertOk(); $this->actingAs($user)->get($path)->assertForbidden(); } From 36766f5913158b493b1c583d4892b1beea731805 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:58:22 +0330 Subject: [PATCH 17/22] Add a reusable responsive data table --- .../views/components/ui/data-table.blade.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 core/resources/views/components/ui/data-table.blade.php diff --git a/core/resources/views/components/ui/data-table.blade.php b/core/resources/views/components/ui/data-table.blade.php new file mode 100644 index 0000000..92a93b6 --- /dev/null +++ b/core/resources/views/components/ui/data-table.blade.php @@ -0,0 +1,13 @@ +@props(['caption' => null]) + +
class('cdn-table-wrap') }}> + + @if (filled($caption)) + + @endif + @if (isset($header)) + {{ $header }} + @endif + {{ $slot }} +
{{ $caption }}
+
From 3452b15aacaa7d9accfb5f7300ebc301f1db5b5a Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:58:34 +0330 Subject: [PATCH 18/22] Document frontend route coverage --- docs/frontend-route-coverage.md | 74 +++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/frontend-route-coverage.md diff --git a/docs/frontend-route-coverage.md b/docs/frontend-route-coverage.md new file mode 100644 index 0000000..4c10444 --- /dev/null +++ b/docs/frontend-route-coverage.md @@ -0,0 +1,74 @@ +# Frontend route coverage + +This audit covers the application-owned routes in `core/routes/web.php` and +`core/routes/api.php`. Framework routes for Livewire, Filament, Sanctum, +Horizon internals, local storage, and Laravel's fallback health route are not +product actions and are excluded. + +The browser UI is intentionally a policy-aware Filament surface, not an HTTP +client pasted over the API. Browser actions call the same controllers, +validators, policies, models, and queued jobs as their API equivalents. An API +route remains API-only when it exists for automation, pagination, detailed +machine diagnostics, or an edge agent rather than for a person to manage. + +## Public and account routes + +| Route family | Browser surface | Coverage | +|---|---|---| +| `GET /`, `/api/health`, `/api/ready`, `/api/nameservers` | Product landing page links to both panels and service health; nameservers appear in domain delegation | Complete | +| `/api/auth/login`, `/api/auth/logout`, `/api/me` | Filament login/logout and shared Profile page | Complete | +| `/api/me/tokens` | Shared API tokens page with one-time secret display and revoke confirmation | Complete | +| `/api/operations/{operation}` | Domain actions show operation IDs; administrator Operations lists global detail and retry state | Complete | + +## Domain-user routes + +| API route family | Browser surface | Coverage | +|---|---|---| +| `/api/domains` and `/api/domains/{domain}` | Domains resource: assigned-domain list, create, status, display label, disable, and lifecycle state | Complete | +| `/verify-nameservers`, `/activate`, `/status` | Domain actions and Domain status card | Complete | +| `/dns/records`, `/dns/import`, `/dns/export` | DNS records relation with create/edit/delete/bulk delete, BIND import/export, type-aware validation, and permission-gated NS rows | Complete | +| `/dns/records/{record}/geo` and `/geo/preview` | Geo-DNS fields and Preview record action | Complete | +| `/dns/deployment` and `/dns/reconcile` | Authoritative DNS deployment card and **Reconcile authoritative DNS** action | Complete | +| `/proxy`, `/origin`, `/origin/test`, `/origin/health` | Delivery settings, proxied-record origin form, test action, and visible health state | Complete | +| `/deployment`, `/deploy`, `/rollback`, `/revisions` | Delivery status, validated revisions, and rollback action. Edge deploy remains deliberately automatic in the UI per the roadmap; the API trigger is for automation | Complete by contract | +| `/cache`, `/cache/development-mode`, `/cache/purge`, `/cache/purges` | Cache menu: settings, bounded development mode, epoch/URL purge, and latest delivery state | Complete | +| `/tls`, `/tls/status`, `/tls/reissue`, `/tls/renew`, `/tls/upload`, `/tls/custom-certificate` | TLS menu and TLS status card | Complete | +| `/security`, `/security/rules`, `/security/rules/import` | Security profile action and ordered Security rules relation | Complete | +| `/security/ddos`, `/security/ddos/status`, `/security/ddos/events`, `/security/events` | Security readiness/status card, bounded settings, state actions for admins, and recent reason codes | Complete | +| `/analytics/*`, `/logs/*` | Assigned-domain Analytics and logs page with all aggregate views and bounded masked previews | Complete | +| `/usage`, `/usage/export` | Finalized usage table and session-authenticated CSV export | Complete | + +Advanced custom origin ports remain API-only as required by the roadmap. The +browser form exposes the standard HTTP/80 and HTTPS/443 pairs so the common +path stays safe and understandable. + +## Administrator routes + +| API route family | Browser surface | Coverage | +|---|---|---| +| `/api/admin/users` and user domain assignment | Users resource plus the domain Users relation | Complete | +| `/api/admin/audit-logs` | Read-only Audit logs resource | Complete | +| `/api/admin/domains/{domain}/force-verify` | Permission-gated Force verify domain action | Complete | +| `/api/admin/dns/clusters` | DNS clusters resource with secret-safe create/edit, test, enable, and disable | Complete | +| `/api/admin/dns/deployments`, `/failed-deployments`, `/reconcile` | Domain deployment cards and cluster-level **Reconcile all zones** action; failures are searchable in Operations | Complete | +| `/api/admin/system/status`, `/api/admin/operations` | Dashboard queue/health cards and Operations resource with guarded retry | Complete | +| `/api/admin/system/settings*` | Platform settings and System DNS identity pages with preview/confirmation for high-risk identity changes | Complete | +| `/api/admin/edges` | Edges resource with enrollment secret boundary, edit, rotate, enable/disable, drain/undrain, and emergency state | Complete | +| `/api/admin/edge-pools` | Service pools resource with provisioning, enable/disable, withdrawal, and emergency state | Complete | +| `/api/admin/edge-cells*` | Per-edge Cells relation with address edit, drain/undrain, restart, emergency state, and runtime diagnostics | Complete | +| `/api/admin/edge-deployments`, `/edge-routing`, `/edge-deployments/reconcile` | Domain delivery cards, pool targets, edge diagnostics, and **Reconcile all domains** maintenance action | Complete | +| `/api/admin/domains/{domain}/isolation`, `/move`, `/restrict`, `/quarantine`, `/release` | Permission-gated Delivery and Security domain actions | Complete | +| `/api/admin/analytics/*`, `/logs/*`, `/usage`, `/usage/export`, `/usage/rebuild` | Telemetry and usage page, masked previews, global CSV, and bounded **Rebuild usage** action | Complete | + +## Machine-only routes + +`/edge/v1/register`, heartbeat, manifest/artifact/full configuration, +apply/reject acknowledgements, task polling, and task results are an authenticated +edge-agent protocol. Exposing them as human buttons would violate the runtime +architecture and one-time enrollment boundary. Their human-observable state is +available through Edges, Cells, Operations, Audit logs, and deployment cards. + +Cursor pagination and machine export variants stay API-only; their human +equivalents use Filament pagination, bounded tables, and session-authenticated +CSV downloads. Horizon remains the authenticated administrator UI for its own +framework routes. From 41168357e3c60ba41094385deeabef13579e4590 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 13:59:49 +0330 Subject: [PATCH 19/22] Unify analytics tables and states --- .../filament/domain/pages/analytics.blade.php | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/core/resources/views/filament/domain/pages/analytics.blade.php b/core/resources/views/filament/domain/pages/analytics.blade.php index 49b272f..b17aa89 100644 --- a/core/resources/views/filament/domain/pages/analytics.blade.php +++ b/core/resources/views/filament/domain/pages/analytics.blade.php @@ -34,11 +34,11 @@ @if ($state['domain'])
- ClickHouse {{ $state['available'] ? 'available' : 'unavailable' }} - {{ ($state['meta']['partial'] ?? true) ? 'Partial / provisional' : 'Finalized' }} + ClickHouse {{ $state['available'] ? 'available' : 'unavailable' }} + {{ ($state['meta']['partial'] ?? true) ? 'Partial / provisional' : 'Finalized' }}
@if (!$state['available']) -
Analytics unavailable. DNS and edge serving continue normally; finalized PostgreSQL usage remains available below.
+ @endif
@@ -55,11 +55,7 @@ ['label' => 'Origin errors', 'value' => number_format((int) ($summary['origin_errors'] ?? 0)), 'description' => 'Origin failures', 'tone' => ((int) ($summary['origin_errors'] ?? 0)) > 0 ? 'warning' : 'success'], ['label' => 'Security blocks', 'value' => number_format((int) ($summary['security_blocks'] ?? 0)), 'description' => 'Protection decisions', 'tone' => 'warning'], ] as $stat) -
-
{{ $stat['label'] }}
-
{{ $stat['value'] }}
-
{{ $stat['description'] }}
-
+ @endforeach @@ -79,7 +75,7 @@
{{ $title }}
{{ $metrics ?: 'No activity' }}
@empty -
No data was recorded for this view.
+ @endforelse @@ -101,7 +97,7 @@ @if (isset($row['status'])){{ $row['status'] }}@endif @empty -
No {{ $stream }} events in the last hour.
+ @endforelse @@ -114,18 +110,14 @@
Usage CSV export
-
- - - + + @forelse ($state['usage'] as $row) @empty @endforelse - -
UTC intervalRequestsTransferDNSState
UTC intervalRequestsTransferDNSState
{{ $row['interval'] }}{{ number_format($row['requests']) }}{{ $formatBytes($row['bytes']) }}{{ number_format($row['dns_queries']) }}{{ str($row['status'])->headline() }}
No finalized usage intervals are available yet.
-
+ @endif From fa821e4474d4b9961a0f18329a73cee20d7a5dc1 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:00:01 +0330 Subject: [PATCH 20/22] Unify analytics tables and states --- .../filament/admin/pages/telemetry.blade.php | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/core/resources/views/filament/admin/pages/telemetry.blade.php b/core/resources/views/filament/admin/pages/telemetry.blade.php index 49e6e74..15282b6 100644 --- a/core/resources/views/filament/admin/pages/telemetry.blade.php +++ b/core/resources/views/filament/admin/pages/telemetry.blade.php @@ -14,12 +14,12 @@
- ClickHouse {{ $state['available'] ? 'available' : 'unavailable' }} - Vector metrics {{ $state['buffer']['available'] ? 'available' : 'unavailable' }} - {{ ($state['meta']['partial'] ?? true) ? 'Partial / provisional' : 'Finalized' }} + ClickHouse {{ $state['available'] ? 'available' : 'unavailable' }} + Vector metrics {{ $state['buffer']['available'] ? 'available' : 'unavailable' }} + {{ ($state['meta']['partial'] ?? true) ? 'Partial / provisional' : 'Finalized' }}
@if (!$state['available']) -
Analytics unavailable. Traffic serving is independent and remains active. Finalized PostgreSQL usage is still shown below.
+ @endif
@@ -33,43 +33,31 @@ ['label' => 'Origin errors', 'value' => number_format((int) ($summary['origin_errors'] ?? 0)), 'description' => 'Origin failures requiring review', 'tone' => ((int) ($summary['origin_errors'] ?? 0)) > 0 ? 'warning' : 'success'], ['label' => 'Security blocks', 'value' => number_format((int) ($summary['security_blocks'] ?? 0)), 'description' => 'Bounded protection decisions', 'tone' => 'warning'], ] as $stat) -
-
{{ $stat['label'] }}
-
{{ $stat['value'] }}
-
{{ $stat['description'] }}
-
+ @endforeach
-
- - - + + @forelse ($state['traffic'] as $row) @empty @endforelse - -
UTC hourRequestsTransfer
UTC hourRequestsTransfer
{{ $row['bucket'] ?? 'Unknown' }}{{ number_format((int) ($row['requests'] ?? 0)) }}{{ $formatBytes(((int) ($row['bytes_in'] ?? 0)) + ((int) ($row['bytes_out'] ?? 0))) }}
No traffic was recorded in this range.
-
+
-
- - - + + @forelse ($state['dns'] as $row) @empty @endforelse - -
TypeResponseQueries
TypeResponseQueries
{{ $row['qtype'] ?? 'Unknown' }}{{ $row['rcode'] ?? 'Unknown' }}{{ number_format((int) ($row['queries'] ?? 0)) }}
No DNS activity was recorded in this range.
-
+
@@ -88,7 +76,7 @@ @if (isset($row['status'])){{ $row['status'] }}@endif @empty -
No {{ $stream }} events in the last hour.
+ @endforelse @@ -109,7 +97,7 @@ {{ is_numeric($value) ? number_format((float) $value, 0) : $value }} @empty -
Vector delivery metrics are unavailable.
+ @endforelse @@ -118,18 +106,14 @@
Global usage CSV
-
- - - + + @forelse ($state['usage'] as $row) @empty @endforelse - -
Domain / intervalRequestsTransferDNSState
Domain / intervalRequestsTransferDNSState
{{ $row['domain'] }}
{{ $row['interval'] }}
{{ number_format($row['requests']) }}{{ $formatBytes($row['bytes']) }}{{ number_format($row['dns_queries']) }}{{ str($row['status'])->headline() }}
No finalized usage intervals are available yet.
-
+ From e28a49839a2e79e371a419d9059c6406e2a8d050 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:00:36 +0330 Subject: [PATCH 21/22] Document frontend routes and manual qualification --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ef39789..2661d90 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,9 @@ make dev-scale-e2e `make dev-e2e` uses the real HTTP APIs, PostgreSQL, queues, PowerDNS, DNSdist, mTLS edge control, pool migration, Pebble DNS-01 issuance, cache/purge delivery, and OpenResty HTTP/HTTPS runtime. Browser acceptance is deliberately manual and is specified in -[manual browser qualification](docs/manual-browser-qualification.md). +[manual browser qualification](docs/manual-browser-qualification.md). The +[frontend route coverage audit](docs/frontend-route-coverage.md) maps every +application route family to its human, automation-only, or agent-only surface. Production uses immutable commit-SHA GHCR images through [`compose.prod.yml`](compose.prod.yml); it has no `latest` tags and no production From 5725fc20b2a98d0bfbc8e7f5e6fca766b103c4e3 Mon Sep 17 00:00:00 2001 From: vaheeD khoshnouD Date: Mon, 20 Jul 2026 14:06:54 +0330 Subject: [PATCH 22/22] Use the product title on the landing page --- core/resources/views/welcome.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/resources/views/welcome.blade.php b/core/resources/views/welcome.blade.php index c7f6a62..7a7516e 100644 --- a/core/resources/views/welcome.blade.php +++ b/core/resources/views/welcome.blade.php @@ -5,7 +5,7 @@ - {{ config('app.name', 'CDNFoundry') }} + CDNFoundry @vite(['resources/css/app.css', 'resources/js/app.js'])