Using Sushi with Filament Table and refresh the data
Hello everyone!
I'm trying to make a LogViewer in a page via a modal. To do this, I was thinking of using Sushi to transform the log rows into an Eloquent array so that I could use the Filament tables.
So I made a Model and a Livewire component with a Table.
In the Table, I do a
Log::query()
and everything works fine.
Where I'm stuck is that I'd like to add a select that will fetch the logs available in the storage/logs folder. When the user selects a file, I'd like to send this file to the Model so that it can redo the Eloquent array for me before refreshing the table. The problem is that I don't really know how to do it, or even if it's possible.
I've tried passing the log file to my Select's afterStateUpdated
method and calling the Model's getRows method, but it doesn't seem to work. I guess I miss something 👀3 Replies
Here, the Model :
And my Component :
Thanks for your help 😅
Ok with somes test, I have new... Here, everything work well :
The dd() shows me the contents of the log file I've selected.
I guess the problem is here:
How can I refresh the data in my table according to the getRows() updated in my Model?
<?php
namespace App\Models;
use App\Enums\LogLevel;
use Illuminate\Database\Eloquent\Model;
use Sushi\Sushi;
use Illuminate\Support\Facades\File;
class Log extends Model
{
use Sushi;
protected $schema = [
'timestamp' => 'string',
'level' => 'string',
'environment' => 'string',
'message' => 'text',
];
protected $casts = [
'level' => LogLevel::class,
];
protected ?string $logFilePath = null;
public function getRows($logFile = '/logs/laravel.log'): array
{
$rows = [];
if($logFile === null) {
$this->logFilePath = storage_path() . '/logs/laravel.log';
} else {
$this->logFilePath = storage_path() . $logFile;
}
if ($this->logFilePath && File::exists($this->logFilePath)) {
$fileContent = File::get($this->logFilePath);
$lines = explode("\n", $fileContent);
$currentEntry = null;
foreach ($lines as $line) {
if (preg_match('/\[(.*?)\] (\w+)\.([A-Z]+): (.*)/', $line, $matches)) {
if ($currentEntry) {
$rows[] = $currentEntry;
}
$currentEntry = [
'timestamp' => $matches[1],
'environment' => $matches[2],
'level' => strtolower($matches[3]),
'message' => $matches[4],
];
} elseif ($currentEntry && trim($line) !== '') {
$currentEntry['message'] .= "\n" . $line;
}
}
if ($currentEntry) {
$rows[] = $currentEntry;
}
}
return $rows;
}
protected function sushiShouldCache(): bool
{
return false;
}
}
<?php
namespace App\Models;
use App\Enums\LogLevel;
use Illuminate\Database\Eloquent\Model;
use Sushi\Sushi;
use Illuminate\Support\Facades\File;
class Log extends Model
{
use Sushi;
protected $schema = [
'timestamp' => 'string',
'level' => 'string',
'environment' => 'string',
'message' => 'text',
];
protected $casts = [
'level' => LogLevel::class,
];
protected ?string $logFilePath = null;
public function getRows($logFile = '/logs/laravel.log'): array
{
$rows = [];
if($logFile === null) {
$this->logFilePath = storage_path() . '/logs/laravel.log';
} else {
$this->logFilePath = storage_path() . $logFile;
}
if ($this->logFilePath && File::exists($this->logFilePath)) {
$fileContent = File::get($this->logFilePath);
$lines = explode("\n", $fileContent);
$currentEntry = null;
foreach ($lines as $line) {
if (preg_match('/\[(.*?)\] (\w+)\.([A-Z]+): (.*)/', $line, $matches)) {
if ($currentEntry) {
$rows[] = $currentEntry;
}
$currentEntry = [
'timestamp' => $matches[1],
'environment' => $matches[2],
'level' => strtolower($matches[3]),
'message' => $matches[4],
];
} elseif ($currentEntry && trim($line) !== '') {
$currentEntry['message'] .= "\n" . $line;
}
}
if ($currentEntry) {
$rows[] = $currentEntry;
}
}
return $rows;
}
protected function sushiShouldCache(): bool
{
return false;
}
}
class LogViewer extends Component implements HasTable, HasForms
{
use InteractsWithTable;
use InteractsWithForms;
public ?array $logsFiles = [];
public string $selectedLogfile = '';
public Model $log;
public function mount(): void
{
$this->logsFiles = $this->getLogFiles();
$this->selectedLogfile = $this->logsFiles[0];
$this->log = new Log();
}
public function form(Form $form): Form {
return $form->schema([
Select::make('selectedLogfile')
->label(false)
->options($this->logsFiles)
->placeholder('Select a log file')
->searchable()
->live()
->afterStateUpdated(function (string $state) {
$this->log->getRows('/logs/'.$this->logsFiles[$state]);
}),
]);
}
public function table(Table $table): Table
{
return $table
->query(Log::query())
->columns([
TextColumn::make('timestamp')->label('Timestamp'),
TextColumn::make('environment')->label('Environment'),
TextColumn::make('level')->label('Level')->badge(),
TextColumn::make('message')->label('Message')->limit(100)->searchable(),
])
->filters([
SelectFilter::make('level')->options(LogLevel::class),
]);
}
public function render()
{
return view('livewire.admin.log-viewer');
}
public function getLogFiles(): array
{
$logPath = storage_path('logs');
$logFiles = File::files($logPath);
return array_map(fn ($file) => $file->getFilename(),
array_filter($logFiles, fn ($file) => $file->isFile() && str_ends_with($file->getFilename(), '.log')));
}
}
class LogViewer extends Component implements HasTable, HasForms
{
use InteractsWithTable;
use InteractsWithForms;
public ?array $logsFiles = [];
public string $selectedLogfile = '';
public Model $log;
public function mount(): void
{
$this->logsFiles = $this->getLogFiles();
$this->selectedLogfile = $this->logsFiles[0];
$this->log = new Log();
}
public function form(Form $form): Form {
return $form->schema([
Select::make('selectedLogfile')
->label(false)
->options($this->logsFiles)
->placeholder('Select a log file')
->searchable()
->live()
->afterStateUpdated(function (string $state) {
$this->log->getRows('/logs/'.$this->logsFiles[$state]);
}),
]);
}
public function table(Table $table): Table
{
return $table
->query(Log::query())
->columns([
TextColumn::make('timestamp')->label('Timestamp'),
TextColumn::make('environment')->label('Environment'),
TextColumn::make('level')->label('Level')->badge(),
TextColumn::make('message')->label('Message')->limit(100)->searchable(),
])
->filters([
SelectFilter::make('level')->options(LogLevel::class),
]);
}
public function render()
{
return view('livewire.admin.log-viewer');
}
public function getLogFiles(): array
{
$logPath = storage_path('logs');
$logFiles = File::files($logPath);
return array_map(fn ($file) => $file->getFilename(),
array_filter($logFiles, fn ($file) => $file->isFile() && str_ends_with($file->getFilename(), '.log')));
}
}
return $form->schema([
Select::make('selectedLogfile')
->label(false)
->options($this->logsFiles)
->placeholder('Select a log file')
->searchable()
->live()
->afterStateUpdated(function (string $state) {
$this->selectedLogfile = $this->logsFiles[$state];
$this->log->setLogFile('/logs/' . $this->selectedLogfile);
dd($this->log->getRows()); //Here, all good ! :)
$this->dispatch('refresh');
}),
]);
return $form->schema([
Select::make('selectedLogfile')
->label(false)
->options($this->logsFiles)
->placeholder('Select a log file')
->searchable()
->live()
->afterStateUpdated(function (string $state) {
$this->selectedLogfile = $this->logsFiles[$state];
$this->log->setLogFile('/logs/' . $this->selectedLogfile);
dd($this->log->getRows()); //Here, all good ! :)
$this->dispatch('refresh');
}),
]);
public function table(Table $table): Table
{
return $table
->query(Log::query()) //Here :(
->columns([
TextColumn::make('timestamp')->label('Timestamp'),
TextColumn::make('environment')->label('Environment'),
TextColumn::make('level')->label('Level')->badge(),
TextColumn::make('message')->label('Message')->limit(100)->searchable(),
])
->filters([
SelectFilter::make('level')->options(LogLevel::class),
]);
}
public function table(Table $table): Table
{
return $table
->query(Log::query()) //Here :(
->columns([
TextColumn::make('timestamp')->label('Timestamp'),
TextColumn::make('environment')->label('Environment'),
TextColumn::make('level')->label('Level')->badge(),
TextColumn::make('message')->label('Message')->limit(100)->searchable(),
])
->filters([
SelectFilter::make('level')->options(LogLevel::class),
]);
}
have you tried the command ->poll('10') which will refresh your table after 10 second
public function table(Table $table): Table
{
return $table
->query( Log::query() )
->poll('10')
->columns([
//
])
->filters([
SelectFilter::make('level')->options(LogLevel::class),
]);
}
public function table(Table $table): Table
{
return $table
->query( Log::query() )
->poll('10')
->columns([
//
])
->filters([
SelectFilter::make('level')->options(LogLevel::class),
]);
}
$this->logFilePath
was empty...
In my Model class :
public function getRows(): array {
$rows = [];
// I added this :
if(!$this->logFilePath) {
$this->logFilePath = storage_path('logs/laravel.log');
}
if ($this->logFilePath && File::exists($this->logFilePath)) {
$fileContent = File::get($this->logFilePath);
$lines = explode("\n", $fileContent);
$currentEntry = null;
foreach ($lines as $line) {
if (preg_match('/\[(.*?)\] (\w+)\.([A-Z]+): (.*)/', $line, $matches)) {
if ($currentEntry) {
$rows[] = $currentEntry;
}
$currentEntry = [
'timestamp' => $matches[1],
'environment' => $matches[2],
'level' => strtolower($matches[3]),
'message' => $matches[4],
];
} elseif ($currentEntry && trim($line) !== '') {
$currentEntry['message'] .= "\n" . $line;
}
}
if ($currentEntry) {
$rows[] = $currentEntry;
}
}
return $rows;
}
public function getRows(): array {
$rows = [];
// I added this :
if(!$this->logFilePath) {
$this->logFilePath = storage_path('logs/laravel.log');
}
if ($this->logFilePath && File::exists($this->logFilePath)) {
$fileContent = File::get($this->logFilePath);
$lines = explode("\n", $fileContent);
$currentEntry = null;
foreach ($lines as $line) {
if (preg_match('/\[(.*?)\] (\w+)\.([A-Z]+): (.*)/', $line, $matches)) {
if ($currentEntry) {
$rows[] = $currentEntry;
}
$currentEntry = [
'timestamp' => $matches[1],
'environment' => $matches[2],
'level' => strtolower($matches[3]),
'message' => $matches[4],
];
} elseif ($currentEntry && trim($line) !== '') {
$currentEntry['message'] .= "\n" . $line;
}
}
if ($currentEntry) {
$rows[] = $currentEntry;
}
}
return $rows;
}