TenantAtlas/app/Filament/Resources/ProviderConnectionResource/Pages/ListProviderConnections.php
ahmido 45a804970e feat: complete admin canonical tenant rollout (#165)
## Summary
- complete Spec 136 canonical admin tenant rollout across admin-visible and shared Filament surfaces
- add the shared panel-aware tenant resolver helper, persisted filter-state synchronization, and admin navigation segregation for tenant-sensitive resources
- expand regression, guard, and parity coverage for admin-path tenant resolution, stale filters, workspace-wide tenant-default surfaces, and panel split behavior

## Validation
- `vendor/bin/sail artisan test --compact tests/Feature/Guards/AdminTenantResolverGuardTest.php`
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/TableStatePersistenceTest.php`
- `vendor/bin/sail artisan test --compact --filter='CanonicalAdminTenantFilterState|PolicyResource|BackupSchedule|BackupSet|FindingResource|BaselineCompareLanding|RestoreRunResource|InventoryItemResource|PolicyVersionResource|ProviderConnectionResource|TenantDiagnostics|InventoryCoverage|InventoryKpiHeader|AuditLog|EntraGroup'`
- `vendor/bin/sail bin pint --dirty --format agent`

## Notes
- Livewire v4.0+ compliance is preserved with Filament v5.
- Provider registration remains unchanged in `bootstrap/providers.php`.
- `PolicyResource` and `PolicyVersionResource` have admin global search disabled explicitly; `EntraGroupResource` keeps admin-aware scoped search with a View page.
- Destructive and governance-sensitive actions retain existing confirmation and authorization behavior while using canonical tenant parity.
- No new assets were introduced, so deployment asset strategy is unchanged and does not add new `filament:assets` work.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #165
2026-03-13 08:09:20 +00:00

253 lines
8.2 KiB
PHP

<?php
namespace App\Filament\Resources\ProviderConnectionResource\Pages;
use App\Filament\Resources\ProviderConnectionResource;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Auth\CapabilityResolver;
use App\Support\Auth\Capabilities;
use App\Support\Filament\CanonicalAdminTenantFilterState;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListProviderConnections extends ListRecords
{
protected static string $resource = ProviderConnectionResource::class;
public function mount(): void
{
app(CanonicalAdminTenantFilterState::class)->sync(
$this->getTableFiltersSessionKey(),
request: request(),
tenantFilterName: 'tenant',
tenantAttribute: 'external_id',
);
parent::mount();
}
private function tableHasRecords(): bool
{
return $this->getTableRecords()->count() > 0;
}
protected function getHeaderActions(): array
{
/** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class);
return [
Actions\CreateAction::make()
->label('New connection')
->url(function (): string {
$tenantExternalId = $this->resolveTenantExternalIdForCreateAction();
if (! is_string($tenantExternalId) || $tenantExternalId === '') {
return ProviderConnectionResource::getUrl('create');
}
return ProviderConnectionResource::getUrl('create', [
'tenant_id' => $tenantExternalId,
]);
})
->visible(function () use ($resolver): bool {
if (! $this->tableHasRecords()) {
return false;
}
$tenant = $this->resolveTenantForCreateAction();
$user = auth()->user();
if (! $tenant instanceof Tenant) {
return true;
}
if (! $user instanceof User) {
return false;
}
return $resolver->isMember($user, $tenant);
})
->disabled(function () use ($resolver): bool {
$tenant = $this->resolveTenantForCreateAction();
$user = auth()->user();
if (! $tenant instanceof Tenant) {
return true;
}
if (! $user instanceof User) {
return true;
}
if (! $resolver->isMember($user, $tenant)) {
return true;
}
return ! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE);
})
->tooltip(function () use ($resolver): ?string {
$tenant = $this->resolveTenantForCreateAction();
if (! $tenant instanceof Tenant) {
return 'Select a tenant to create provider connections.';
}
$user = auth()->user();
if (! $user instanceof User) {
return null;
}
if (! $resolver->isMember($user, $tenant)) {
return null;
}
if (! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE)) {
return 'You do not have permission to create provider connections.';
}
return null;
})
->authorize(function () use ($resolver): bool {
$tenant = $this->resolveTenantForCreateAction();
$user = auth()->user();
return $tenant instanceof Tenant
&& $user instanceof User
&& $resolver->isMember($user, $tenant);
}),
];
}
private function makeEmptyStateCreateAction(): Actions\CreateAction
{
/** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class);
return Actions\CreateAction::make()
->label('New connection')
->url(function (): string {
$tenantExternalId = $this->resolveTenantExternalIdForCreateAction();
if (! is_string($tenantExternalId) || $tenantExternalId === '') {
return ProviderConnectionResource::getUrl('create');
}
return ProviderConnectionResource::getUrl('create', [
'tenant_id' => $tenantExternalId,
]);
})
->visible(function () use ($resolver): bool {
$tenant = $this->resolveTenantForCreateAction();
$user = auth()->user();
if (! $tenant instanceof Tenant) {
return true;
}
if (! $user instanceof User) {
return false;
}
return $resolver->isMember($user, $tenant);
})
->disabled(function () use ($resolver): bool {
$tenant = $this->resolveTenantForCreateAction();
$user = auth()->user();
if (! $tenant instanceof Tenant) {
return true;
}
if (! $user instanceof User) {
return true;
}
if (! $resolver->isMember($user, $tenant)) {
return true;
}
return ! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE);
})
->tooltip(function () use ($resolver): ?string {
$tenant = $this->resolveTenantForCreateAction();
if (! $tenant instanceof Tenant) {
return 'Select a tenant to create provider connections.';
}
$user = auth()->user();
if (! $user instanceof User) {
return null;
}
if (! $resolver->isMember($user, $tenant)) {
return null;
}
if (! $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE)) {
return 'You do not have permission to create provider connections.';
}
return null;
})
->authorize(function () use ($resolver): bool {
$tenant = $this->resolveTenantForCreateAction();
$user = auth()->user();
return $tenant instanceof Tenant
&& $user instanceof User
&& $resolver->isMember($user, $tenant);
});
}
private function resolveTenantExternalIdForCreateAction(): ?string
{
$filterValue = data_get($this->getTableFilterState('tenant'), 'value');
if (is_string($filterValue) && $filterValue !== '') {
return $filterValue;
}
$requested = ProviderConnectionResource::resolveRequestedTenantExternalId()
?? ProviderConnectionResource::resolveContextTenantExternalId();
if (is_string($requested) && $requested !== '') {
return $requested;
}
return ProviderConnectionResource::resolveContextTenantExternalId();
}
private function resolveTenantForCreateAction(): ?Tenant
{
$tenantExternalId = $this->resolveTenantExternalIdForCreateAction();
if (! is_string($tenantExternalId) || $tenantExternalId === '') {
return null;
}
return Tenant::query()
->where('external_id', $tenantExternalId)
->first();
}
public function getTableEmptyStateHeading(): ?string
{
return 'No provider connections found';
}
public function getTableEmptyStateDescription(): ?string
{
return 'Create a Microsoft provider connection or adjust the tenant filter to inspect another managed tenant.';
}
public function getTableEmptyStateActions(): array
{
return [$this->makeEmptyStateCreateAction()];
}
}