feat: action logs (#702)

This commit is contained in:
Kyrch
2024-06-27 06:22:16 -03:00
committed by GitHub
parent 847189aad4
commit ea5683d080
92 changed files with 2373 additions and 328 deletions
+26 -19
View File
@@ -6,6 +6,7 @@ namespace App\Actions\Discord;
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Anime;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
@@ -21,32 +22,38 @@ class DiscordThreadAction
*
* @param Anime $anime
* @param array $fields
*
* @return void
* @return Exception|null
*/
public function handle(Anime $anime, array $fields): void
public function handle(Anime $anime, array $fields): ?Exception
{
$anime->load(Anime::RELATION_IMAGES);
try {
$anime->load(Anime::RELATION_IMAGES);
$anime->name = Arr::get($fields, 'name');
$anime->name = Arr::get($fields, 'name');
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$fs = Storage::disk(Config::get('image.disk'));
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$fs = Storage::disk(Config::get('image.disk'));
$anime->images->each(fn ($image) => Arr::set($image, 'link', $fs->url($image->path)));
$anime->images->each(fn ($image) => Arr::set($image, 'link', $fs->url($image->path)));
$response = Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')])
->acceptJson()
->post(Config::get('services.discord.api_url') . '/thread', $anime->toArray())
->throw()
->json();
$response = Http::withHeaders(['x-api-key' => Config::get('services.discord.api_key')])
->acceptJson()
->post(Config::get('services.discord.api_url') . '/thread', $anime->toArray())
->throw()
->json();
if (Arr::has($response, 'id')) {
DiscordThread::query()->create([
DiscordThread::ATTRIBUTE_NAME => Arr::get($response, 'name'),
DiscordThread::ATTRIBUTE_ID => intval(Arr::get($response, 'id')),
DiscordThread::ATTRIBUTE_ANIME => $anime->getKey(),
]);
if (Arr::has($response, 'id')) {
DiscordThread::query()->create([
DiscordThread::ATTRIBUTE_NAME => Arr::get($response, 'name'),
DiscordThread::ATTRIBUTE_ID => intval(Arr::get($response, 'id')),
DiscordThread::ATTRIBUTE_ANIME => $anime->getKey(),
]);
}
return null;
} catch (Exception $e) {
return $e;
}
}
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace App\Concerns\Filament\Actions;
use App\Models\Admin\ActionLog;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Throwable;
/**
* Trait HasActionLogs.
*/
trait HasActionLogs
{
protected string $batchId = '';
protected ?ActionLog $actionLog = null;
protected ?Model $recordLog = null;
protected ?Model $parentRecordLog = null;
protected ?Model $pivot = null;
/**
* Create a batch id for the action.
*
* @return string
*/
public function createBatchId(): string
{
$this->batchId = Str::orderedUuid()->__toString();
return $this->batchId;
}
/**
* Create an action log.
*
* @param mixed $action
* @param Model|null $record
* @param bool $shouldCreateNewBatchId
* @return void
*/
public function createActionLog(mixed $action, ?Model $record = null, ?bool $shouldCreateNewBatchId = true): void
{
if ($shouldCreateNewBatchId) {
$this->createBatchId();
}
// $record must be specified if in context of bulk action
$this->recordLog = $record ?? $this->getRecord();
$actionLog = ActionLog::modelActioned(
$this->batchId,
$action,
$this->recordLog,
);
$this->actionLog = $actionLog;
}
/**
* Update the log for pivot actions.
*
* @param Model $relatedModel
* @param Model $pivot
* @return void
*/
public function updateLog(Model $relatedModel, Model $pivot): void
{
$this->actionLog->update([
ActionLog::ATTRIBUTE_TARGET_TYPE => $relatedModel->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $relatedModel->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => $pivot->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $pivot->getKey(),
]);
}
/**
* Mark the action as failed.
*
* @param Throwable|string|null $exception
* @return void
*/
public function failedLog(Throwable|string|null $exception): void
{
$this->actionLog->failed($exception);
}
/**
* Mark the action as finished if not failed.
*
* @return void
*/
public function finishedLog(): void
{
if (!$this->isFailedLog()) {
$this->actionLog->finished();
}
}
/**
* Mark batch action as finished where not failed.
*
* @return void
*/
public function batchFinishedLog(): void
{
$this->actionLog->batchFinished();
}
/**
* Check if the action is failed.
*
* @return bool
*/
public function isFailedLog(): bool
{
return $this->actionLog->isFailed();
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Concerns\Filament\Actions;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Models\Admin\ActionLog;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* Trait HasPivotActionLogs.
*/
trait HasPivotActionLogs
{
/**
* Create the pivot action log.
*
* @param string $actionName
* @param BaseRelationManager $livewire
* @param Model $record
* @return void
*/
public function pivotActionLog(string $actionName, BaseRelationManager $livewire, Model $record): void
{
$ownerRecord = $livewire->getOwnerRecord();
/** @var BelongsToMany */
$relation = $livewire->getRelationship();
$pivotClass = $relation->getPivotClass();
$pivot = $pivotClass::query()
->where($ownerRecord->getKeyName(), $ownerRecord->getKey())
->where($record->getKeyName(), $record->getKey())
->first();
ActionLog::modelPivot(
$actionName,
$livewire->getOwnerRecord(),
$record,
$pivot,
);
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Enums\Actions;
use App\Concerns\Enums\LocalizesName;
/**
* Enum ActionLogStatus.
*/
enum ActionLogStatus: int
{
use LocalizesName;
case RUNNING = 0;
case FAILED = 1;
case FINISHED = 2;
/**
* Get the filament color for the enum.
*
* @return string
*/
public function color(): string
{
return match ($this) {
ActionLogStatus::RUNNING => 'primary',
ActionLogStatus::FAILED => 'danger',
ActionLogStatus::FINISHED => 'success',
};
}
}
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use App\Concerns\Filament\Actions\HasPivotActionLogs;
use App\Filament\Components\Fields\Select;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\RelationManagers\Wiki\ResourceRelationManager;
use App\Filament\Resources\Wiki\Artist\RelationManagers\SongArtistRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\AnimeResourceRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\ArtistResourceRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\SongResourceRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\StudioResourceRelationManager;
use App\Filament\Resources\Wiki\Song\RelationManagers\ArtistSongRelationManager;
use App\Pivots\Wiki\AnimeResource;
use App\Pivots\Wiki\ArtistSong;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Tables\Actions\AttachAction as DefaultAttachAction;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* Class AttachAction.
*/
class AttachAction extends DefaultAttachAction
{
use HasPivotActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->authorize('create');
$this->hidden(fn (BaseRelationManager $livewire) => !($livewire->getRelationship() instanceof BelongsToMany));
$this->recordSelect(function (BaseRelationManager $livewire) {
/** @var string */
$model = $livewire->getTable()->getModel();
$title = $livewire->getTable()->getRecordTitle(new $model);
return Select::make('recordId')
->label($title)
->useScout($model);
});
$this->form(function (Form $form, AttachAction $action) {
return $form
->schema([
$action->getRecordSelect(),
TextInput::make(AnimeResource::ATTRIBUTE_AS)
->label(__('filament.fields.anime.resources.as.name'))
->helperText(__('filament.fields.anime.resources.as.help'))
->visibleOn([
AnimeResourceRelationManager::class,
ArtistResourceRelationManager::class,
SongResourceRelationManager::class,
StudioResourceRelationManager::class,
ResourceRelationManager::class,
]),
TextInput::make(ArtistSong::ATTRIBUTE_AS)
->label(__('filament.fields.artist.songs.as.name'))
->helperText(__('filament.fields.artist.songs.as.help'))
->visibleOn([
ArtistSongRelationManager::class,
SongArtistRelationManager::class,
]),
]);
});
$this->after(fn ($livewire, $record) => $this->pivotActionLog('Attach', $livewire, $record));
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use App\Concerns\Filament\Actions\HasPivotActionLogs;
use App\Filament\RelationManagers\BaseRelationManager;
use Filament\Tables\Actions\CreateAction as DefaultCreateAction;
/**
* Class CreateAction.
*/
class CreateAction extends DefaultCreateAction
{
use HasPivotActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->after(function ($livewire, $record) {
if ($livewire instanceof BaseRelationManager) {
$this->pivotActionLog('Create and Attach', $livewire, $record);
}
});
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use App\Models\Admin\ActionLog;
use Filament\Tables\Actions\DeleteAction as DefaultDeleteAction;
/**
* Class DeleteAction.
*/
class DeleteAction extends DefaultDeleteAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.delete'));
$this->after(function ($record) {
ActionLog::modelDeleted($record);
});
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use App\Concerns\Filament\Actions\HasPivotActionLogs;
use App\Filament\RelationManagers\BaseRelationManager;
use Filament\Tables\Actions\DetachAction as DefaultDetachAction;
/**
* Class DetachAction.
*/
class DetachAction extends DefaultDetachAction
{
use HasPivotActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.detach'));
$this->hidden(fn ($livewire) => !($livewire instanceof BaseRelationManager));
$this->authorize('delete');
$this->after(fn ($livewire, $record) => $this->pivotActionLog('Detach', $livewire, $record));
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use App\Concerns\Filament\Actions\HasPivotActionLogs;
use App\Filament\RelationManagers\BaseRelationManager;
use Filament\Tables\Actions\EditAction as DefaultEditAction;
/**
* Class EditAction.
*/
class EditAction extends DefaultEditAction
{
use HasPivotActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.edit'));
$this->after(function ($livewire, $record) {
if ($livewire instanceof BaseRelationManager) {
$this->pivotActionLog('Update Attached', $livewire, $record);
}
});
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use Filament\Tables\Actions\ForceDeleteAction as DefaultForceDeleteAction;
/**
* Class ForceDeleteAction.
*/
class ForceDeleteAction extends DefaultForceDeleteAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.forcedelete'));
$this->visible(true);
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use App\Models\Admin\ActionLog;
use Filament\Tables\Actions\RestoreAction as DefaultRestoreAction;
/**
* Class RestoreAction.
*/
class RestoreAction extends DefaultRestoreAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.restore'));
$this->after(function ($record) {
ActionLog::modelRestored($record);
});
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Base;
use Filament\Tables\Actions\ViewAction as DefaultViewAction;
/**
* Class ViewAction.
*/
class ViewAction extends DefaultViewAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.view'));
}
}
+7
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Filament\Actions;
use App\Concerns\Filament\Actions\HasActionLogs;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\Action;
@@ -15,6 +16,8 @@ use Filament\Tables\Actions\Action;
*/
abstract class BaseAction extends Action
{
use HasActionLogs;
/**
* Initial setup for the action.
*
@@ -26,6 +29,10 @@ abstract class BaseAction extends Action
$this->requiresConfirmation();
$this->afterFormValidated(fn (BaseAction $action) => $this->createActionLog($action));
$this->after(fn () => $this->finishedLog());
$this->modalWidth(MaxWidth::FourExtraLarge);
}
}
@@ -8,6 +8,7 @@ use App\Actions\Discord\DiscordThreadAction as DiscordThreadActionAction;
use App\Filament\Actions\BaseAction;
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Anime;
use Exception;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@@ -32,9 +33,15 @@ class DiscordThreadAction extends BaseAction
$this->fillForm(fn (Anime $record): array => ['name' => $record->getName()]);
$this->action(fn (Anime $record, array $data) => (new DiscordThreadActionAction())->handle($record, $data));
$this->action(function (Anime $record, array $data) {
$action = (new DiscordThreadActionAction())->handle($record, $data);
if ($action instanceof Exception) {
$this->failedLog($action);
}
});
}
/**
* Get the fields available on the action.
*
@@ -50,7 +50,7 @@ class AssignHashidsAction extends BaseAction
try {
$action->assign($model, $this->connection);
} catch (Exception $e) {
//$this->markAsFailed($model, $e);
$this->failedLog($e);
}
}
}
@@ -83,7 +83,7 @@ class BackfillAnimeAction extends BaseAction implements ShouldQueue
public function handle(Anime $anime, array $fields): void
{
if ($anime->resources()->doesntExist()) {
$this->fail(__('filament.actions.anime.backfill.message.resource_required_failure'));
$this->failedLog(__('filament.actions.anime.backfill.message.resource_required_failure'));
return;
}
@@ -105,7 +105,7 @@ class BackfillAnimeAction extends BaseAction implements ShouldQueue
}
}
} catch (Exception $e) {
$this->fail($e);
$this->failedLog($e);
} finally {
// Try not to upset third-party APIs
Sleep::for(rand(3, 5))->second();
@@ -16,7 +16,6 @@ use Filament\Forms\Components\Section;
use Filament\Forms\Form;
use Filament\Notifications\Actions\Action as NotificationAction;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
@@ -45,8 +44,6 @@ class BackfillStudioAction extends BaseAction implements ShouldQueue
$this->label(__('filament.actions.studio.backfill.name'));
$this->modalWidth(MaxWidth::FourExtraLarge);
$this->authorize('update', Studio::class);
$this->action(fn (Studio $record, array $data) => $this->handle($record, $data));
@@ -62,7 +59,7 @@ class BackfillStudioAction extends BaseAction implements ShouldQueue
public function handle(Studio $studio, array $fields): void
{
if ($studio->resources()->doesntExist()) {
//$this->markAsFailed($studio, __('filament.actions.studio.backfill.message.resource_required_failure'));
$this->failedLog(__('filament.actions.studio.backfill.message.resource_required_failure'));
return;
}
@@ -84,7 +81,7 @@ class BackfillStudioAction extends BaseAction implements ShouldQueue
}
}
} catch (Exception $e) {
//$this->markAsFailed($studio, $e);
$this->failedLog($e);
} finally {
// Try not to upset third-party APIs
Sleep::for(rand(3, 5))->second();
@@ -7,6 +7,7 @@ namespace App\Filament\Actions\Models\Wiki\Video;
use App\Actions\Models\Wiki\Video\Audio\BackfillAudioAction as BackfillAudio;
use App\Enums\Actions\Models\Wiki\Video\DeriveSourceVideo;
use App\Enums\Actions\Models\Wiki\Video\OverwriteAudio;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Models\Wiki\Audio;
use App\Models\Wiki\Video;
@@ -14,8 +15,6 @@ use Exception;
use Filament\Forms\Form;
use Filament\Notifications\Actions\Action as NotificationAction;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\Action;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
@@ -26,7 +25,7 @@ use Illuminate\Validation\Rules\Enum;
/**
* Class BackfillAudioAction.
*/
class BackfillAudioAction extends Action implements ShouldQueue
class BackfillAudioAction extends BaseAction implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
@@ -45,10 +44,6 @@ class BackfillAudioAction extends Action implements ShouldQueue
$this->label(__('filament.actions.video.backfill.name'));
$this->requiresConfirmation();
$this->modalWidth(MaxWidth::TwoExtraLarge);
$this->authorize('create', Audio::class);
$this->action(fn (Video $record, array $data) => $this->handle($record, $data));
@@ -82,7 +77,7 @@ class BackfillAudioAction extends Action implements ShouldQueue
->sendToDatabase(Auth::user());
}
} catch (Exception $e) {
//$this->markAsFailed($video, $e);
$this->failedLog($e);
}
}
@@ -52,5 +52,9 @@ abstract class StorageAction extends BaseAction
$action->then($storageResults);
$actionResult = $storageResults->toActionResult();
if ($actionResult->hasFailed()) {
$this->failedLog($actionResult->getMessage());
}
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Filament\BulkActions\Base;
use App\Concerns\Filament\Actions\HasActionLogs;
use Filament\Tables\Actions\DeleteBulkAction as DefaultDeleteBulkAction;
/**
* Class DeleteBulkAction.
*/
class DeleteBulkAction extends DefaultDeleteBulkAction
{
use HasActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.bulk_actions.base.delete'));
$this->authorize('forcedeleteany');
$this->after(function (DeleteBulkAction $action) {
foreach ($this->getRecords() as $record) {
$this->createActionLog($action, $record);
$this->finishedLog();
}
});
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Filament\BulkActions\Base;
use App\Concerns\Filament\Actions\HasActionLogs;
use Filament\Tables\Actions\DetachBulkAction as DefaultDetachBulkAction;
/**
* Class DetachBulkAction.
*/
class DetachBulkAction extends DefaultDetachBulkAction
{
use HasActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.bulk_actions.base.detach'));
$this->authorize('forcedeleteany');
$this->after(function (DetachBulkAction $action) {
foreach ($this->getRecords() as $record) {
$this->createActionLog($action, $record);
$this->finishedLog();
}
});
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Filament\BulkActions\Base;
use App\Concerns\Filament\Actions\HasActionLogs;
use Filament\Tables\Actions\ForceDeleteBulkAction as DefaultForceDeleteBulkAction;
/**
* Class ForceDeleteBulkAction.
*/
class ForceDeleteBulkAction extends DefaultForceDeleteBulkAction
{
use HasActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.bulk_actions.base.forcedelete'));
$this->hidden(false);
$this->authorize('forcedeleteany');
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Filament\BulkActions\Base;
use App\Concerns\Filament\Actions\HasActionLogs;
use Filament\Tables\Actions\RestoreBulkAction as DefaultRestoreBulkAction;
/**
* Class RestoreBulkAction.
*/
class RestoreBulkAction extends DefaultRestoreBulkAction
{
use HasActionLogs;
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.bulk_actions.base.restore'));
$this->authorize('forcedeleteany');
$this->after(function (RestoreBulkAction $action) {
foreach ($this->getRecords() as $record) {
$this->createActionLog($action, $record);
$this->finishedLog();
}
});
}
}
@@ -4,7 +4,9 @@ declare(strict_types=1);
namespace App\Filament\BulkActions;
use App\Concerns\Filament\Actions\HasActionLogs;
use App\Models\BaseModel;
use Filament\Forms\Form;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\BulkAction;
use Illuminate\Database\Eloquent\Collection;
@@ -16,6 +18,8 @@ use Illuminate\Database\Eloquent\Collection;
*/
abstract class BaseBulkAction extends BulkAction
{
use HasActionLogs;
/**
* Initial setup for the action.
*
@@ -27,6 +31,14 @@ abstract class BaseBulkAction extends BulkAction
$this->requiresConfirmation();
$this->before(function ($action) {
foreach ($this->getRecords() as $record) {
$this->createActionLog($action, $record, false);
}
});
$this->after(fn () => $this->batchFinishedLog());
$this->modalWidth(MaxWidth::FourExtraLarge);
$this->action(fn (Collection $records, array $data) => $this->handle($records, $data));
@@ -8,6 +8,7 @@ use App\Filament\Resources\BaseResource;
use App\Models\BaseModel;
use Filament\Support\Enums\FontWeight;
use Filament\Tables\Columns\TextColumn as ColumnsTextColumn;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
@@ -30,7 +31,7 @@ class TextColumn extends ColumnsTextColumn
return $this
->weight(FontWeight::SemiBold)
->html()
->url(function (BaseModel $record) use ($resourceRelated, $relation, $shouldUseName, $limit) {
->url(function (BaseModel|Model $record) use ($resourceRelated, $relation, $shouldUseName, $limit) {
foreach (explode('.', $relation) as $element) {
$record = Arr::get($record, $element);
if ($record === null) return null;
@@ -8,6 +8,7 @@ use App\Filament\Resources\BaseResource;
use App\Models\BaseModel;
use Filament\Infolists\Components\TextEntry as ComponentsTextEntry;
use Filament\Support\Enums\FontWeight;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
/**
@@ -28,7 +29,7 @@ class TextEntry extends ComponentsTextEntry
return $this
->weight(FontWeight::SemiBold)
->html()
->url(function (BaseModel $record) use ($resourceRelated, $relation, $shouldUseName) {
->url(function (BaseModel|Model $record) use ($resourceRelated, $relation, $shouldUseName) {
if (!empty($relation)) {
foreach (explode('.', $relation) as $element) {
$record = Arr::get($record, $element);
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Filament\HeaderActions\Base;
use Filament\Actions\CreateAction as DefaultCreateAction;
/**
* Class CreateHeaderAction.
*/
class CreateHeaderAction extends DefaultCreateAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Filament\HeaderActions\Base;
use App\Models\Admin\ActionLog;
use Filament\Actions\DeleteAction as DefaultDeleteAction;
/**
* Class DeleteHeaderAction.
*/
class DeleteHeaderAction extends DefaultDeleteAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.delete'));
$this->after(function ($record) {
ActionLog::modelDeleted($record);
});
}
}
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace App\Filament\HeaderActions\Base;
use Filament\Actions\EditAction as DefaultEditAction;
/**
* Class EditHeaderAction.
*/
class EditHeaderAction extends DefaultEditAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.edit'));
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Filament\HeaderActions\Base;
use Filament\Actions\ForceDeleteAction as DefaultForceDeleteAction;
/**
* Class ForceDeleteHeaderAction.
*/
class ForceDeleteHeaderAction extends DefaultForceDeleteAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.forcedelete'));
$this->visible(true);
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Filament\HeaderActions\Base;
use App\Models\Admin\ActionLog;
use Filament\Actions\RestoreAction as DefaultRestoreAction;
/**
* Class RestoreHeaderAction.
*/
class RestoreHeaderAction extends DefaultRestoreAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.restore'));
$this->after(function ($record) {
ActionLog::modelRestored($record);
});
}
}
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Filament\HeaderActions\Base;
use App\Filament\Resources\Base\BaseViewResource;
use Filament\Actions\ViewAction as DefaultViewAction;
/**
* Class ViewHeaderAction.
*/
class ViewHeaderAction extends DefaultViewAction
{
/**
* Initial setup for the action.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.base.view'));
$this->hidden(fn ($livewire) => $livewire instanceof BaseViewResource);
}
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Filament\HeaderActions;
use App\Concerns\Filament\Actions\HasActionLogs;
use Filament\Actions\Action;
use Filament\Support\Enums\MaxWidth;
@@ -14,6 +15,8 @@ use Filament\Support\Enums\MaxWidth;
*/
abstract class BaseHeaderAction extends Action
{
use HasActionLogs;
/**
* Initial setup for the action.
*
@@ -25,6 +28,10 @@ abstract class BaseHeaderAction extends Action
$this->requiresConfirmation();
$this->afterFormValidated(fn (BaseHeaderAction $action) => $this->createActionLog($action));
$this->after(fn () => $this->finishedLog());
$this->modalWidth(MaxWidth::FourExtraLarge);
}
}
@@ -8,6 +8,7 @@ use App\Actions\Discord\DiscordThreadAction as DiscordThreadActionAction;
use App\Filament\HeaderActions\BaseHeaderAction;
use App\Models\Discord\DiscordThread;
use App\Models\Wiki\Anime;
use Exception;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@@ -34,9 +35,15 @@ class DiscordThreadHeaderAction extends BaseHeaderAction
$this->fillForm(fn (Anime $record): array => ['name' => $record->getName()]);
$this->action(fn (Anime $record, array $data) => (new DiscordThreadActionAction())->handle($record, $data));
$this->action(function (Anime $record, array $data) {
$action = (new DiscordThreadActionAction())->handle($record, $data);
if ($action instanceof Exception) {
$this->failedLog($action);
}
});
}
/**
* Get the fields available on the action.
*
@@ -55,4 +62,4 @@ class DiscordThreadHeaderAction extends BaseHeaderAction
->rules(['required', 'max:100']),
]);
}
}
}
@@ -50,7 +50,7 @@ class AssignHashidsHeaderAction extends BaseHeaderAction
try {
$action->assign($model, $this->connection);
} catch (Exception $e) {
//$this->markAsFailed($model, $e);
$this->failedLog($e);
}
}
}
@@ -80,10 +80,10 @@ class BackfillAnimeHeaderAction extends BaseHeaderAction implements ShouldQueue
public function handle(Anime $anime, array $fields): void
{
if ($anime->resources()->doesntExist()) {
$this->fail(__('filament.actions.anime.backfill.message.resource_required_failure'));
$this->failedLog(__('filament.actions.anime.backfill.message.resource_required_failure'));
return;
}
$actions = $this->getActions($fields, $anime);
try {
@@ -102,7 +102,7 @@ class BackfillAnimeHeaderAction extends BaseHeaderAction implements ShouldQueue
}
}
} catch (Exception $e) {
$this->fail($e);
$this->failedLog($e);
} finally {
// Try not to upset third-party APIs
Sleep::for(rand(3, 5))->second();
@@ -16,7 +16,6 @@ use Filament\Forms\Components\Section;
use Filament\Forms\Form;
use Filament\Notifications\Actions\Action as NotificationAction;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
@@ -45,8 +44,6 @@ class BackfillStudioHeaderAction extends BaseHeaderAction implements ShouldQueue
$this->label(__('filament.actions.studio.backfill.name'));
$this->modalWidth(MaxWidth::FourExtraLarge);
$this->authorize('update', Studio::class);
$this->action(fn (Studio $record, array $data) => $this->handle($record, $data));
@@ -62,7 +59,7 @@ class BackfillStudioHeaderAction extends BaseHeaderAction implements ShouldQueue
public function handle(Studio $studio, array $fields): void
{
if ($studio->resources()->doesntExist()) {
//$this->markAsFailed($studio, __('filament.actions.studio.backfill.message.resource_required_failure'));
$this->failedLog(__('filament.actions.studio.backfill.message.resource_required_failure'));
return;
}
@@ -84,7 +81,7 @@ class BackfillStudioHeaderAction extends BaseHeaderAction implements ShouldQueue
}
}
} catch (Exception $e) {
//$this->markAsFailed($studio, $e);
$this->failedLog($e);
} finally {
// Try not to upset third-party APIs
Sleep::for(rand(3, 5))->second();
@@ -80,7 +80,7 @@ class BackfillAudioHeaderAction extends BaseHeaderAction implements ShouldQueue
->sendToDatabase(Auth::user());
}
} catch (Exception $e) {
//$this->markAsFailed($video, $e);
$this->failedLog($e);
}
}
@@ -52,5 +52,9 @@ abstract class StorageHeaderAction extends BaseHeaderAction
$action->then($storageResults);
$actionResult = $storageResults->toActionResult();
if ($actionResult->hasFailed()) {
$this->failedLog($actionResult->getMessage());
}
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Filament\RelationManagers\Base;
use App\Filament\Actions\Base\ViewAction;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\Resources\Admin\ActionLog;
use App\Models\Admin\ActionLog as ActionLogModel;
use Filament\Forms\Form;
use Filament\Tables\Table;
/**
* Class ActionLogRelationManager.
*/
class ActionLogRelationManager extends BaseRelationManager
{
protected static string $relationship = 'action_logs';
protected static ?string $recordTitleAttribute = ActionLogModel::ATTRIBUTE_ID;
/**
* The index page of the resource.
*
* @param Table $table
* @return Table
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public function table(Table $table): Table
{
return parent::table($table)
->defaultSort(ActionLogModel::ATTRIBUTE_ID, 'desc')
->heading(__('filament.resources.label.action_logs'))
->pluralModelLabel(__('filament.resources.label.action_logs'))
->columns(ActionLog::table($table)->getColumns())
->paginationPageOptions([5, 10, 25])
->defaultPaginationPageOption(5)
->actions([
ViewAction::make()
->form(fn (Form $form) => ActionLog::form($form)),
]);
}
/**
* Get the filters available for the relation.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getFilters(): array
{
return [];
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
protected function canCreate(): bool
{
return false;
}
}
@@ -4,26 +4,13 @@ declare(strict_types=1);
namespace App\Filament\RelationManagers;
use App\Filament\Actions\Base\AttachAction;
use App\Filament\Actions\Base\CreateAction;
use App\Filament\BulkActions\Base\DetachBulkAction;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\Select;
use App\Filament\RelationManagers\Wiki\ResourceRelationManager;
use App\Filament\Resources\Wiki\Artist\RelationManagers\SongArtistRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\AnimeResourceRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\ArtistResourceRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\SongResourceRelationManager;
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\StudioResourceRelationManager;
use App\Filament\Resources\Wiki\Song\RelationManagers\ArtistSongRelationManager;
use App\Models\BaseModel;
use App\Pivots\BasePivot;
use App\Pivots\Wiki\AnimeResource;
use App\Pivots\Wiki\ArtistSong;
use DateTime;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Actions\AttachAction;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Actions\DetachBulkAction;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -63,17 +50,21 @@ abstract class BaseRelationManager extends RelationManager
TextColumn::make(BasePivot::ATTRIBUTE_CREATED_AT)
->label(__('filament.fields.base.created_at'))
->hidden(fn ($livewire) => !($livewire->getRelationship() instanceof BelongsToMany))
->formatStateUsing(function (BaseModel $record) {
->formatStateUsing(function ($record) {
$pivot = current($record->getRelations());
return (new DateTime(Arr::get($pivot->getAttributes(), BasePivot::ATTRIBUTE_CREATED_AT)))->format('M j, Y H:i:s');
$createdAtField = Arr::get($pivot->getAttributes(), BasePivot::ATTRIBUTE_CREATED_AT);
if (!$createdAtField) return '-';
return (new DateTime($createdAtField))->format('M j, Y H:i:s');
}),
TextColumn::make(BasePivot::ATTRIBUTE_UPDATED_AT)
->label(__('filament.fields.base.updated_at'))
->hidden(fn ($livewire) => !($livewire->getRelationship() instanceof BelongsToMany))
->formatStateUsing(function (BaseModel $record) {
->formatStateUsing(function ($record) {
$pivot = current($record->getRelations());
return (new DateTime(Arr::get($pivot->getAttributes(), BasePivot::ATTRIBUTE_UPDATED_AT)))->format('M j, Y H:i:s');
$updatedAtField = Arr::get($pivot->getAttributes(), BasePivot::ATTRIBUTE_UPDATED_AT);
if (!$updatedAtField) return '-';
return (new DateTime($updatedAtField))->format('M j, Y H:i:s');
}),
],
))
@@ -122,9 +113,7 @@ abstract class BaseRelationManager extends RelationManager
public static function getBulkActions(): array
{
return [
DetachBulkAction::make()
->label(__('filament.bulk_actions.base.detach'))
->authorize('forcedeleteany'),
DetachBulkAction::make(),
];
}
@@ -140,42 +129,7 @@ abstract class BaseRelationManager extends RelationManager
return [
CreateAction::make(),
AttachAction::make()
->hidden(fn (BaseRelationManager $livewire) => !($livewire->getRelationship() instanceof BelongsToMany))
->authorize('create')
->recordSelect(function (BaseRelationManager $livewire) {
/** @var string */
$model = $livewire->getTable()->getModel();
$title = $livewire->getTable()->getRecordTitle(new $model);
return Select::make('recordId')
->label($title)
->useScout($model);
})
->form(function (Form $form, AttachAction $action) {
return $form
->schema([
$action->getRecordSelect(),
TextInput::make(AnimeResource::ATTRIBUTE_AS)
->label(__('filament.fields.anime.resources.as.name'))
->helperText(__('filament.fields.anime.resources.as.help'))
->visibleOn([
AnimeResourceRelationManager::class,
ArtistResourceRelationManager::class,
SongResourceRelationManager::class,
StudioResourceRelationManager::class,
ResourceRelationManager::class,
]),
TextInput::make(ArtistSong::ATTRIBUTE_AS)
->label(__('filament.fields.artist.songs.as.name'))
->helperText(__('filament.fields.artist.songs.as.help'))
->visibleOn([
ArtistSongRelationManager::class,
SongArtistRelationManager::class,
]),
]);
}),
AttachAction::make(),
];
}
}
@@ -107,4 +107,14 @@ abstract class ImageRelationManager extends BaseRelationManager
ImageResource::getHeaderActions(),
);
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
protected function canCreate(): bool
{
return false;
}
}
@@ -107,4 +107,14 @@ abstract class ScriptRelationManager extends BaseRelationManager
ScriptResource::getHeaderActions(),
);
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
protected function canCreate(): bool
{
return false;
}
}
@@ -107,4 +107,14 @@ abstract class VideoRelationManager extends BaseRelationManager
VideoResource::getHeaderActions(),
);
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
protected function canCreate(): bool
{
return false;
}
}
+394
View File
@@ -0,0 +1,394 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Admin;
use App\Enums\Actions\ActionLogStatus;
use App\Enums\Auth\Role;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Filters\DateFilter;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Resources\BaseResource;
use App\Filament\Resources\Admin\ActionLog\Pages\ListActionLogs;
use App\Filament\Resources\Admin\ActionLog\Pages\ViewActionLog;
use App\Filament\Resources\Auth\User;
use App\Models\Admin\ActionLog as ActionLogModel;
use App\Models\Auth\User as UserModel;
use App\Models\BaseModel;
use Filament\Facades\Filament;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Form;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
/**
* Class ActionLog.
*/
class ActionLog extends BaseResource
{
/**
* The model the resource corresponds to.
*
* @var string|null
*/
protected static ?string $model = ActionLogModel::class;
/**
* Get the displayable singular label of the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getLabel(): string
{
return __('filament.resources.singularLabel.action_log');
}
/**
* Get the displayable label of the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getPluralLabel(): string
{
return __('filament.resources.label.action_logs');
}
/**
* The logical group associated with the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getNavigationGroup(): string
{
return __('filament.resources.group.admin');
}
/**
* The icon displayed to the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getNavigationIcon(): string
{
return __('filament.resources.icon.action_logs');
}
/**
* Get the slug (URI key) for the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getRecordSlug(): string
{
return 'action-logs';
}
/**
* Get the route key for the resource.
*
* @return string
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getRecordRouteKeyName(): string
{
return ActionLogModel::ATTRIBUTE_ID;
}
/**
* The form to the actions.
*
* @param Form $form
* @return Form
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function form(Form $form): Form
{
return $form
->schema([
Textarea::make(ActionLogModel::ATTRIBUTE_EXCEPTION)
->label(__('filament.fields.action_log.exception'))
->disabled()
->placeholder('-')
->columnSpanFull(),
]);
}
/**
* The index page of the resource.
*
* @param Table $table
* @return Table
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function table(Table $table): Table
{
return parent::table($table)
->columns([
TextColumn::make(ActionLogModel::ATTRIBUTE_ID)
->label(__('filament.fields.base.id'))
->sortable(),
TextColumn::make(ActionLogModel::ATTRIBUTE_NAME)
->label(__('filament.fields.action_log.name'))
->sortable()
->searchable(),
TextColumn::make(ActionLogModel::ATTRIBUTE_USER)
->label(__('filament.resources.singularLabel.user'))
->urlToRelated(User::class, ActionLogModel::RELATION_USER, true)
->sortable(),
TextColumn::make(ActionLogModel::ATTRIBUTE_TARGET)
->label(__('filament.fields.action_log.target'))
->formatStateUsing(fn ($state) => class_basename($state) . ': ' . $state->getName())
->sortable(),
TextColumn::make(ActionLogModel::ATTRIBUTE_STATUS)
->label(__('filament.fields.action_log.status'))
->formatStateUsing(fn (ActionLogStatus $state) => $state->localize())
->color(fn (ActionLogStatus $state) => $state->color())
->badge()
->sortable(),
TextColumn::make(BaseModel::ATTRIBUTE_CREATED_AT)
->label(__('filament.fields.action_log.happened_at'))
->dateTime()
->sortable(),
TextColumn::make(ActionLogModel::ATTRIBUTE_FINISHED_AT)
->label(__('filament.fields.action_log.finished_at'))
->dateTime()
->sortable()
->placeholder('-'),
]);
}
/**
* Get the infolist available for the resource.
*
* @param Infolist $infolist
* @return Infolist
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
Section::make(static::getRecordTitle($infolist->getRecord()))
->schema([
TextEntry::make(ActionLogModel::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextEntry::make(ActionLogModel::ATTRIBUTE_NAME)
->label(__('filament.fields.action_log.name'))
->formatStateUsing(fn ($state) => ucfirst($state)),
TextEntry::make(ActionLogModel::ATTRIBUTE_USER)
->label(__('filament.resources.singularLabel.user'))
->urlToRelated(User::class, ActionLogModel::RELATION_USER, true),
TextEntry::make(ActionLogModel::ATTRIBUTE_TARGET)
->label(__('filament.fields.action_log.target'))
->formatStateUsing(fn ($state) => class_basename($state) . ': ' . $state->getName()),
TextEntry::make(ActionLogModel::ATTRIBUTE_STATUS)
->label(__('filament.fields.action_log.status'))
->formatStateUsing(fn (ActionLogStatus $state) => $state->localize())
->color(fn (ActionLogStatus $state) => $state->color())
->badge(),
TextEntry::make(BaseModel::ATTRIBUTE_CREATED_AT)
->label(__('filament.fields.action_log.happened_at'))
->dateTime(),
TextEntry::make(ActionLogModel::ATTRIBUTE_FINISHED_AT)
->label(__('filament.fields.action_log.finished_at'))
->dateTime()
->placeholder('-'),
TextEntry::make(ActionLogModel::ATTRIBUTE_EXCEPTION)
->label(__('filament.fields.action_log.exception'))
->placeholder('-')
->columnSpanFull(),
])
->columns(3),
Section::make(__('filament.fields.base.timestamps'))
->schema(parent::timestamps())
->columns(3),
]);
}
/**
* Get the relationships available for the resource.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getRelations(): array
{
return [
RelationGroup::make(
static::getLabel(),
[],
),
];
}
/**
* Get the filters available for the resource.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getFilters(): array
{
return [
SelectFilter::make(ActionLogModel::ATTRIBUTE_STATUS)
->label(__('filament.fields.action_log.status'))
->options(ActionLogStatus::asSelectArray()),
DateFilter::make(BaseModel::ATTRIBUTE_CREATED_AT)
->labels(__('filament.filters.action_log.happened_at_from'), __('filament.filters.action_log.happened_at_to')),
DateFilter::make(ActionLogModel::ATTRIBUTE_FINISHED_AT)
->labels(__('filament.filters.action_log.finished_at_from'), __('filament.filters.action_log.finished_at_to')),
];
}
/**
* Get the actions available for the resource.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getActions(): array
{
return array_merge(
parent::getActions(),
[],
);
}
/**
* Get the bulk actions available for the resource.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getBulkActions(): array
{
return array_merge(
parent::getBulkActions(),
[],
);
}
/**
* Get the header actions available for the resource.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getHeaderActions(): array
{
return array_merge(
parent::getHeaderActions(),
[],
);
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
public static function canCreate(): bool
{
return false;
}
/**
* Determine whether the related model can be edited.
*
* @param Model $record
* @return bool
*/
public static function canEdit(Model $record): bool
{
return false;
}
/**
* Determine whether the related model can be deleted.
*
* @param Model $record
* @return bool
*/
public static function canDelete(Model $record): bool
{
return false;
}
/**
* Determine whether the related model can be force-deleted.
*
* @param Model $record
* @return bool
*/
public static function canForceDelete(Model $record): bool
{
return false;
}
/**
* Determine if the user can access the table.
*
* @return bool
*/
public static function canAccess(): bool
{
/** @var UserModel */
$user = Filament::auth()->user();
return $user->hasRole(Role::ADMIN->value);
}
/**
* Get the pages available for the resource.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getPages(): array
{
return [
'index' => ListActionLogs::route('/'),
'view' => ViewActionLog::route('/{record:id}'),
];
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Admin\ActionLog\Pages;
use App\Filament\Resources\Base\BaseListResources;
use App\Filament\Resources\Admin\ActionLog;
/**
* Class ListActionLogs.
*/
class ListActionLogs extends BaseListResources
{
protected static string $resource = ActionLog::class;
/**
* Get the header actions available.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
protected function getHeaderActions(): array
{
return array_merge(
parent::getHeaderActions(),
[],
);
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Admin\ActionLog\Pages;
use App\Filament\Resources\Base\BaseViewResource;
use App\Filament\Resources\Admin\ActionLog;
/**
* Class ViewActionLog.
*/
class ViewActionLog extends BaseViewResource
{
protected static string $resource = ActionLog::class;
/**
* Get the header actions available.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
protected function getHeaderActions(): array
{
return array_merge(
parent::getHeaderActions(),
[],
);
}
}
@@ -16,6 +16,7 @@ use Filament\Forms\Components\MarkdownEditor;
use Filament\Forms\Form;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Table;
/**
@@ -187,7 +188,14 @@ class Announcement extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
+9 -1
View File
@@ -21,6 +21,7 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Table;
/**
@@ -191,7 +192,14 @@ class Dump extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
+9 -1
View File
@@ -17,6 +17,7 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
@@ -206,7 +207,14 @@ class Feature extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
@@ -27,6 +27,7 @@ use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
@@ -323,7 +324,14 @@ class FeaturedTheme extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
+8 -4
View File
@@ -194,10 +194,14 @@ class Permission extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
RolePermissionRelationManager::class,
UserPermissionRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
RolePermissionRelationManager::class,
UserPermissionRelationManager::class,
],
)
),
];
}
+8 -4
View File
@@ -246,10 +246,14 @@ class Role extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
PermissionRoleRelationManager::class,
UserRoleRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
PermissionRoleRelationManager::class,
UserRoleRelationManager::class,
],
)
),
];
}
+10 -5
View File
@@ -224,11 +224,16 @@ class User extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
RoleUserRelationManager::class,
PermissionUserRelationManager::class,
PlaylistUserRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
RoleUserRelationManager::class,
PermissionUserRelationManager::class,
PlaylistUserRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Filament\Resources\Base;
use App\Models\Admin\ActionLog;
use Filament\Resources\Pages\CreateRecord;
/**
@@ -11,5 +12,13 @@ use Filament\Resources\Pages\CreateRecord;
*/
abstract class BaseCreateResource extends CreateRecord
{
/**
* Run after the record is created.
*
* @return void
*/
protected function afterCreate(): void
{
ActionLog::modelCreated($this->getRecord());
}
}
@@ -4,11 +4,12 @@ declare(strict_types=1);
namespace App\Filament\Resources\Base;
use App\Filament\HeaderActions\Base\DeleteHeaderAction;
use App\Filament\HeaderActions\Base\ForceDeleteHeaderAction;
use App\Filament\HeaderActions\Base\RestoreHeaderAction;
use App\Filament\HeaderActions\Base\ViewHeaderAction;
use App\Models\Admin\ActionLog;
use Filament\Actions\ActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
/**
@@ -26,23 +27,28 @@ abstract class BaseEditResource extends EditRecord
protected function getHeaderActions(): array
{
return [
ViewAction::make()
->label(__('filament.actions.base.view'))
->hidden(fn ($livewire) => $livewire instanceof BaseViewResource),
ViewHeaderAction::make(),
ActionGroup::make([
DeleteAction::make()
DeleteHeaderAction::make()
->label(__('filament.actions.base.delete')),
ForceDeleteAction::make()
->label(__('filament.actions.base.forcedelete'))
->visible(true),
ForceDeleteHeaderAction::make(),
])
->icon('heroicon-o-trash')
->color('danger'),
RestoreAction::make()
->label(__('filament.actions.base.restore')),
RestoreHeaderAction::make(),
];
}
/**
* Run after the record is edited.
*
* @return void
*/
protected function afterSave(): void
{
ActionLog::modelUpdated($this->getRecord());
}
}
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Filament\Resources\Base;
use Filament\Actions\CreateAction;
use App\Filament\HeaderActions\Base\CreateHeaderAction;
use Filament\Resources\Pages\ListRecords;
/**
@@ -22,7 +22,7 @@ abstract class BaseListResources extends ListRecords
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
CreateHeaderAction::make(),
];
}
}
@@ -4,8 +4,9 @@ declare(strict_types=1);
namespace App\Filament\Resources\Base;
use Filament\Actions\EditAction;
use App\Filament\HeaderActions\Base\EditHeaderAction;
use Filament\Resources\Pages\ViewRecord;
use Illuminate\Support\Arr;
/**
* Class BaseViewResource.
@@ -21,14 +22,21 @@ class BaseViewResource extends ViewRecord
*/
protected function getHeaderActions(): array
{
$editPage = (new static::$resource)::getPages()['edit']->getPage();
$pages = (new static::$resource)::getPages();
if (Arr::has($pages, 'edit')) {
$editPage = $pages['edit']->getPage();
$action = (new $editPage)->getHeaderActions();
} else {
$action = [];
}
return array_merge(
[
EditAction::make()
->label(__('filament.actions.base.edit')),
EditHeaderAction::make()
->visible(Arr::has($pages, 'edit')),
],
(new $editPage)->getHeaderActions(),
$action,
);
}
}
+33 -35
View File
@@ -4,22 +4,22 @@ declare(strict_types=1);
namespace App\Filament\Resources;
use App\Filament\Actions\Base\DeleteAction;
use App\Filament\Actions\Base\DetachAction;
use App\Filament\Actions\Base\EditAction;
use App\Filament\Actions\Base\ForceDeleteAction;
use App\Filament\Actions\Base\RestoreAction;
use App\Filament\Actions\Base\ViewAction;
use App\Filament\BulkActions\Base\DeleteBulkAction;
use App\Filament\BulkActions\Base\ForceDeleteBulkAction;
use App\Filament\BulkActions\Base\RestoreBulkAction;
use App\Filament\Components\Filters\DateFilter;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\RelationManagers\Base\ActionLogRelationManager;
use App\Models\BaseModel;
use Filament\Infolists\Components\TextEntry;
use Filament\Resources\Resource;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Actions\DetachAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Actions\ForceDeleteAction;
use Filament\Tables\Actions\ForceDeleteBulkAction;
use Filament\Tables\Actions\RestoreAction;
use Filament\Tables\Actions\RestoreBulkAction;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
@@ -121,30 +121,21 @@ abstract class BaseResource extends Resource
public static function getActions(): array
{
return [
ViewAction::make()
->label(__('filament.actions.base.view')),
ViewAction::make(),
EditAction::make()
->label(__('filament.actions.base.edit')),
EditAction::make(),
ActionGroup::make([
DetachAction::make()
->label(__('filament.actions.base.detach'))
->hidden(fn ($livewire) => !($livewire instanceof BaseRelationManager))
->authorize('delete'),
DetachAction::make(),
DeleteAction::make()
->label(__('filament.actions.base.delete')),
DeleteAction::make(),
ForceDeleteAction::make()
->label(__('filament.actions.base.forcedelete'))
->visible(true),
ForceDeleteAction::make(),
])
->icon('heroicon-o-trash')
->color('danger'),
RestoreAction::make()
->label(__('filament.actions.base.restore')),
RestoreAction::make(),
];
}
@@ -159,18 +150,11 @@ abstract class BaseResource extends Resource
{
return [
BulkActionGroup::make([
DeleteBulkAction::make()
->label(__('filament.bulk_actions.base.delete'))
->authorize('delete', (new static::$model)),
DeleteBulkAction::make(),
ForceDeleteBulkAction::make()
->label(__('filament.bulk_actions.base.forcedelete'))
->hidden(false)
->authorize('forcedelete', (new static::$model)),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make()
->label(__('filament.bulk_actions.base.restore'))
->authorize('restore', (new static::$model)),
RestoreBulkAction::make(),
]),
];
}
@@ -202,6 +186,20 @@ abstract class BaseResource extends Resource
]);
}
/**
* Get the base relationships available for all resources.
*
* @return array
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getBaseRelations(): array
{
return [
ActionLogRelationManager::class,
];
}
/**
* Get the generic slug (URI key) for the resource.
*
@@ -24,6 +24,7 @@ use Filament\Forms\Form;
use Filament\Forms\Set;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Table;
use Illuminate\Support\Arr;
@@ -228,7 +229,14 @@ class DiscordThread extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
+9 -1
View File
@@ -21,6 +21,7 @@ use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
@@ -299,7 +300,14 @@ class Page extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
+9 -4
View File
@@ -301,10 +301,15 @@ class Playlist extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
ImagePlaylistRelationManager::class,
TrackPlaylistRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
ImagePlaylistRelationManager::class,
TrackPlaylistRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
@@ -23,6 +23,7 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Table;
/**
@@ -241,7 +242,14 @@ class Track extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
+13 -8
View File
@@ -352,14 +352,19 @@ class Anime extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
SynonymAnimeRelationManager::class,
ThemeAnimeRelationManager::class,
SeriesAnimeRelationManager::class,
ResourceAnimeRelationManager::class,
ImageAnimeRelationManager::class,
StudioAnimeRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
SynonymAnimeRelationManager::class,
ThemeAnimeRelationManager::class,
SeriesAnimeRelationManager::class,
ResourceAnimeRelationManager::class,
ImageAnimeRelationManager::class,
StudioAnimeRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
@@ -22,6 +22,7 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Validation\Rules\Enum;
@@ -228,7 +229,14 @@ class Synonym extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
+10 -3
View File
@@ -235,6 +235,7 @@ class Theme extends BaseResource
Repeater::make(ThemeModel::RELATION_SONG . '.' . Song::RELATION_ARTISTS)
->label(__('filament.resources.label.artists'))
->addActionLabel(__('filament.buttons.add').' '.__('filament.resources.singularLabel.artist'))
->hidden(fn (Get $get) => $get(ThemeModel::ATTRIBUTE_SONG) === null)
->live(true)
->key('song.artists')
@@ -292,6 +293,7 @@ class Theme extends BaseResource
->schema([
Repeater::make(ThemeModel::RELATION_ENTRIES)
->label(__('filament.resources.label.anime_theme_entries'))
->addActionLabel(__('filament.buttons.add'))
->relationship()
->schema(Entry::form($form)->getComponents()),
]),
@@ -471,9 +473,14 @@ class Theme extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
EntryThemeRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
EntryThemeRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
@@ -337,9 +337,14 @@ class Entry extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
VideoEntryRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
VideoEntryRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
+12 -7
View File
@@ -277,13 +277,18 @@ class Artist extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
SongArtistRelationManager::class,
ResourceArtistRelationManager::class,
MemberArtistRelationManager::class,
GroupArtistRelationManager::class,
ImageArtistRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
SongArtistRelationManager::class,
ResourceArtistRelationManager::class,
MemberArtistRelationManager::class,
GroupArtistRelationManager::class,
ImageArtistRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
+18 -5
View File
@@ -12,7 +12,6 @@ use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Filters\NumberFilter;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Resources\BaseResource;
use App\Filament\Resources\Wiki\Audio\Pages\CreateAudio;
use App\Filament\Resources\Wiki\Audio\Pages\EditAudio;
use App\Filament\Resources\Wiki\Audio\Pages\ListAudios;
use App\Filament\Resources\Wiki\Audio\Pages\ViewAudio;
@@ -219,9 +218,14 @@ class Audio extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
VideoAudioRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
VideoAudioRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
@@ -301,6 +305,16 @@ class Audio extends BaseResource
];
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
public static function canCreate(): bool
{
return false;
}
/**
* Get the pages available for the resource.
*
@@ -312,7 +326,6 @@ class Audio extends BaseResource
{
return [
'index' => ListAudios::route('/'),
'create' => CreateAudio::route('/create'),
'view' => ViewAudio::route('/{record:audio_id}'),
'edit' => EditAudio::route('/{record:audio_id}/edit'),
];
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Wiki\Audio\Pages;
use App\Filament\Resources\Base\BaseCreateResource;
use App\Filament\Resources\Wiki\Audio;
/**
* Class CreateAudio.
*/
class CreateAudio extends BaseCreateResource
{
protected static string $resource = Audio::class;
}
@@ -246,12 +246,17 @@ class ExternalResource extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
AnimeResourceRelationManager::class,
ArtistResourceRelationManager::class,
SongResourceRelationManager::class,
StudioResourceRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
AnimeResourceRelationManager::class,
ArtistResourceRelationManager::class,
SongResourceRelationManager::class,
StudioResourceRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
+8 -3
View File
@@ -230,9 +230,14 @@ class Group extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
ThemeGroupRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
ThemeGroupRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
+26 -13
View File
@@ -9,7 +9,6 @@ use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Resources\BaseResource;
use App\Filament\Resources\Wiki\Image\Pages\CreateImage;
use App\Filament\Resources\Wiki\Image\Pages\EditImage;
use App\Filament\Resources\Wiki\Image\Pages\ListImages;
use App\Filament\Resources\Wiki\Image\Pages\ViewImage;
@@ -208,12 +207,17 @@ class Image extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
AnimeImageRelationManager::class,
ArtistImageRelationManager::class,
PlaylistImageRelationManager::class,
StudioImageRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
AnimeImageRelationManager::class,
ArtistImageRelationManager::class,
PlaylistImageRelationManager::class,
StudioImageRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
@@ -273,12 +277,22 @@ class Image extends BaseResource
*
* @noinspection PhpMissingParentCallCommonInspection
*/
public static function getHeaderActions(): array
{
return [
public static function getHeaderActions(): array
{
return [
UploadImageTableAction::make('upload-image'),
];
}
];
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
public static function canCreate(): bool
{
return false;
}
/**
* Get the pages available for the resource.
@@ -291,7 +305,6 @@ class Image extends BaseResource
{
return [
'index' => ListImages::route('/'),
'create' => CreateImage::route('/create'),
'view' => ViewImage::route('/{record:image_id}'),
'edit' => EditImage::route('/{record:image_id}/edit'),
];
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Wiki\Image\Pages;
use App\Filament\Resources\Base\BaseCreateResource;
use App\Filament\Resources\Wiki\Image;
/**
* Class CreateImage.
*/
class CreateImage extends BaseCreateResource
{
protected static string $resource = Image::class;
}
+8 -3
View File
@@ -246,9 +246,14 @@ class Series extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
AnimeSeriesRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
AnimeSeriesRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
+10 -5
View File
@@ -245,11 +245,16 @@ class Song extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
ArtistSongRelationManager::class,
ThemeSongRelationManager::class,
ResourceSongRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
ArtistSongRelationManager::class,
ThemeSongRelationManager::class,
ResourceSongRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
+10 -5
View File
@@ -239,11 +239,16 @@ class Studio extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
AnimeStudioRelationManager::class,
ResourceStudioRelationManager::class,
ImageStudioRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
AnimeStudioRelationManager::class,
ResourceStudioRelationManager::class,
ImageStudioRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
+20 -7
View File
@@ -16,7 +16,6 @@ use App\Filament\Components\Fields\Select;
use App\Filament\Components\Filters\NumberFilter;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Resources\BaseResource;
use App\Filament\Resources\Wiki\Video\Pages\CreateVideo;
use App\Filament\Resources\Wiki\Video\Pages\EditVideo;
use App\Filament\Resources\Wiki\Video\Pages\ListVideos;
use App\Filament\Resources\Wiki\Video\Pages\ViewVideo;
@@ -302,11 +301,16 @@ class Video extends BaseResource
public static function getRelations(): array
{
return [
RelationGroup::make(static::getLabel(), [
EntryVideoRelationManager::class,
ScriptVideoRelationManager::class,
TrackVideoRelationManager::class,
]),
RelationGroup::make(static::getLabel(),
array_merge(
[
EntryVideoRelationManager::class,
ScriptVideoRelationManager::class,
TrackVideoRelationManager::class,
],
parent::getBaseRelations(),
)
),
];
}
@@ -415,6 +419,16 @@ class Video extends BaseResource
];
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
public static function canCreate(): bool
{
return false;
}
/**
* Get the pages available for the resource.
*
@@ -426,7 +440,6 @@ class Video extends BaseResource
{
return [
'index' => ListVideos::route('/'),
'create' => CreateVideo::route('/create'),
'view' => ViewVideo::route('/{record:video_id}'),
'edit' => EditVideo::route("/{record:video_id}/edit"),
];
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Wiki\Video\Pages;
use App\Filament\Resources\Base\BaseCreateResource;
use App\Filament\Resources\Wiki\Video;
/**
* Class CreateVideo.
*/
class CreateVideo extends BaseCreateResource
{
protected static string $resource = Video::class;
}
+19 -3
View File
@@ -9,7 +9,6 @@ use App\Filament\Actions\Storage\Wiki\Video\Script\MoveScriptAction;
use App\Filament\BulkActions\Storage\Wiki\Video\Script\DeleteScriptBulkAction;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Resources\BaseResource;
use App\Filament\Resources\Wiki\Video\Script\Pages\CreateScript;
use App\Filament\Resources\Wiki\Video\Script\Pages\EditScript;
use App\Filament\Resources\Wiki\Video\Script\Pages\ListScripts;
use App\Filament\Resources\Wiki\Video\Script\Pages\ViewScript;
@@ -20,6 +19,7 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Infolist;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Table;
@@ -175,7 +175,14 @@ class Script extends BaseResource
*/
public static function getRelations(): array
{
return [];
return [
RelationGroup::make(static::getLabel(),
array_merge(
[],
parent::getBaseRelations(),
)
),
];
}
/**
@@ -249,6 +256,16 @@ class Script extends BaseResource
];
}
/**
* Determine whether the related model can be created.
*
* @return bool
*/
public static function canCreate(): bool
{
return false;
}
/**
* Get the pages available for the resource.
*
@@ -260,7 +277,6 @@ class Script extends BaseResource
{
return [
'index' => ListScripts::route('/'),
'create' => CreateScript::route('/create'),
'view' => ViewScript::route('/{record:script_id}'),
'edit' => EditScript::route('/{record:script_id}/edit'),
];
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Wiki\Video\Script\Pages;
use App\Filament\Resources\Base\BaseCreateResource;
use App\Filament\Resources\Wiki\Video\Script;
/**
* Class CreateScript.
*/
class CreateScript extends BaseCreateResource
{
protected static string $resource = Script::class;
}
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace App\Filament\TableActions;
use App\Concerns\Filament\Actions\HasActionLogs;
use App\Filament\RelationManagers\BaseRelationManager;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\Action;
@@ -17,6 +19,8 @@ use Filament\Tables\Actions\Action;
*/
abstract class BaseTableAction extends Action
{
use HasActionLogs;
/**
* Initial setup for the action.
*
@@ -28,6 +32,18 @@ abstract class BaseTableAction extends Action
$this->requiresConfirmation();
$this->afterFormValidated(function ($livewire, BaseTableAction $action) {
if ($livewire instanceof BaseRelationManager) {
$this->createActionLog($action, $livewire->getOwnerRecord());
}
});
$this->after(function ($livewire, BaseTableAction $action) {
if ($livewire instanceof BaseRelationManager) {
$this->finishedLog();
}
});
$this->modalWidth(MaxWidth::FourExtraLarge);
$this->action(fn (array $data) => $this->handle($data));
@@ -112,6 +112,7 @@ class DiscordEditMessageTableAction extends BaseTableAction
Repeater::make(DiscordMessage::ATTRIBUTE_EMBEDS)
->label(__('filament.table_actions.discord_thread.message.embeds.name'))
->addActionLabel(__('filament.buttons.add'))
->helperText(__('filament.table_actions.discord_thread.message.embeds.help'))
->key(DiscordMessage::ATTRIBUTE_EMBEDS)
->collapsible()
@@ -141,6 +142,7 @@ class DiscordEditMessageTableAction extends BaseTableAction
Repeater::make(DiscordEmbed::ATTRIBUTE_FIELDS)
->label(__('filament.table_actions.discord_thread.message.embeds.body.fields.title.name'))
->addActionLabel(__('filament.buttons.add'))
->helperText(__('filament.table_actions.discord_thread.message.embeds.body.fields.title.help'))
->collapsible()
->schema([
@@ -164,6 +166,7 @@ class DiscordEditMessageTableAction extends BaseTableAction
Repeater::make(DiscordMessage::ATTRIBUTE_IMAGES)
->label(__('filament.table_actions.discord_thread.message.images.name'))
->addActionLabel(__('filament.buttons.add'))
->helperText(__('filament.table_actions.discord_thread.message.images.help'))
->key(DiscordMessage::ATTRIBUTE_IMAGES)
->collapsible()
@@ -73,6 +73,7 @@ class DiscordSendMessageTableAction extends BaseTableAction
Repeater::make(DiscordMessage::ATTRIBUTE_EMBEDS)
->label(__('filament.table_actions.discord_thread.message.embeds.name'))
->addActionLabel(__('filament.buttons.add'))
->helperText(__('filament.table_actions.discord_thread.message.embeds.help'))
->collapsible()
->defaultItems(0)
@@ -101,6 +102,7 @@ class DiscordSendMessageTableAction extends BaseTableAction
Repeater::make(DiscordEmbed::ATTRIBUTE_FIELDS)
->label(__('filament.table_actions.discord_thread.message.embeds.body.fields.title.name'))
->addActionLabel(__('filament.buttons.add'))
->helperText(__('filament.table_actions.discord_thread.message.embeds.body.fields.title.help'))
->collapsible()
->schema([
@@ -124,6 +126,7 @@ class DiscordSendMessageTableAction extends BaseTableAction
Repeater::make(DiscordMessage::ATTRIBUTE_IMAGES)
->label(__('filament.table_actions.discord_thread.message.images.name'))
->addActionLabel(__('filament.buttons.add'))
->helperText(__('filament.table_actions.discord_thread.message.images.help'))
->collapsible()
->defaultItems(0)
@@ -5,7 +5,9 @@ declare(strict_types=1);
namespace App\Filament\TableActions\Storage;
use App\Contracts\Actions\Storage\StorageAction as BaseStorageAction;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\TableActions\BaseTableAction;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* Class StorageTableAction.
@@ -34,8 +36,27 @@ abstract class StorageTableAction extends BaseTableAction
$storageResults->toLog();
$action->then($storageResults);
$model = $action->then($storageResults);
$actionResult = $storageResults->toActionResult();
if ($actionResult->hasFailed()) {
$this->failedLog($actionResult->getMessage());
return;
}
$livewire = $this->getLivewire();
if ($livewire instanceof BaseRelationManager) {
/** @var BelongsToMany */
$relation = $livewire->getRelationship();
$pivotClass = $relation->getPivotClass();
$pivot = $pivotClass::query()
->where($livewire->getOwnerRecord()->getKeyName(), $livewire->getOwnerRecord()->getKey())
->where($model->getKeyName(), $model->getKey())
->first();
$this->updateLog($model, $pivot);
}
}
}
+408
View File
@@ -0,0 +1,408 @@
<?php
declare(strict_types=1);
namespace App\Models\Admin;
use App\Contracts\Models\HasSubtitle;
use App\Contracts\Models\Nameable;
use App\Enums\Actions\ActionLogStatus;
use App\Models\Auth\User;
use App\Models\BaseModel;
use Filament\Facades\Filament;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Str;
use Throwable;
/**
* Class ActionLog.
*
* @property int $id
* @property string $batch_id
* @property string $name
* @property string $actionable_type
* @property int $actionable_id
* @property string $target_type
* @property int $target_id
* @property string $model_type
* @property int $model_id
* @property string|null $exception
* @property Carbon|null $finished_at
* @property ActionLogStatus $status
* @property morphs $target
* @property int $user_id
* @property User $user
*/
class ActionLog extends Model implements Nameable, HasSubtitle
{
final public const TABLE = 'action_logs';
final public const ATTRIBUTE_ID = 'id';
final public const ATTRIBUTE_BATCH_ID = 'batch_id';
final public const ATTRIBUTE_NAME = 'name';
final public const ATTRIBUTE_USER = 'user_id';
final public const ATTRIBUTE_ACTIONABLE = 'actionable';
final public const ATTRIBUTE_ACTIONABLE_TYPE = 'actionable_type';
final public const ATTRIBUTE_ACTIONABLE_ID = 'actionable_id';
final public const ATTRIBUTE_TARGET = 'target';
final public const ATTRIBUTE_TARGET_TYPE = 'target_type';
final public const ATTRIBUTE_TARGET_ID = 'target_id';
final public const ATTRIBUTE_MODEL_TYPE = 'model_type';
final public const ATTRIBUTE_MODEL_ID = 'model_id';
final public const ATTRIBUTE_STATUS = 'status';
final public const ATTRIBUTE_EXCEPTION = 'exception';
final public const ATTRIBUTE_FINISHED_AT = 'finished_at';
final public const RELATION_USER = 'user';
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
ActionLog::ATTRIBUTE_BATCH_ID,
ActionLog::ATTRIBUTE_NAME,
ActionLog::ATTRIBUTE_USER,
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE,
ActionLog::ATTRIBUTE_ACTIONABLE_ID,
ActionLog::ATTRIBUTE_TARGET_TYPE,
ActionLog::ATTRIBUTE_TARGET_ID,
ActionLog::ATTRIBUTE_MODEL_TYPE,
ActionLog::ATTRIBUTE_MODEL_ID,
ActionLog::ATTRIBUTE_STATUS,
ActionLog::ATTRIBUTE_EXCEPTION,
ActionLog::ATTRIBUTE_FINISHED_AT,
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::class,
];
/**
* The table associated with the model.
*
* @var string
*/
protected $table = ActionLog::TABLE;
/**
* The primary key associated with the table.
*
* @var string
*/
protected $primaryKey = ActionLog::ATTRIBUTE_ID;
/**
* Get name.
*
* @return string
*/
public function getName(): string
{
return Str::of($this->name)
->append(' - ')
->append($this->target()->getName())
->__toString();
}
/**
* Get subtitle.
*
* @return string
*/
public function getSubtitle(): string
{
return $this->name;
}
/**
* Get the actionable.
*
* @return MorphTo
*/
public function actionable(): MorphTo
{
return $this->morphTo();
}
/**
* Get the user that initiated the action.
*
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, ActionLog::ATTRIBUTE_USER);
}
/**
* Get the target of the action for user interface linking.
*
* @return MorphTo|BaseModel
*/
public function target(): MorphTo|BaseModel
{
return $this
->morphTo(ActionLog::ATTRIBUTE_TARGET, ActionLog::ATTRIBUTE_TARGET_TYPE, ActionLog::ATTRIBUTE_TARGET_ID)
->withTrashed();
}
/**
* Get the user for the action log.
*
* @return User
*/
public static function getUser(): User
{
return Filament::auth()->user();
}
/**
* Register an action log for a model created.
*
* @param Model $model
* @return ActionLog
*/
public static function modelCreated(Model $model): ActionLog
{
return ActionLog::query()->create([
ActionLog::ATTRIBUTE_BATCH_ID => (string) Str::orderedUuid(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUser()->getKey(),
ActionLog::ATTRIBUTE_NAME => 'Create',
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(),
]);
}
/**
* Register an action log for a model updated.
*
* @param Model $model
* @return ActionLog
*/
public static function modelUpdated(Model $model): ActionLog
{
return ActionLog::query()->create([
ActionLog::ATTRIBUTE_BATCH_ID => (string) Str::orderedUuid(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUser()->getKey(),
ActionLog::ATTRIBUTE_NAME => 'Update',
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(),
]);
}
/**
* Register an action log for a model deleted.
*
* @param Model $model
* @return ActionLog
*/
public static function modelDeleted(Model $model): ActionLog
{
return ActionLog::modelSoftDeleted('Delete', $model);
}
/**
* Register an action log for a model restored.
*
* @param Model $model
* @return ActionLog
*/
public static function modelRestored(Model $model): ActionLog
{
return ActionLog::modelSoftDeleted('Restore', $model);
}
/**
* Register an action log for a model that is soft-deleted.
*
* @param string $actionName
* @param Model $model
* @return ActionLog
*/
public static function modelSoftDeleted(string $actionName, Model $model): ActionLog
{
return ActionLog::query()->create([
ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUser()->getKey(),
ActionLog::ATTRIBUTE_NAME => $actionName,
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(),
]);
}
/**
* Register an action log for a model attached.
*
* @param string $action
* @param Model $related
* @param Model $parent
* @param Model $pivot
* @return ActionLog
*/
public static function modelPivot(string $action, Model $related, Model $parent, Model $pivot): ActionLog
{
return ActionLog::query()->create([
ActionLog::ATTRIBUTE_BATCH_ID => Str::orderedUuid()->__toString(),
ActionLog::ATTRIBUTE_USER => ActionLog::getUser()->getKey(),
ActionLog::ATTRIBUTE_NAME => $action,
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $related->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $related->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => $parent->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $parent->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => $pivot->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $pivot->getKey(),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(),
]);
}
/**
* Register an action log for when a model has an action executed.
*
* @param string $batchId
* @param mixed $action
* @param Model $model
* @return ActionLog
*/
public static function modelActioned(string $batchId, mixed $action, Model $model): ActionLog
{
return ActionLog::query()->create([
ActionLog::ATTRIBUTE_BATCH_ID => $batchId,
ActionLog::ATTRIBUTE_USER => ActionLog::getUser()->getKey(),
ActionLog::ATTRIBUTE_NAME => $action->getLabel(),
ActionLog::ATTRIBUTE_ACTIONABLE_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_ACTIONABLE_ID => $model->getKey(),
ActionLog::ATTRIBUTE_TARGET_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_TARGET_ID => $model->getKey(),
ActionLog::ATTRIBUTE_MODEL_TYPE => $model->getMorphClass(),
ActionLog::ATTRIBUTE_MODEL_ID => $model->getKey(),
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::RUNNING->value,
]);
}
/**
* Update the all the models status of a batch to running.
*
* @return void
*/
public function batchRunning(): void
{
$this->query()->where(ActionLog::ATTRIBUTE_BATCH_ID, $this->batch_id)
->whereNotIn(ActionLog::ATTRIBUTE_STATUS, [ActionLogStatus::FINISHED->value, ActionLogStatus::FAILED->value])
->update([
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::RUNNING->value,
]);
}
/**
* Update the all the models status of a batch to finished.
*
* @return void
*/
public function batchFinished(): void
{
$this->query()->where(ActionLog::ATTRIBUTE_BATCH_ID, $this->batch_id)
->whereNotIn(ActionLog::ATTRIBUTE_STATUS, [ActionLogStatus::FINISHED->value, ActionLogStatus::FAILED->value])
->update([
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FINISHED->value,
ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(),
]);
}
/**
* Update the all the models status of a batch to failed.
*
* @param Throwable|string|null $exception
* @return void
*/
public function batchFailed(Throwable|string|null $exception = null): void
{
$this->query()->where(ActionLog::ATTRIBUTE_BATCH_ID, $this->batch_id)
->whereNotIn(ActionLog::ATTRIBUTE_STATUS, [ActionLogStatus::FINISHED->value, ActionLogStatus::FAILED->value])
->update([
ActionLog::ATTRIBUTE_STATUS => ActionLogStatus::FAILED->value,
ActionLog::ATTRIBUTE_EXCEPTION => $exception ? Str::of($exception)->__toString() : null,
ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(),
]);
}
/**
* Update the model status to finished.
*
* @return void
*/
public function finished(): void
{
$this->updateStatus(ActionLogStatus::FINISHED);
}
/**
* Update the model status to failed.
*
* @param Throwable|string|null $exception
* @return void
*/
public function failed(Throwable|string|null $exception = null): void
{
$this->updateStatus(ActionLogStatus::FAILED, $exception);
}
/**
* Check if the model status is failed.
*
* @return bool
*/
public function isFailed(): bool
{
return $this->status === ActionLogStatus::FAILED;
}
/**
* Update the status of a given action event.
*
* @param ActionLogStatus $status
* @param Throwable|string|null $exception
* @return void
*/
public function updateStatus(ActionLogStatus $status, Throwable|string|null $exception = null): void
{
$this->update([
ActionLog::ATTRIBUTE_STATUS => $status->value,
ActionLog::ATTRIBUTE_EXCEPTION => Str::of($exception)->__toString(),
ActionLog::ATTRIBUTE_FINISHED_AT => Date::now(),
]);
}
}
+11
View File
@@ -11,6 +11,7 @@ use App\Events\Auth\User\UserCreated;
use App\Events\Auth\User\UserDeleted;
use App\Events\Auth\User\UserRestored;
use App\Events\Auth\User\UserUpdated;
use App\Models\Admin\ActionLog;
use App\Models\List\Playlist;
use Database\Factories\Auth\UserFactory;
use Filament\Models\Contracts\FilamentUser;
@@ -223,4 +224,14 @@ class User extends Authenticatable implements MustVerifyEmail, Nameable, HasSubt
{
return $this->hasMany(Playlist::class, Playlist::ATTRIBUTE_USER);
}
/**
* Get the action logs the user executed.
*
* @return HasMany
*/
public function action_logs(): HasMany
{
return $this->hasMany(ActionLog::class, ActionLog::ATTRIBUTE_USER);
}
}
+12
View File
@@ -7,10 +7,12 @@ namespace App\Models;
use App\Contracts\Models\Nameable;
use App\Contracts\Models\HasSubtitle;
use App\Enums\Http\Api\Filter\ComparisonOperator;
use App\Models\Admin\ActionLog;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
@@ -100,4 +102,14 @@ abstract class BaseModel extends Model implements Nameable, HasSubtitle
now()->subWeek()
);
}
/**
* Get the action logs for the model.
*
* @return MorphMany
*/
public function action_logs(): MorphMany
{
return $this->morphMany(ActionLog::class, 'actionable');
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
use App\Models\Admin\ActionLog;
use App\Models\Auth\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (! Schema::hasTable(ActionLog::TABLE)) {
Schema::create(ActionLog::TABLE, function (Blueprint $table) {
$table->bigIncrements(ActionLog::ATTRIBUTE_ID);
$table->string(ActionLog::ATTRIBUTE_BATCH_ID);
$table->unsignedBigInteger(ActionLog::ATTRIBUTE_USER);
$table->foreign(ActionLog::ATTRIBUTE_USER)->references(User::ATTRIBUTE_ID)->on(User::TABLE)->cascadeOnDelete();
$table->string(ActionLog::ATTRIBUTE_NAME);
$table->morphs(ActionLog::ATTRIBUTE_ACTIONABLE);
$table->morphs(ActionLog::ATTRIBUTE_TARGET);
$table->string(ActionLog::ATTRIBUTE_MODEL_TYPE);
$table->uuid(ActionLog::ATTRIBUTE_MODEL_ID)->nullable();
$table->integer(ActionLog::ATTRIBUTE_STATUS)->nullable();
$table->text(ActionLog::ATTRIBUTE_EXCEPTION)->nullable();
$table->timestamps(6);
$table->timestamp(ActionLog::ATTRIBUTE_FINISHED_AT, 6)->nullable();
$table->index([ActionLog::ATTRIBUTE_BATCH_ID, ActionLog::ATTRIBUTE_MODEL_TYPE, ActionLog::ATTRIBUTE_MODEL_ID]);
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists(ActionLog::TABLE);
}
};
+6
View File
@@ -2,6 +2,7 @@
declare(strict_types=1);
use App\Enums\Actions\ActionLogStatus;
use App\Enums\Models\List\PlaylistVisibility;
use App\Enums\Models\Wiki\AnimeMediaFormat;
use App\Enums\Models\Wiki\AnimeSeason;
@@ -34,6 +35,11 @@ return [
AnimeSynonymType::ENGLISH->name => 'English',
AnimeSynonymType::SHORT->name => 'Short',
],
ActionLogStatus::class => [
ActionLogStatus::RUNNING->name => 'Running',
ActionLogStatus::FAILED->name => 'Failed',
ActionLogStatus::FINISHED->name => 'Finished',
],
ImageFacet::class => [
ImageFacet::COVER_SMALL->name => 'Small Cover',
ImageFacet::COVER_LARGE->name => 'Large Cover',
+20
View File
@@ -433,6 +433,9 @@ return [
],
],
],
'buttons' => [
'add' => 'Add',
],
'dashboards' => [
'icon' => [
'admin' => 'heroicon-m-chart-bar',
@@ -444,6 +447,14 @@ return [
],
],
'fields' => [
'action_log' => [
'name' => 'Name',
'target' => 'Target',
'status' => 'Status',
'happened_at' => 'Happened At',
'finished_at' => 'Finished At',
'exception' => 'Exception',
],
'anime_synonym' => [
'text' => [
'help' => 'For alternative titles, licensed titles, common abbreviations and/or shortenings',
@@ -822,6 +833,12 @@ return [
],
],
'filters' => [
'action_log' => [
'finished_at_from' => 'Finished At - From',
'finished_at_to' => 'Finished At - To',
'happened_at_from' => 'Happened At - From',
'happened_at_to' => 'Happened At - To',
],
'anime' => [
'year_from' => 'Year - From',
'year_to' => 'Year - To',
@@ -871,6 +888,7 @@ return [
'wiki' => 'Wiki',
],
'icon' => [
'action_logs' => 'heroicon-o-rectangle-stack',
'anime_synonyms' => 'heroicon-o-globe-alt',
'anime_theme_entries' => 'heroicon-o-list-bullet',
'anime_themes' => 'heroicon-o-list-bullet',
@@ -899,6 +917,7 @@ return [
'videos' => 'heroicon-o-film',
],
'label' => [
'action_logs' => 'Action Logs',
'anime_synonyms' => 'Anime Synonyms',
'anime_theme_entries' => 'Anime Theme Entries',
'anime_themes' => 'Anime Themes',
@@ -927,6 +946,7 @@ return [
'videos' => 'Videos',
],
'singularLabel' => [
'action_log' => 'Action Log',
'anime_synonym' => 'Anime Synonym',
'anime_theme_entry' => 'Anime Theme Entry',
'anime_theme' => 'Anime Theme',
+3
View File
@@ -28,3 +28,6 @@ parameters:
-
message: '#Right side of && is always true.#'
path: app/Providers/RouteServiceProvider.php
-
message: '#Call to an undefined method App\\Filament\\BulkActions\\.*::getRecord\(\).#'
path: app/Concerns/Filament/Actions/HasActionLogs.php