diff --git a/app/Bots/BashScript.php b/app/Bots/BashScript.php new file mode 100644 index 0000000..c71fcf6 --- /dev/null +++ b/app/Bots/BashScript.php @@ -0,0 +1,46 @@ +config = $config; + } + + public function run(): void + { + if (!empty($this->config['script'])) { + // Execute the bash script + $script = $this->config['script']; + $output = []; + $returnVar = null; + exec($script, $output, $returnVar); + + if ($returnVar !== 0) { + // Log error if the script failed + \Illuminate\Support\Facades\Log::error("Bash script execution failed: " . implode("\n", $output)); + } else { + Log::info("Bash script executed successfully: " . implode("\n", $output)); + } + } + } + + public static function configSchema(): array + { + return [ + 'script' => [ + 'type' => 'textarea', + 'label' => 'Bash Script', + 'rules' => [ + 'required', + ] + ], + ]; + } +} diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index f908a28..d9d2f89 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -3,12 +3,13 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; class DashboardController extends Controller { public function index() { - $bots = \App\Models\Bot::all(); + $bots = Auth::user()->bots; return view('dashboard', compact('bots')); } diff --git a/app/Jobs/RunBot.php b/app/Jobs/RunBot.php index 674a25b..59dc610 100644 --- a/app/Jobs/RunBot.php +++ b/app/Jobs/RunBot.php @@ -3,6 +3,7 @@ namespace App\Jobs; use App\Models\Bot; +use App\Models\BotLog; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; @@ -13,9 +14,7 @@ class RunBot implements ShouldQueue /** * Create a new job instance. */ - public function __construct(private Bot $bot) - { - } + public function __construct(private Bot $bot, private BotLog $log) {} /** * Execute the job. @@ -24,6 +23,22 @@ class RunBot implements ShouldQueue { $class = new $this->bot->class($this->bot->config ?? []); - $class->run(); + try { + $class->run(); + + // Update the log entry on success + $this->log->update([ + 'finished_at' => now(), + 'status' => 'success', + // 'output' => is_string($result) ? $result : json_encode($result, JSON_PRETTY_PRINT), + ]); + } catch (\Throwable $e) { + // Log the error in the bot log + $this->log->update([ + 'finished_at' => now(), + 'status' => 'failed', + 'error' => $e->getMessage(), + ]); + } } } diff --git a/app/Livewire/BotLogs.php b/app/Livewire/BotLogs.php new file mode 100644 index 0000000..f199c8e --- /dev/null +++ b/app/Livewire/BotLogs.php @@ -0,0 +1,35 @@ +getLogs(); + // } + + // public function getLogs(): void + // { + // $this->logs = Auth::user()->bots->pluck('logs')->flatten()->sortByDesc('created_at'); + // } + + public function render() + { + return view('livewire.bot-logs', [ + 'logs' => BotLog::whereHas('bot', fn($q) => $q->where('user_id', Auth::id())) + ->orderBy('created_at', 'desc') + ->latest() + ->paginate(10), + ]); + } +} diff --git a/app/Livewire/BotsList.php b/app/Livewire/BotsList.php index 1a2cdc4..cc13936 100644 --- a/app/Livewire/BotsList.php +++ b/app/Livewire/BotsList.php @@ -12,11 +12,21 @@ class BotsList extends Component { public $bots; + public string $query = ''; + public function mount() { $this->bots = Auth::user()->bots; } + public function search() + { + $this->bots = Auth::user()->bots() + ->where('name', 'like', '%' . $this->query . '%') + ->orWhere('class', 'like', '%' . $this->query . '%') + ->get(); + } + public function toggleBot($botId) { $bot = \App\Models\Bot::find($botId); @@ -62,8 +72,13 @@ class BotsList extends Component public function confirmRunBot(Bot $bot): void { + $log = $bot->logs()->create([ + 'status' => 'pending', + 'started_at' => now(), + ]); + // Dispatch the job to run the bot - dispatch(new RunBot($bot)); + dispatch(new RunBot($bot, $log)); flash()->success("Bot '{$bot->name}' is being executed."); } diff --git a/app/Livewire/ViewBot.php b/app/Livewire/ViewBot.php new file mode 100644 index 0000000..e2c239b --- /dev/null +++ b/app/Livewire/ViewBot.php @@ -0,0 +1,38 @@ +bot->name . '?') + ->asConfirm() + ->onConfirm('confirmRunBot') + ->show(); + } + + public function confirmRunBot(): void + { + $log = $this->bot->logs()->create([ + 'status' => 'pending', + ]); + + // Dispatch the job to run the bot + dispatch(new RunBot($this->bot, $log)); + + flash()->success("Bot '{$this->bot->name}' is being executed."); + } + + public function render() + { + return view('livewire.view-bot'); + } +} diff --git a/app/Models/Bot.php b/app/Models/Bot.php index 1af2e7d..80b8a5d 100644 --- a/app/Models/Bot.php +++ b/app/Models/Bot.php @@ -51,4 +51,19 @@ class Bot extends Model { return $this->belongsTo(User::class); } + + public function logs() + { + return $this->hasMany(BotLog::class); + } + + public function latestLog() + { + return $this->hasOne(BotLog::class)->latestOfMany(); + } + + public function failedLogs() + { + return $this->hasMany(BotLog::class)->where('status', 'failed'); + } } diff --git a/app/Models/BotLog.php b/app/Models/BotLog.php new file mode 100644 index 0000000..ea20660 --- /dev/null +++ b/app/Models/BotLog.php @@ -0,0 +1,30 @@ + 'datetime', + 'finished_at' => 'datetime', + ]; + } + + public function bot() + { + return $this->belongsTo(Bot::class); + } +} diff --git a/app/Services/BotService.php b/app/Services/BotService.php index 62a4279..8074e98 100644 --- a/app/Services/BotService.php +++ b/app/Services/BotService.php @@ -4,6 +4,7 @@ namespace App\Services; use App\Models\Bot; use App\Bots\BotContract; +use App\Jobs\RunBot; use Cron\CronExpression; use Illuminate\Support\Facades\Log; @@ -16,14 +17,29 @@ class BotService foreach ($bots as $bot) { $cron = new CronExpression($bot->schedule); if ($cron->isDue()) { + $log = $bot->logs()->create([ + 'status' => 'pending', + 'started_at' => now(), + ]); + try { $instance = app($bot->class, ['config' => $bot->config]); if ($instance instanceof BotContract) { - $instance->run(); + dispatch(RunBot::class, $bot, $log); + $log->update([ + 'started_at' => now(), + 'status' => 'running' + ]); } } catch (\Throwable $e) { Log::error("Bot [{$bot->name}] failed: " . $e->getMessage()); + + $log->update([ + 'finished_at' => now(), + 'status' => 'failed', + 'error' => $e->getMessage(), + ]); } Log::info("Bot [{$bot->name}] executed successfully."); diff --git a/database/migrations/2025_08_31_170246_create_bot_logs_table.php b/database/migrations/2025_08_31_170246_create_bot_logs_table.php new file mode 100644 index 0000000..530b1e6 --- /dev/null +++ b/database/migrations/2025_08_31_170246_create_bot_logs_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('bot_id'); + $table->timestamp('started_at')->nullable(); + $table->timestamp('finished_at')->nullable(); + $table->string('status')->default('pending'); // pending, running, success, failed + $table->text('output')->nullable(); // store raw response/log text + $table->text('error')->nullable(); // store error message/trace if failed + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('bot_logs'); + } +}; diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 2f30e47..383d42c 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -28,13 +28,16 @@
-
+
Logs
+
+ @livewire('bot-logs') +

All Bots

diff --git a/resources/views/livewire/bot-logs.blade.php b/resources/views/livewire/bot-logs.blade.php new file mode 100644 index 0000000..28b59cb --- /dev/null +++ b/resources/views/livewire/bot-logs.blade.php @@ -0,0 +1,40 @@ +
+ + + + + + + + + + + @foreach ($logs as $log) + + + + + + + @endforeach + +
BotStatusStarted AtFinished At
+ {{ $log->bot->name }} + $log->status === 'failed', + 'text-green-400' => $log->status === 'success', + 'text-yellow-400' => $log->status === 'pending', + ]), + class="px-4 py-2" + > + {{ $log->status }} + + {{ $log->started_at ?? '' }} + + {{ $log->finished_at ?? '' }} +
+
+ {{ $logs->links() }} +
+
diff --git a/resources/views/livewire/bots-list.blade.php b/resources/views/livewire/bots-list.blade.php index e66fa49..c11409d 100644 --- a/resources/views/livewire/bots-list.blade.php +++ b/resources/views/livewire/bots-list.blade.php @@ -1,13 +1,27 @@
- +
+ +
+
+ Searching + +
+
- - - - - - + + + + + + @@ -16,7 +30,7 @@ -
NameSchemaScheduleNext dueEnabledActionsNameSchemaScheduleNext dueEnabledActions
{{ $bot->name }} @@ -37,7 +51,7 @@ wire:click="toggleBot({{ $bot->id }})" /> + + + + diff --git a/routes/web.php b/routes/web.php index 936cbc6..972f415 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,6 +6,7 @@ use App\Livewire\EditBot; use App\Livewire\Settings\Appearance; use App\Livewire\Settings\Password; use App\Livewire\Settings\Profile; +use App\Livewire\ViewBot; use Illuminate\Support\Facades\Route; Route::get('/', function () { @@ -19,6 +20,8 @@ Route::get('dashboard', [App\Http\Controllers\DashboardController::class, 'index Route::middleware(['auth'])->group(function () { Route::get('bots/create', CreateEditBot::class) ->name('bots.create'); + Route::get('bots/{bot}', ViewBot::class) + ->name('bots.show')->middleware('can:view,bot'); Route::get('bots/edit/{bot}', CreateEditBot::class) ->name('bots.edit')->middleware('can:view,bot');