added a help system, replaced the words "tenant" and "Pwa" with better alternatives. corrected and implemented cron jobs. prepared going live on a coolify-powered system.
This commit is contained in:
194
app/Console/Commands/MonitorStorageCommand.php
Normal file
194
app/Console/Commands/MonitorStorageCommand.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Console\Concerns\InteractsWithCacheLocks;
|
||||
use App\Models\EventMediaAsset;
|
||||
use App\Models\MediaStorageTarget;
|
||||
use App\Services\Storage\StorageHealthService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Cache\Lock;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class MonitorStorageCommand extends Command
|
||||
{
|
||||
use InteractsWithCacheLocks;
|
||||
|
||||
protected $signature = 'storage:monitor {--force : Execute even if another run is still in progress}';
|
||||
|
||||
protected $description = 'Collect storage capacity statistics and cache them for dashboards & alerts.';
|
||||
|
||||
public function __construct(private readonly StorageHealthService $storageHealth)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$lockSeconds = (int) config('storage-monitor.monitor.lock_seconds', 300);
|
||||
$lock = $this->acquireCommandLock('storage:monitor', $lockSeconds, (bool) $this->option('force'));
|
||||
|
||||
if ($lock === false) {
|
||||
$this->warn('storage:monitor is already running on another worker.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
try {
|
||||
$targets = MediaStorageTarget::active()->get();
|
||||
|
||||
if ($targets->isEmpty()) {
|
||||
$this->info('No media storage targets available.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$assetStats = $this->buildAssetStatistics();
|
||||
$thresholds = $this->capacityThresholds();
|
||||
$alerts = [];
|
||||
$snapshotTargets = [];
|
||||
|
||||
foreach ($targets as $target) {
|
||||
$capacity = $this->storageHealth->getCapacity($target);
|
||||
$assets = $assetStats[$target->id] ?? [
|
||||
'total' => 0,
|
||||
'bytes' => 0,
|
||||
'by_status' => [],
|
||||
];
|
||||
|
||||
$severity = $this->determineCapacitySeverity($capacity, $thresholds);
|
||||
if ($severity !== 'ok') {
|
||||
$alerts[] = [
|
||||
'target' => $target->key,
|
||||
'type' => 'capacity',
|
||||
'severity' => $severity,
|
||||
'percentage' => $capacity['percentage'] ?? null,
|
||||
'status' => $capacity['status'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
$failedCount = $assets['by_status']['failed']['count'] ?? 0;
|
||||
if ($failedCount > 0) {
|
||||
$alerts[] = [
|
||||
'target' => $target->key,
|
||||
'type' => 'failed_assets',
|
||||
'severity' => 'warning',
|
||||
'failed' => $failedCount,
|
||||
];
|
||||
}
|
||||
|
||||
$snapshotTargets[] = [
|
||||
'id' => $target->id,
|
||||
'key' => $target->key,
|
||||
'name' => $target->name,
|
||||
'is_hot' => (bool) $target->is_hot,
|
||||
'capacity' => $capacity,
|
||||
'assets' => $assets,
|
||||
];
|
||||
}
|
||||
|
||||
$snapshot = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'targets' => $snapshotTargets,
|
||||
'alerts' => $alerts,
|
||||
];
|
||||
|
||||
$ttlMinutes = max(1, (int) config('storage-monitor.monitor.cache_minutes', 15));
|
||||
Cache::put('storage:monitor:last', $snapshot, now()->addMinutes($ttlMinutes));
|
||||
|
||||
Log::channel('storage-jobs')->info('Storage monitor snapshot generated', [
|
||||
'targets' => count($snapshotTargets),
|
||||
'alerts' => count($alerts),
|
||||
]);
|
||||
|
||||
$this->info(sprintf(
|
||||
'Storage monitor finished: %d targets, %d alerts.',
|
||||
count($snapshotTargets),
|
||||
count($alerts)
|
||||
));
|
||||
|
||||
return self::SUCCESS;
|
||||
} finally {
|
||||
if ($lock instanceof Lock) {
|
||||
$lock->release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array>
|
||||
*/
|
||||
private function buildAssetStatistics(): array
|
||||
{
|
||||
return EventMediaAsset::query()
|
||||
->selectRaw('media_storage_target_id, status, COUNT(*) as total_count, COALESCE(SUM(size_bytes), 0) as total_bytes')
|
||||
->groupBy('media_storage_target_id', 'status')
|
||||
->get()
|
||||
->groupBy('media_storage_target_id')
|
||||
->map(function ($rows) {
|
||||
$byStatus = [];
|
||||
$totalCount = 0;
|
||||
$totalBytes = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$count = (int) ($row->total_count ?? 0);
|
||||
$bytes = (int) ($row->total_bytes ?? 0);
|
||||
|
||||
$totalCount += $count;
|
||||
$totalBytes += $bytes;
|
||||
$byStatus[$row->status] = [
|
||||
'count' => $count,
|
||||
'bytes' => $bytes,
|
||||
];
|
||||
}
|
||||
|
||||
ksort($byStatus);
|
||||
|
||||
return [
|
||||
'total' => $totalCount,
|
||||
'bytes' => $totalBytes,
|
||||
'by_status' => $byStatus,
|
||||
];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
private function capacityThresholds(): array
|
||||
{
|
||||
$warning = (int) config('storage-monitor.capacity_thresholds.warning', 75);
|
||||
$critical = (int) config('storage-monitor.capacity_thresholds.critical', 90);
|
||||
|
||||
if ($warning > $critical) {
|
||||
[$warning, $critical] = [$critical, $warning];
|
||||
}
|
||||
|
||||
return [
|
||||
'warning' => $warning,
|
||||
'critical' => $critical,
|
||||
];
|
||||
}
|
||||
|
||||
private function determineCapacitySeverity(array $capacity, array $thresholds): string
|
||||
{
|
||||
$status = $capacity['status'] ?? 'ok';
|
||||
if ($status !== 'ok') {
|
||||
return $status;
|
||||
}
|
||||
|
||||
$percentage = $capacity['percentage'] ?? null;
|
||||
if ($percentage === null) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
if ($percentage >= ($thresholds['critical'] ?? 95)) {
|
||||
return 'critical';
|
||||
}
|
||||
|
||||
if ($percentage >= ($thresholds['warning'] ?? 75)) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
return 'ok';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user