F
Filament2mo ago
Gryfon

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
Gryfon
GryfonOP2mo ago
Here, the 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;
}
}
And my Component :
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')));
}
}
Thanks for your help 😅 Ok with somes test, I have new... Here, everything work well :
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');
}),
]);
The dd() shows me the contents of the log file I've selected. I guess the problem is here:
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),
]);
}
How can I refresh the data in my table according to the getRows() updated in my Model?
oks_voice123
oks_voice1232mo ago
have you tried the command ->poll('10') which will refresh your table after 10 second
Gryfon
GryfonOP2mo ago
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),
]);
}
Unfortunately, it doesn't change anything 😢 I've also added log loading by default in my Model because otherwise the $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;
}
I don't know what to do 😦 Just a little up 🙈
Want results from more Discord servers?
Add your server