horizon manage command
This commit is contained in:
127
app/Console/Commands/HorizonManage.php
Normal file
127
app/Console/Commands/HorizonManage.php
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Repositories\CustomJobRepository;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
use Laravel\Horizon\Contracts\MetricsRepository;
|
||||||
|
|
||||||
|
use function Laravel\Prompts\multiselect;
|
||||||
|
use function Laravel\Prompts\select;
|
||||||
|
use function Laravel\Prompts\table;
|
||||||
|
|
||||||
|
class HorizonManage extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'horizon:manage ';
|
||||||
|
|
||||||
|
protected $description = 'Manage horizon';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$action = select(
|
||||||
|
label: 'What to do?',
|
||||||
|
options: [
|
||||||
|
'pending' => 'Pending Jobs',
|
||||||
|
'running' => 'Running Jobs',
|
||||||
|
'workers' => 'Workers',
|
||||||
|
'failed' => 'Failed Jobs',
|
||||||
|
'failed-delete' => 'Failed Jobs - Delete',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($action === 'pending') {
|
||||||
|
$pendingJobs = app(JobRepository::class)->getPending();
|
||||||
|
$pendingJobsTable = [];
|
||||||
|
if (count($pendingJobs) === 0) {
|
||||||
|
$this->info('No pending jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($pendingJobs as $pendingJob) {
|
||||||
|
$pendingJobsTable[] = [
|
||||||
|
'id' => $pendingJob->id,
|
||||||
|
'name' => $pendingJob->name,
|
||||||
|
'status' => $pendingJob->status,
|
||||||
|
'reserved_at' => $pendingJob->reserved_at ? now()->parse($pendingJob->reserved_at)->format('Y-m-d H:i:s') : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($pendingJobsTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'failed') {
|
||||||
|
$failedJobs = app(JobRepository::class)->getFailed();
|
||||||
|
$failedJobsTable = [];
|
||||||
|
if (count($failedJobs) === 0) {
|
||||||
|
$this->info('No failed jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($failedJobs as $failedJob) {
|
||||||
|
$failedJobsTable[] = [
|
||||||
|
'id' => $failedJob->id,
|
||||||
|
'name' => $failedJob->name,
|
||||||
|
'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($failedJobsTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'failed-delete') {
|
||||||
|
$failedJobs = app(JobRepository::class)->getFailed();
|
||||||
|
$failedJobsTable = [];
|
||||||
|
foreach ($failedJobs as $failedJob) {
|
||||||
|
$failedJobsTable[] = [
|
||||||
|
'id' => $failedJob->id,
|
||||||
|
'name' => $failedJob->name,
|
||||||
|
'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
app(MetricsRepository::class)->clear();
|
||||||
|
if (count($failedJobsTable) === 0) {
|
||||||
|
$this->info('No failed jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$jobIds = multiselect(
|
||||||
|
label: 'Which job to delete?',
|
||||||
|
options: collect($failedJobsTable)->mapWithKeys(fn ($job) => [$job['id'] => $job['id'].' - '.$job['name']])->toArray(),
|
||||||
|
);
|
||||||
|
foreach ($jobIds as $jobId) {
|
||||||
|
Artisan::queue('horizon:forget', ['id' => $jobId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'running') {
|
||||||
|
$redisJobRepository = app(CustomJobRepository::class);
|
||||||
|
$runningJobs = $redisJobRepository->getReservedJobs();
|
||||||
|
$runningJobsTable = [];
|
||||||
|
if (count($runningJobs) === 0) {
|
||||||
|
$this->info('No running jobs found.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dump($runningJobs);
|
||||||
|
foreach ($runningJobs as $runningJob) {
|
||||||
|
$runningJobsTable[] = [
|
||||||
|
'id' => $runningJob->id,
|
||||||
|
'name' => $runningJob->name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($runningJobsTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'workers') {
|
||||||
|
$redisJobRepository = app(CustomJobRepository::class);
|
||||||
|
$workers = $redisJobRepository->getHorizonWorkers();
|
||||||
|
$workersTable = [];
|
||||||
|
foreach ($workers as $worker) {
|
||||||
|
$workersTable[] = [
|
||||||
|
'name' => $worker->name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
table($workersTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/Contracts/CustomJobRepositoryInterface.php
Normal file
24
app/Contracts/CustomJobRepositoryInterface.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
|
||||||
|
interface CustomJobRepositoryInterface extends JobRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get all jobs with a specific status.
|
||||||
|
*/
|
||||||
|
public function getJobsByStatus(string $status): Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the count of jobs with a specific status.
|
||||||
|
*/
|
||||||
|
public function countJobsByStatus(string $status): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get jobs that have been running longer than a specified duration in seconds.
|
||||||
|
*/
|
||||||
|
public function getLongRunningJobs(int $seconds): Collection;
|
||||||
|
}
|
||||||
@@ -2,40 +2,27 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Contracts\CustomJobRepositoryInterface;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use App\Repositories\CustomJobRepository;
|
||||||
use Laravel\Horizon\Horizon;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Laravel\Horizon\HorizonApplicationServiceProvider;
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
|
||||||
class HorizonServiceProvider extends HorizonApplicationServiceProvider
|
class HorizonServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Bootstrap any application services.
|
* Register services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->singleton(JobRepository::class, CustomJobRepository::class);
|
||||||
|
$this->app->singleton(CustomJobRepositoryInterface::class, CustomJobRepository::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap services.
|
||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
parent::boot();
|
//
|
||||||
|
|
||||||
// Horizon::routeSmsNotificationsTo('15556667777');
|
|
||||||
// Horizon::routeMailNotificationsTo('example@example.com');
|
|
||||||
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
|
|
||||||
|
|
||||||
Horizon::night();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the Horizon gate.
|
|
||||||
*
|
|
||||||
* This gate determines who can access Horizon in non-local environments.
|
|
||||||
*/
|
|
||||||
protected function gate(): void
|
|
||||||
{
|
|
||||||
Gate::define('viewHorizon', function ($user) {
|
|
||||||
$root_user = User::find(0);
|
|
||||||
|
|
||||||
return in_array($user->email, [
|
|
||||||
$root_user->email,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
app/Repositories/CustomJobRepository.php
Normal file
71
app/Repositories/CustomJobRepository.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\CustomJobRepositoryInterface;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Laravel\Horizon\Repositories\RedisJobRepository;
|
||||||
|
use Laravel\Horizon\Repositories\RedisMasterSupervisorRepository;
|
||||||
|
|
||||||
|
class CustomJobRepository extends RedisJobRepository implements CustomJobRepositoryInterface
|
||||||
|
{
|
||||||
|
public function getHorizonWorkers()
|
||||||
|
{
|
||||||
|
$redisMasterSupervisorRepository = app(RedisMasterSupervisorRepository::class);
|
||||||
|
|
||||||
|
return $redisMasterSupervisorRepository->all();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReservedJobs(): Collection
|
||||||
|
{
|
||||||
|
return $this->getJobsByStatus('reserved');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all jobs with a specific status.
|
||||||
|
*/
|
||||||
|
public function getJobsByStatus(string $status, ?string $worker = null): Collection
|
||||||
|
{
|
||||||
|
$jobs = new Collection;
|
||||||
|
|
||||||
|
$this->getRecent()->each(function ($job) use ($jobs, $status, $worker) {
|
||||||
|
if ($job->status === $status) {
|
||||||
|
if ($worker) {
|
||||||
|
dump($job);
|
||||||
|
if ($job->worker !== $worker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$jobs->push($job);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the count of jobs with a specific status.
|
||||||
|
*/
|
||||||
|
public function countJobsByStatus(string $status): int
|
||||||
|
{
|
||||||
|
return $this->getJobsByStatus($status)->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get jobs that have been running longer than a specified duration in seconds.
|
||||||
|
*/
|
||||||
|
public function getLongRunningJobs(int $seconds): Collection
|
||||||
|
{
|
||||||
|
$jobs = new Collection;
|
||||||
|
|
||||||
|
$this->getRecent()->each(function ($job) use ($jobs, $seconds) {
|
||||||
|
if ($job->status === 'reserved' &&
|
||||||
|
isset($job->reserved_at) &&
|
||||||
|
(time() - strtotime($job->reserved_at)) > $seconds) {
|
||||||
|
$jobs->push($job);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $jobs;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user