109 lines
3.6 KiB
PHP
109 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Terminal;
|
|
|
|
use App\Models\HostCredential;
|
|
use App\Models\Server;
|
|
use App\Models\TerminalSession;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Str;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\On;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* Terminal page. Lists the targets (the Clusev host + each server) and, on selection, mints a
|
|
* single-use session token that the xterm.js island uses to open a WebSocket to the terminal
|
|
* sidecar. The component itself never touches the SSH connection or credentials — it only authorizes
|
|
* the operator and hands out a short-lived token (resolved server-side over the internal network).
|
|
*/
|
|
#[Layout('layouts.app')]
|
|
class Index extends Component
|
|
{
|
|
/** Show the server search box once the fleet is large enough to warrant filtering. */
|
|
private const SEARCH_THRESHOLD = 5;
|
|
|
|
/** 'host' or a server uuid — drives the active-target highlight in the rail. */
|
|
public string $activeKey = '';
|
|
|
|
/** Live filter over the server rail (only shown past SEARCH_THRESHOLD servers). */
|
|
public string $search = '';
|
|
|
|
public function title(): string
|
|
{
|
|
return __('terminal.title');
|
|
}
|
|
|
|
/** Mint a single-use token for the chosen target and hand it to the xterm island via an event. */
|
|
public function open(string $kind, ?string $uuid = null): void
|
|
{
|
|
$serverId = null;
|
|
$label = __('terminal.host_label');
|
|
$this->activeKey = 'host';
|
|
|
|
if ($kind === 'host') {
|
|
// The host terminal is a real SSH login into the Clusev machine. With no login configured
|
|
// yet, open the setup form instead of minting a dead session.
|
|
if (HostCredential::current() === null) {
|
|
$this->configureHost();
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
$kind = 'server';
|
|
$server = Server::where('uuid', $uuid)->first();
|
|
if ($server === null) {
|
|
return;
|
|
}
|
|
$serverId = $server->id;
|
|
$label = $server->name;
|
|
$this->activeKey = $server->uuid;
|
|
}
|
|
|
|
$token = Str::random(48);
|
|
TerminalSession::create([
|
|
'token' => $token,
|
|
'user_id' => Auth::id(),
|
|
'kind' => $kind,
|
|
'server_id' => $serverId,
|
|
'expires_at' => now()->addSeconds(60),
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$this->dispatch('terminal-open', token: $token, title: $label);
|
|
}
|
|
|
|
/** Open the modal that stores the Clusev host SSH login. */
|
|
public function configureHost(): void
|
|
{
|
|
$this->dispatch('openModal', component: 'modals.host-shell');
|
|
}
|
|
|
|
/** The host-login modal saved/removed — re-render so the tile reflects the new state. */
|
|
#[On('hostShellSaved')]
|
|
public function hostShellSaved(): void
|
|
{
|
|
// No state to set; the empty handler simply triggers a re-render.
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$serverCount = Server::count();
|
|
|
|
$servers = Server::query()
|
|
->when($this->search !== '', fn ($q) => $q->where('name', 'like', '%'.$this->search.'%'))
|
|
->orderBy('name')
|
|
->get(['uuid', 'name', 'status']);
|
|
|
|
$host = HostCredential::current();
|
|
|
|
return view('livewire.terminal.index', [
|
|
'servers' => $servers,
|
|
'serverCount' => $serverCount,
|
|
'showSearch' => $serverCount > self::SEARCH_THRESHOLD,
|
|
'hostConfigured' => $host !== null,
|
|
'hostTarget' => $host !== null ? $host->username.'@'.$host->host : null,
|
|
])->title($this->title());
|
|
}
|
|
}
|