[Feature] Convert API page to Resource (#2214)

This commit is contained in:
Sven van Ginkel
2025-05-22 15:40:43 +02:00
committed by GitHub
parent 8ebbfab599
commit c7fc4067cd
4 changed files with 196 additions and 145 deletions
-134
View File
@@ -1,134 +0,0 @@
<?php
namespace App\Filament\Pages;
use Carbon\Carbon;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Concerns\InteractsWithInfolists;
use Filament\Infolists\Contracts\HasInfolists;
use Filament\Infolists\Infolist;
use Filament\Pages\Page;
use Filament\Support\Enums\FontFamily;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\Auth;
class ApiTokens extends Page implements HasForms, HasInfolists, HasTable
{
use InteractsWithForms, InteractsWithInfolists, InteractsWithTable;
protected static ?string $navigationIcon = 'tabler-api';
protected static string $view = 'filament.pages.api-tokens';
protected static ?string $title = 'API Tokens';
protected static ?string $navigationGroup = 'Settings';
public ?string $token = '';
public function tokenInfolist(Infolist $infolist): Infolist
{
return $infolist
->state([
'token' => $this->token,
])
->schema([
Section::make()
->columns(1)
->schema([
TextEntry::make('token')
->label('API Token')
->formatStateUsing(fn (string $state) => explode('|', $state)[1])
->helperText('Copy and save the token above, this token will not be shown again!')
->color('gray')
->copyable()
->copyableState(fn (string $state) => explode('|', $state)[1])
->fontFamily(FontFamily::Mono),
]),
]);
}
public function table(Table $table): Table
{
return $table
->relationship(fn (): MorphMany => Auth::user()->tokens())
->headerActions([
Action::make('createToken')
->form([
TextInput::make('token_name')
->label('Name')
->maxLength('100')
->required()
->autocomplete(false),
CheckboxList::make('abilities')
->options([
'results:read' => 'Read results',
'speedtests:run' => 'Run speedtest',
'ookla:list-servers' => 'List servers',
])
->descriptions([
'results:read' => 'Allow this token to read results.',
'speedtests:run' => 'Allow this token to run speedtests.',
'ookla:list-servers' => 'Allow this token to list server.',
])
->bulkToggleable(),
DateTimePicker::make('token_expires_at')
->label('Expires at')
->nullable()
->helperText('Leave empty for no expiration'),
])
->action(function (array $data) {
$token = Auth::user()->createToken(
name: $data['token_name'],
abilities: $data['abilities'],
expiresAt: $data['token_expires_at'] ? Carbon::parse($data['token_expires_at']) : null,
);
$this->token = $token->plainTextToken;
})
->label('Create API Token')
->modal('createToken')
->modalWidth(MaxWidth::ExtraLarge),
])
->columns([
TextColumn::make('name')
->searchable(),
TextColumn::make('abilities')
->badge(),
TextColumn::make('created_at')
->alignEnd()
->dateTime()
->sortable(),
TextColumn::make('last_used_at')
->alignEnd()
->dateTime()
->sortable(),
TextColumn::make('expires_at')
->alignEnd()
->dateTime()
->sortable(),
])
->actions([
ActionGroup::make([
DeleteAction::make(),
]),
])
->bulkActions([
// ...
]);
}
}
+158
View File
@@ -0,0 +1,158 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\ApiTokenResource\Pages;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Laravel\Sanctum\PersonalAccessToken;
class ApiTokenResource extends Resource
{
protected static ?string $model = PersonalAccessToken::class;
protected static ?string $navigationIcon = 'tabler-api';
protected static ?string $navigationGroup = 'Settings';
protected static ?string $label = 'API Token';
protected static ?string $pluralLabel = 'API Tokens';
public static function getTokenFormSchema(): array
{
return [
Grid::make()
->schema([
TextInput::make('name')
->label('Name')
->unique(ignoreRecord: true)
->maxLength(100)
->required(),
CheckboxList::make('abilities')
->label('Abilities')
->options([
'results:read' => 'Read results',
'speedtests:run' => 'Run speedtest',
'ookla:list-servers' => 'List servers',
])
->required()
->bulkToggleable()
->descriptions([
'results:read' => 'Allow this token to read results.',
'speedtests:run' => 'Allow this token to run speedtests.',
'ookla:list-servers' => 'Allow this token to list servers.',
]),
DateTimePicker::make('expires_at')
->label('Expires at')
->nullable()
->native(false)
->helperText('Leave empty for no expiration'),
])
->columns([
'lg' => 1,
]),
];
}
public static function form(Form $form): Form
{
return $form->schema(static::getTokenFormSchema());
}
public static function table(Table $table): Table
{
return $table
->query(PersonalAccessToken::query()->where('tokenable_id', Auth::id()))
->columns([
TextColumn::make('name')->searchable(),
TextColumn::make('abilities')->badge(),
TextColumn::make('created_at')
->dateTime(config('app.datetime_format'))
->timezone(config('app.display_timezone'))
->toggleable()
->sortable()
->alignEnd(),
TextColumn::make('last_used_at')
->dateTime(config('app.datetime_format'))
->timezone(config('app.display_timezone'))
->toggleable()
->toggledHiddenByDefault()
->sortable()
->alignEnd(),
TextColumn::make('expires_at')
->dateTime(config('app.datetime_format'))
->timezone(config('app.display_timezone'))
->toggleable()
->sortable()
->alignEnd(),
])
->filters([
TernaryFilter::make('expired')
->label('Token Status')
->placeholder('All tokens')
->falseLabel('Active tokens')
->trueLabel('Expired tokens')
->native(false)
->queries(
true: fn (Builder $query) => $query
->where('expires_at', '<=', now()),
false: fn (Builder $query) => $query
->where(function (Builder $q) {
$q->whereNull('expires_at')
->orWhere('expires_at', '>', now());
}),
blank: fn (Builder $query) => $query,
),
SelectFilter::make('abilities')
->label('Abilities')
->multiple()
->options([
'results:read' => 'Read results',
'speedtests:run' => 'Run speedtest',
'ookla:list-servers' => 'List servers',
])
->query(function (Builder $query, array $data): Builder {
foreach ($data['values'] ?? [] as $value) {
$query->whereJsonContains('abilities', $value);
}
return $query;
}),
])
->actions([
ActionGroup::make([
EditAction::make()
->disabled(fn ($record) => $record->expires_at !== null && $record->expires_at->isPast())
->modalWidth('xl'),
DeleteAction::make(),
]),
])
->bulkActions([
DeleteBulkAction::make(),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListApiTokens::route('/'),
];
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Filament\Resources\ApiTokenResource\Pages;
use App\Filament\Resources\ApiTokenResource;
use Carbon\Carbon;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\ListRecords;
class ListApiTokens extends ListRecords
{
protected static string $resource = ApiTokenResource::class;
protected function getHeaderActions(): array
{
return [
Action::make('createToken')
->label('Create API Token')
->form(ApiTokenResource::getTokenFormSchema())
->action(function (array $data): void {
$token = auth()->user()->createToken(
$data['name'],
$data['abilities'],
$data['expires_at'] ? Carbon::parse($data['expires_at']) : null
);
Notification::make()
->title('Token Created')
->body('Your token: `'.explode('|', $token->plainTextToken)[1].'`')
->success()
->persistent()
->send();
})
->modalWidth('xl'),
];
}
}
@@ -1,11 +0,0 @@
<x-filament-panels::page>
@filled($token)
<div>
{{ $this->tokenInfolist }}
</div>
@endfilled
<div>
{{ $this->table }}
</div>
</x-filament-panels::page>