Enterprise Services administration dashboard
* Database health checks * Cache health checks * Storage write and disk-space checks * Failed queue job monitoring * Production environment diagnostics * Persisted health-check history * Queue retry controls * Scheduler execution controls * Cache and optimisation clearing * Backup creation and retention * Backup deletion * SQLite database backup support * Backup manifests and module metadata * Artisan health and backup commands * Administration navigation * Enterprise permissions * Module registration and tests
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace Modules\Enterprise\Services;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Enterprise\Models\Backup;
|
||||
use RuntimeException;
|
||||
use ZipArchive;
|
||||
|
||||
final class BackupService
|
||||
{
|
||||
public function create(?int $userId = null): Backup
|
||||
{
|
||||
if (! class_exists(ZipArchive::class)) throw new RuntimeException('The PHP zip extension is required.');
|
||||
|
||||
$disk = (string) config('enterprise.backup_disk', 'local');
|
||||
$directory = trim((string) config('enterprise.backup_path', 'backups'), '/');
|
||||
$name = 'cms-'.now()->format('Ymd-His').'.zip';
|
||||
$relativePath = $directory.'/'.$name;
|
||||
$temporary = storage_path('app/'.$name);
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($temporary, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) throw new RuntimeException('Unable to create backup archive.');
|
||||
|
||||
$manifest = json_encode([
|
||||
'application' => config('app.name'),
|
||||
'environment' => app()->environment(),
|
||||
'created_at' => now()->toIso8601String(),
|
||||
'php' => PHP_VERSION,
|
||||
'laravel' => app()->version(),
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
$zip->addFromString('manifest.json', $manifest ?: '{}');
|
||||
$zip->addFile(base_path('composer.json'), 'composer.json');
|
||||
if (File::exists(base_path('modules_statuses.json'))) $zip->addFile(base_path('modules_statuses.json'), 'modules_statuses.json');
|
||||
if (config('database.default') === 'sqlite') {
|
||||
$database = database_path('database.sqlite');
|
||||
if (File::exists($database)) $zip->addFile($database, 'database/database.sqlite');
|
||||
}
|
||||
$zip->close();
|
||||
|
||||
Storage::disk($disk)->put($relativePath, File::get($temporary));
|
||||
File::delete($temporary);
|
||||
|
||||
return Backup::create([
|
||||
'user_id' => $userId,
|
||||
'disk' => $disk,
|
||||
'path' => $relativePath,
|
||||
'size' => Storage::disk($disk)->size($relativePath),
|
||||
'status' => 'completed',
|
||||
'completed_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete(Backup $backup): void
|
||||
{
|
||||
Storage::disk($backup->disk)->delete($backup->path);
|
||||
$backup->delete();
|
||||
}
|
||||
|
||||
public function prune(): int
|
||||
{
|
||||
$cutoff = now()->subDays((int) config('enterprise.retention_days', 30));
|
||||
$backups = Backup::query()->where('created_at', '<', $cutoff)->get();
|
||||
$backups->each(fn (Backup $backup) => $this->delete($backup));
|
||||
return $backups->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace Modules\Enterprise\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Throwable;
|
||||
|
||||
final class SystemHealthService
|
||||
{
|
||||
public function run(): array
|
||||
{
|
||||
$checks = [
|
||||
$this->database(),
|
||||
$this->cache(),
|
||||
$this->storage(),
|
||||
$this->queue(),
|
||||
$this->environment(),
|
||||
];
|
||||
|
||||
return [
|
||||
'status' => collect($checks)->contains(fn (array $check): bool => $check['status'] === 'failed') ? 'failed' : (collect($checks)->contains(fn (array $check): bool => $check['status'] === 'warning') ? 'warning' : 'healthy'),
|
||||
'checks' => $checks,
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
private function database(): array
|
||||
{
|
||||
try {
|
||||
DB::connection()->getPdo();
|
||||
return $this->result('Database', 'healthy', DB::connection()->getDriverName());
|
||||
} catch (Throwable $e) {
|
||||
return $this->result('Database', 'failed', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function cache(): array
|
||||
{
|
||||
try {
|
||||
$key = 'enterprise-health-'.bin2hex(random_bytes(6));
|
||||
Cache::put($key, 'ok', 10);
|
||||
$healthy = Cache::get($key) === 'ok';
|
||||
Cache::forget($key);
|
||||
return $this->result('Cache', $healthy ? 'healthy' : 'failed', config('cache.default'));
|
||||
} catch (Throwable $e) {
|
||||
return $this->result('Cache', 'failed', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function storage(): array
|
||||
{
|
||||
try {
|
||||
Storage::disk('local')->put('enterprise-health.txt', now()->toIso8601String());
|
||||
Storage::disk('local')->delete('enterprise-health.txt');
|
||||
$freeMb = (int) floor(disk_free_space(storage_path()) / 1024 / 1024);
|
||||
$minimum = (int) config('enterprise.health.minimum_free_disk_mb', 512);
|
||||
return $this->result('Storage', $freeMb < $minimum ? 'warning' : 'healthy', $freeMb.' MB free');
|
||||
} catch (Throwable $e) {
|
||||
return $this->result('Storage', 'failed', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function queue(): array
|
||||
{
|
||||
$failed = Schema::hasTable('failed_jobs') ? DB::table('failed_jobs')->count() : 0;
|
||||
$maximum = (int) config('enterprise.health.maximum_failed_jobs', 0);
|
||||
return $this->result('Queue', $failed > $maximum ? 'warning' : 'healthy', $failed.' failed jobs');
|
||||
}
|
||||
|
||||
private function environment(): array
|
||||
{
|
||||
$warnings = [];
|
||||
if (app()->environment('production') && config('app.debug')) $warnings[] = 'APP_DEBUG is enabled';
|
||||
if (! app()->environment('production')) $warnings[] = 'Not running in production';
|
||||
return $this->result('Environment', $warnings ? 'warning' : 'healthy', $warnings ? implode('; ', $warnings) : app()->environment());
|
||||
}
|
||||
|
||||
private function result(string $name, string $status, string $message): array
|
||||
{
|
||||
return compact('name', 'status', 'message');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user