107 lines
3.2 KiB
PHP
107 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Docker;
|
|
|
|
use App\Livewire\Concerns\WithFleetContext;
|
|
use App\Models\AuditEvent;
|
|
use App\Services\DockerService;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use InvalidArgumentException;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Container management for the active fleet server (agentless over SSH). Viewing the list is open
|
|
* to any role; container actions (start/stop/restart/pause) require `operate`, matching how systemd
|
|
* service actions are gated. Loads lazily via wire:init like Files/Services.
|
|
*/
|
|
#[Layout('layouts.app')]
|
|
class Index extends Component
|
|
{
|
|
use WithFleetContext;
|
|
|
|
/** @var array<int, array<string, string>> */
|
|
public array $containers = [];
|
|
|
|
public bool $connected = false;
|
|
|
|
public bool $ready = false;
|
|
|
|
public function title(): string
|
|
{
|
|
return __('docker.title');
|
|
}
|
|
|
|
public function load(DockerService $docker): void
|
|
{
|
|
$this->containers = [];
|
|
$this->connected = false;
|
|
|
|
$active = $this->activeServer();
|
|
if ($active && $active->credential_exists) {
|
|
try {
|
|
$this->containers = $docker->containers($active);
|
|
$this->connected = true;
|
|
} catch (Throwable) {
|
|
$this->connected = false;
|
|
}
|
|
}
|
|
|
|
$this->ready = true;
|
|
}
|
|
|
|
public function action(string $id, string $op, DockerService $docker): void
|
|
{
|
|
abort_unless(Auth::user()?->can('operate'), 403);
|
|
|
|
$active = $this->activeServer();
|
|
if (! $active || ! $active->credential_exists) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$res = $docker->containerAction($active, $id, $op);
|
|
AuditEvent::create([
|
|
'user_id' => Auth::id(),
|
|
'server_id' => $active->id,
|
|
'actor' => Auth::user()?->name ?? 'system',
|
|
'action' => 'docker.action',
|
|
'target' => "{$op} {$id} · {$active->name}",
|
|
'ip' => request()->ip(),
|
|
]);
|
|
$this->dispatch('notify', message: $res['ok']
|
|
? __('docker.action_ok', ['op' => $op, 'ref' => $id])
|
|
: __('docker.action_failed', ['error' => $res['output']]));
|
|
} catch (InvalidArgumentException) {
|
|
$this->dispatch('notify', message: __('docker.invalid_ref'));
|
|
}
|
|
|
|
$this->load($docker);
|
|
}
|
|
|
|
/** Open the read-only logs modal for a container (the ref is re-validated in DockerService). */
|
|
public function viewLogs(string $id): void
|
|
{
|
|
// Container logs can contain secrets (env dumps, tokens in traces), so reading them is an
|
|
// operate action — consistent with gating file CONTENT reads. The list itself stays open.
|
|
abort_unless(Auth::user()?->can('operate'), 403);
|
|
|
|
$active = $this->activeServer();
|
|
if (! $active) {
|
|
return;
|
|
}
|
|
|
|
$this->dispatch('openModal', component: 'modals.container-logs', arguments: [
|
|
'serverId' => $active->id,
|
|
'ref' => $id,
|
|
]);
|
|
}
|
|
|
|
public function render(): View
|
|
{
|
|
return view('livewire.docker.index')->title($this->title());
|
|
}
|
|
}
|