TenantAtlas/app/Livewire/BulkOperationProgress.php
ahmido a62c855851 feat/032-backup-scheduling-mvp (#36)
Adds Backup Scheduling MVP (CRUD, dispatcher, run job, retention, audit logs)
Run now / Retry persist Filament DB notifications
Bulk Run/Retry now create BulkOperationRun so bottom-right progress widget shows them
Progress widget includes “recent finished” window + reconciles stale backup bulk runs
Adds purge command + migration backup_schedule_runs.user_id + tests updates

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #36
2026-01-07 01:12:12 +00:00

146 lines
4.1 KiB
PHP

<?php
namespace App\Livewire;
use App\Models\BackupScheduleRun;
use App\Models\BulkOperationRun;
use App\Models\Tenant;
use Illuminate\Support\Arr;
use Livewire\Attributes\Computed;
use Livewire\Component;
class BulkOperationProgress extends Component
{
public $runs;
public int $pollSeconds = 3;
public int $recentFinishedSeconds = 12;
public function mount()
{
$this->pollSeconds = max(1, min(10, (int) config('tenantpilot.bulk_operations.poll_interval_seconds', 3)));
$this->recentFinishedSeconds = max(3, min(60, (int) config('tenantpilot.bulk_operations.recent_finished_seconds', 12)));
$this->loadRuns();
}
#[Computed]
public function activeRuns()
{
return $this->runs;
}
public function loadRuns()
{
try {
$tenant = Tenant::current();
} catch (\RuntimeException $e) {
$this->runs = collect();
return;
}
$recentThreshold = now()->subSeconds($this->recentFinishedSeconds);
$this->runs = BulkOperationRun::query()
->where('tenant_id', $tenant->id)
->where('user_id', auth()->id())
->where(function ($query) use ($recentThreshold): void {
$query->whereIn('status', ['pending', 'running'])
->orWhere(function ($query) use ($recentThreshold): void {
$query->whereIn('status', ['completed', 'completed_with_errors', 'failed', 'aborted'])
->where('updated_at', '>=', $recentThreshold);
});
})
->orderByDesc('created_at')
->get();
$this->reconcileBackupScheduleRuns($tenant->id);
}
private function reconcileBackupScheduleRuns(int $tenantId): void
{
$userId = auth()->id();
if (! $userId) {
return;
}
$staleThreshold = now()->subSeconds(60);
foreach ($this->runs as $bulkRun) {
if ($bulkRun->resource !== 'backup_schedule') {
continue;
}
if (! in_array($bulkRun->status, ['pending', 'running'], true)) {
continue;
}
if (! $bulkRun->created_at || $bulkRun->created_at->gt($staleThreshold)) {
continue;
}
$scheduleId = (int) Arr::first($bulkRun->item_ids ?? []);
if ($scheduleId <= 0) {
continue;
}
$scheduleRun = BackupScheduleRun::query()
->where('tenant_id', $tenantId)
->where('user_id', $userId)
->where('backup_schedule_id', $scheduleId)
->where('created_at', '>=', $bulkRun->created_at)
->orderByDesc('id')
->first();
if (! $scheduleRun) {
continue;
}
if ($scheduleRun->finished_at) {
$processed = 1;
$succeeded = 0;
$failed = 0;
$skipped = 0;
$status = 'completed';
switch ($scheduleRun->status) {
case BackupScheduleRun::STATUS_SUCCESS:
$succeeded = 1;
break;
case BackupScheduleRun::STATUS_SKIPPED:
$skipped = 1;
break;
default:
$failed = 1;
$status = 'completed_with_errors';
break;
}
$bulkRun->forceFill([
'status' => $status,
'processed_items' => $processed,
'succeeded' => $succeeded,
'failed' => $failed,
'skipped' => $skipped,
])->save();
continue;
}
if ($scheduleRun->started_at && $bulkRun->status === 'pending') {
$bulkRun->forceFill(['status' => 'running'])->save();
}
}
}
public function render(): \Illuminate\Contracts\View\View
{
return view('livewire.bulk-operation-progress');
}
}