Skip to content
18 changes: 17 additions & 1 deletion core/app/Filament/Admin/Resources/Edges/Pages/ListEdges.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 1 addition & 10 deletions core/app/Filament/Domain/Resources/Domains/Pages/ViewDomain.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions core/app/Providers/Filament/DomainPanelProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
1 change: 1 addition & 0 deletions core/phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
</include>
</source>
<php>
<ini name="memory_limit" value="256M"/>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
Expand Down
136 changes: 136 additions & 0 deletions core/resources/css/filament/shared/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
18 changes: 18 additions & 0 deletions core/resources/views/components/ui/form-actions.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@props([
'submit' => 'save',
'label' => 'Save changes',
'loadingLabel' => 'Saving…',
'color' => 'primary',
])

<div {{ $attributes->class('cdn-form-actions') }}>
<x-filament::button
type="submit"
:color="$color"
wire:loading.attr="disabled"
wire:target="{{ $submit }}"
>
<span wire:loading.remove wire:target="{{ $submit }}">{{ $label }}</span>
<span wire:loading wire:target="{{ $submit }}">{{ $loadingLabel }}</span>
</x-filament::button>
</div>
14 changes: 5 additions & 9 deletions core/resources/views/filament/admin/pages/dashboard.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
<div class="cdn-dashboard">
<div class="cdn-stat-grid">
@foreach ($this->summary as $stat)
<a class="cdn-stat-card" data-tone="{{ $stat['tone'] }}" href="{{ $stat['url'] }}" aria-label="Open {{ $stat['label'] }}">
<div class="cdn-stat-label">{{ $stat['label'] }}</div>
<div class="cdn-stat-value">{{ number_format($stat['value']) }}</div>
<div class="cdn-stat-description">{{ $stat['description'] }}</div>
</a>
<x-ui.stat-card :label="$stat['label']" :value="number_format($stat['value'])" :description="$stat['description']" :tone="$stat['tone']" :href="$stat['url']" />
@endforeach
</div>

Expand All @@ -19,9 +15,9 @@
<div class="cdn-row-title">{{ $lane['label'] }}</div>
<div class="cdn-row-meta"><code>{{ $lane['key'] }}</code> · {{ $lane['oldest'] }}</div>
</div>
<span class="cdn-status-pill" data-tone="{{ $lane['tone'] }}">
<x-ui.status-pill :tone="$lane['tone']">
{{ $lane['depth'] === null ? 'Unavailable' : number_format($lane['depth']) }}
</span>
</x-ui.status-pill>
</div>
@endforeach
</div>
Expand All @@ -35,10 +31,10 @@
<div class="cdn-row-title">{{ str($entry->action)->replace(['.', '_'], ' ')->headline() }}</div>
<div class="cdn-row-meta">{{ $entry->actor?->email ?? 'System' }} · {{ $entry->created_at?->diffForHumans() }}</div>
</div>
<span class="cdn-status-pill">#{{ $entry->id }}</span>
<x-ui.status-pill>#{{ $entry->id }}</x-ui.status-pill>
</div>
@empty
<div class="cdn-empty-state">No audit activity has been recorded yet.</div>
<x-ui.empty-state title="No audit activity" description="Configuration and security changes will appear here." />
@endforelse
</div>
</x-filament::section>
Expand Down
26 changes: 16 additions & 10 deletions core/resources/views/filament/shared/pages/api-tokens.blade.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
<x-filament-panels::page>
@if ($plainTextToken)
<x-filament::section heading="New token — copy now">
<code class="break-all select-all">{{ $plainTextToken }}</code>
<x-filament::section heading="New token — copy now" description="This secret is shown once. Store it before leaving this page." icon="heroicon-o-key">
<code class="cdn-secret-value">{{ $plainTextToken }}</code>
</x-filament::section>
@endif

<x-filament::section heading="Create token">
<form wire:submit="createToken" class="flex flex-col gap-3 sm:flex-row sm:items-end">
<x-filament::input.wrapper class="flex-1">
<x-filament::input wire:model="name" placeholder="Token name" maxlength="100" />
</x-filament::input.wrapper>
<x-filament::button type="submit">Create</x-filament::button>
<form wire:submit="createToken" class="cdn-inline-form">
<div class="min-w-0 flex-1">
<label class="cdn-field-label" for="token-name">Token name</label>
<x-filament::input.wrapper :valid="! $errors->has('name')">
<x-filament::input id="token-name" wire:model="name" placeholder="Deployment automation" maxlength="100" autocomplete="off" />
</x-filament::input.wrapper>
@error('name') <p class="cdn-field-error" role="alert">{{ $message }}</p> @enderror
</div>
<x-filament::button type="submit" wire:loading.attr="disabled" wire:target="createToken">
<span wire:loading.remove wire:target="createToken">Create token</span>
<span wire:loading wire:target="createToken">Creating…</span>
</x-filament::button>
</form>
@error('name') <p class="text-sm text-danger-600">{{ $message }}</p> @enderror
</x-filament::section>

<x-filament::section heading="Active tokens">
Expand All @@ -28,10 +34,10 @@
Created {{ $token->created_at?->diffForHumans() }}
</div>
</div>
<x-filament::button color="danger" size="sm" wire:click="revokeToken({{ $token->id }})" wire:confirm="Revoke this token?">Revoke</x-filament::button>
<x-filament::button color="danger" size="sm" wire:click="revokeToken({{ $token->id }})" wire:loading.attr="disabled" wire:target="revokeToken({{ $token->id }})" wire:confirm="Revoke this token?">Revoke</x-filament::button>
</div>
@empty
<p class="text-sm text-gray-500">No API tokens.</p>
<x-ui.empty-state title="No active API tokens" description="Create a named token only when an integration needs API access." />
@endforelse
</div>
</x-filament::section>
Expand Down
7 changes: 2 additions & 5 deletions core/tests/Feature/FilamentWorkflowTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
}
Loading
Loading