64 lines
1.9 KiB
PHP
64 lines
1.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Posture;
|
|
|
|
use App\Models\Server;
|
|
use App\Services\PostureService;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Fleet security-posture scoreboard. Scores each active-credential server from its hardening state
|
|
* (PostureService). Reading a server's posture exposes its hardening GAPS, so the page is
|
|
* `operate`-gated (route + mount). The scan is lazy (wire:init) and per-server guarded — one
|
|
* unreachable host shows an error row instead of blocking the whole scan.
|
|
*/
|
|
#[Layout('layouts.app')]
|
|
class Index extends Component
|
|
{
|
|
/** @var array<string, array<string, mixed>> server uuid → score result (or ['error' => msg]) */
|
|
public array $scores = [];
|
|
|
|
public bool $ready = false;
|
|
|
|
public function mount(): void
|
|
{
|
|
abort_unless(Auth::user()?->can('operate'), 403);
|
|
}
|
|
|
|
public function title(): string
|
|
{
|
|
return __('posture.title');
|
|
}
|
|
|
|
public function scan(PostureService $posture): void
|
|
{
|
|
abort_unless(Auth::user()?->can('operate'), 403);
|
|
|
|
$this->scores = [];
|
|
foreach (Server::withActiveCredential()->orderBy('name')->get() as $server) {
|
|
try {
|
|
$this->scores[$server->uuid] = $posture->score($server);
|
|
} catch (Throwable $e) {
|
|
$this->scores[$server->uuid] = ['error' => $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
$this->ready = true;
|
|
}
|
|
|
|
public function render(): View
|
|
{
|
|
$scored = array_filter($this->scores, fn ($s) => isset($s['score']));
|
|
$avg = $scored !== [] ? (int) round(array_sum(array_column($scored, 'score')) / count($scored)) : null;
|
|
|
|
return view('livewire.posture.index', [
|
|
'servers' => Server::withActiveCredential()->orderBy('name')->get(['uuid', 'name']),
|
|
'average' => $avg,
|
|
])->title($this->title());
|
|
}
|
|
}
|