clusev/app/Livewire/Terminal/Index.php

110 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 there is more than one server to filter between. */
private const SEARCH_THRESHOLD = 1;
/** '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,
// The host is always the local machine, so the tile shows just the login user.
'hostTarget' => $host?->username,
])->title($this->title());
}
}