204 lines
7.6 KiB
PHP
204 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use App\Filament\Resources\EntraGroupResource;
|
|
use App\Filament\Resources\EntraGroupSyncRunResource;
|
|
use App\Models\EntraGroup;
|
|
use App\Models\Tenant;
|
|
use Filament\Actions\Action;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Filters\SelectFilter;
|
|
use Filament\Tables\Table;
|
|
use Filament\Tables\TableComponent;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
class EntraGroupCachePickerTable extends TableComponent
|
|
{
|
|
public string $sourceGroupId;
|
|
|
|
public function mount(string $sourceGroupId): void
|
|
{
|
|
$this->sourceGroupId = $sourceGroupId;
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
$tenantId = Tenant::current()?->getKey();
|
|
|
|
$query = EntraGroup::query();
|
|
|
|
if ($tenantId) {
|
|
$query->where('tenant_id', $tenantId);
|
|
} else {
|
|
$query->whereRaw('1 = 0');
|
|
}
|
|
|
|
$stalenessDays = (int) config('directory_groups.staleness_days', 30);
|
|
$cutoff = now('UTC')->subDays(max(1, $stalenessDays));
|
|
|
|
return $table
|
|
->queryStringIdentifier('entraGroupCachePicker')
|
|
->query($query)
|
|
->defaultSort('display_name')
|
|
->paginated([10, 25, 50])
|
|
->defaultPaginationPageOption(10)
|
|
->searchable()
|
|
->searchPlaceholder('Search groups…')
|
|
->deferLoading(! app()->runningUnitTests())
|
|
->columns([
|
|
TextColumn::make('display_name')
|
|
->label('Name')
|
|
->searchable()
|
|
->sortable()
|
|
->wrap()
|
|
->limit(60),
|
|
TextColumn::make('type')
|
|
->label('Type')
|
|
->badge()
|
|
->state(fn (EntraGroup $record): string => $this->groupTypeLabel($this->groupType($record)))
|
|
->color(fn (EntraGroup $record): string => $this->groupTypeColor($this->groupType($record)))
|
|
->toggleable(),
|
|
TextColumn::make('entra_id')
|
|
->label('ID')
|
|
->formatStateUsing(fn (?string $state): string => filled($state) ? ('…'.substr($state, -8)) : '—')
|
|
->extraAttributes(['class' => 'font-mono'])
|
|
->toggleable(),
|
|
TextColumn::make('last_seen_at')
|
|
->label('Last seen')
|
|
->since()
|
|
->sortable(),
|
|
])
|
|
->filters([
|
|
SelectFilter::make('stale')
|
|
->label('Stale')
|
|
->options([
|
|
'1' => 'Stale',
|
|
'0' => 'Fresh',
|
|
])
|
|
->query(function (Builder $query, array $data) use ($cutoff): Builder {
|
|
$value = $data['value'] ?? null;
|
|
|
|
if ($value === null || $value === '') {
|
|
return $query;
|
|
}
|
|
|
|
if ((string) $value === '1') {
|
|
return $query->where(function (Builder $q) use ($cutoff): void {
|
|
$q->whereNull('last_seen_at')
|
|
->orWhere('last_seen_at', '<', $cutoff);
|
|
});
|
|
}
|
|
|
|
return $query->where('last_seen_at', '>=', $cutoff);
|
|
}),
|
|
|
|
SelectFilter::make('group_type')
|
|
->label('Type')
|
|
->options([
|
|
'security' => 'Security',
|
|
'microsoft365' => 'Microsoft 365',
|
|
'mail' => 'Mail-enabled',
|
|
'unknown' => 'Unknown',
|
|
])
|
|
->query(function (Builder $query, array $data): Builder {
|
|
$value = (string) ($data['value'] ?? '');
|
|
|
|
if ($value === '') {
|
|
return $query;
|
|
}
|
|
|
|
return match ($value) {
|
|
'microsoft365' => $query->whereJsonContains('group_types', 'Unified'),
|
|
'security' => $query
|
|
->where('security_enabled', true)
|
|
->where(function (Builder $q): void {
|
|
$q->whereNull('group_types')
|
|
->orWhereJsonDoesntContain('group_types', 'Unified');
|
|
}),
|
|
'mail' => $query
|
|
->where('mail_enabled', true)
|
|
->where(function (Builder $q): void {
|
|
$q->whereNull('group_types')
|
|
->orWhereJsonDoesntContain('group_types', 'Unified');
|
|
}),
|
|
'unknown' => $query
|
|
->where(function (Builder $q): void {
|
|
$q->whereNull('group_types')
|
|
->orWhereJsonDoesntContain('group_types', 'Unified');
|
|
})
|
|
->where('security_enabled', false)
|
|
->where('mail_enabled', false),
|
|
default => $query,
|
|
};
|
|
}),
|
|
])
|
|
->actions([
|
|
Action::make('select')
|
|
->label('Select')
|
|
->icon('heroicon-o-check')
|
|
->color('primary')
|
|
->action(function (EntraGroup $record): void {
|
|
$this->dispatch('entra-group-cache-picked', sourceGroupId: $this->sourceGroupId, entraId: (string) $record->entra_id);
|
|
}),
|
|
])
|
|
->emptyStateHeading('No cached groups found')
|
|
->emptyStateDescription('Run “Sync Groups” first, then come back here.')
|
|
->emptyStateActions([
|
|
Action::make('open_groups')
|
|
->label('Directory Groups')
|
|
->icon('heroicon-o-user-group')
|
|
->url(fn (): string => EntraGroupResource::getUrl('index', tenant: Tenant::current())),
|
|
Action::make('open_sync_runs')
|
|
->label('Group Sync Runs')
|
|
->icon('heroicon-o-clock')
|
|
->url(fn (): string => EntraGroupSyncRunResource::getUrl('index', tenant: Tenant::current())),
|
|
]);
|
|
}
|
|
|
|
public function render(): View
|
|
{
|
|
return view('livewire.entra-group-cache-picker-table');
|
|
}
|
|
|
|
private function groupType(EntraGroup $record): string
|
|
{
|
|
$groupTypes = $record->group_types;
|
|
|
|
if (is_array($groupTypes) && in_array('Unified', $groupTypes, true)) {
|
|
return 'microsoft365';
|
|
}
|
|
|
|
if ($record->security_enabled) {
|
|
return 'security';
|
|
}
|
|
|
|
if ($record->mail_enabled) {
|
|
return 'mail';
|
|
}
|
|
|
|
return 'unknown';
|
|
}
|
|
|
|
private function groupTypeLabel(string $type): string
|
|
{
|
|
return match ($type) {
|
|
'microsoft365' => 'Microsoft 365',
|
|
'security' => 'Security',
|
|
'mail' => 'Mail-enabled',
|
|
default => 'Unknown',
|
|
};
|
|
}
|
|
|
|
private function groupTypeColor(string $type): string
|
|
{
|
|
return match ($type) {
|
|
'microsoft365' => 'info',
|
|
'security' => 'success',
|
|
'mail' => 'warning',
|
|
default => 'gray',
|
|
};
|
|
}
|
|
}
|