mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat: action logs (#702)
This commit is contained in:
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user