refactor(terminal): host login targets the local machine only — drop the HOST/IP field
The "Clusev host" terminal is for the LOCAL machine Clusev runs on. Letting the operator point it at an arbitrary host/server makes no sense (that is what fleet servers are for) and the pre-filled host.docker.internal read as a confusing mandatory magic value. - the host is now FIXED to host.docker.internal server-side (HostShell::HOST), not a form field; the modal asks only for the login — user + password|key, and the port. - the rail tile sub-label shows just the login user (the host is implied/local). Browser-verified: modal has no HOST/IP field (user/port/auth/password only); saving shows the user on the tile; opening the host reaches the real host sshd (a wrong password yields an SSH auth error, proving the connection), zero console errors. 16 terminal tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/v1-foundation
parent
0ec9f3664a
commit
e1a886671b
|
|
@ -10,14 +10,16 @@ use Illuminate\Validation\ValidationException;
|
|||
use LivewireUI\Modal\ModalComponent;
|
||||
|
||||
/**
|
||||
* Form modal: configure the SSH login for the "Clusev host" terminal. Stores a single
|
||||
* HostCredential (encrypted) so the terminal sidecar can SSH into the actual host instead of
|
||||
* opening a shell in its own container. On edit, leaving the secret blank keeps the stored one.
|
||||
* The clear-text secret is never echoed back into the form.
|
||||
* Form modal: configure the SSH login for the "Clusev host" terminal. The target is ALWAYS the local
|
||||
* machine Clusev runs on (the Docker host, reached at host.docker.internal — fixed, not a free-form
|
||||
* host, because an arbitrary machine is what fleet servers are for). The operator only supplies the
|
||||
* login (user + password/key, and the port). Stores a single encrypted HostCredential; on edit a
|
||||
* blank secret keeps the stored one, and the clear-text secret is never echoed back into the form.
|
||||
*/
|
||||
class HostShell extends ModalComponent
|
||||
{
|
||||
public string $host = 'host.docker.internal';
|
||||
/** The host terminal always targets the local machine; not operator-editable. */
|
||||
private const HOST = 'host.docker.internal';
|
||||
|
||||
public int $port = 22;
|
||||
|
||||
|
|
@ -42,7 +44,6 @@ class HostShell extends ModalComponent
|
|||
$hc = HostCredential::current();
|
||||
if ($hc !== null) {
|
||||
$this->configured = true;
|
||||
$this->host = $hc->host;
|
||||
$this->port = $hc->port;
|
||||
$this->username = $hc->username;
|
||||
$this->authType = $hc->auth_type;
|
||||
|
|
@ -56,7 +57,6 @@ class HostShell extends ModalComponent
|
|||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'host' => ['required', 'string', 'max:255'],
|
||||
'port' => ['required', 'integer', 'min:1', 'max:65535'],
|
||||
'username' => ['required', 'string', 'max:120'],
|
||||
'authType' => ['required', Rule::in(['password', 'key'])],
|
||||
|
|
@ -73,7 +73,6 @@ class HostShell extends ModalComponent
|
|||
protected function validationAttributes(): array
|
||||
{
|
||||
return [
|
||||
'host' => __('terminal.host_field_host'),
|
||||
'port' => __('terminal.host_field_port'),
|
||||
'username' => __('terminal.host_field_user'),
|
||||
'authType' => __('terminal.host_field_auth'),
|
||||
|
|
@ -95,7 +94,7 @@ class HostShell extends ModalComponent
|
|||
}
|
||||
|
||||
$hc = $existing ?? new HostCredential;
|
||||
$hc->host = trim($data['host']);
|
||||
$hc->host = self::HOST; // always the local machine — not operator-editable
|
||||
$hc->port = (int) $data['port'];
|
||||
$hc->username = trim($data['username']);
|
||||
$hc->auth_type = $data['authType'] === 'key' ? 'key' : 'password';
|
||||
|
|
|
|||
|
|
@ -102,7 +102,8 @@ class Index extends Component
|
|||
'serverCount' => $serverCount,
|
||||
'showSearch' => $serverCount > self::SEARCH_THRESHOLD,
|
||||
'hostConfigured' => $host !== null,
|
||||
'hostTarget' => $host !== null ? $host->username.'@'.$host->host : null,
|
||||
// The host is always the local machine, so the tile shows just the login user.
|
||||
'hostTarget' => $host?->username,
|
||||
])->title($this->title());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ return [
|
|||
// Host-Login-Einrichtung (Clusev-Host-Terminal)
|
||||
'host_setup_title' => 'Clusev-Host-Login',
|
||||
'host_setup_subtitle' => 'SSH-Zugang zum Clusev-Host. Damit wird das Terminal ein echtes Login auf der Maschine — kein Container.',
|
||||
'host_field_host' => 'Host / IP',
|
||||
'host_field_host_hint' => 'Standard: die Maschine, auf der Clusev läuft (Docker-Host). So lassen — oder eine andere IP/Host eintragen.',
|
||||
'host_field_port' => 'Port',
|
||||
'host_field_user' => 'Benutzer',
|
||||
'host_field_auth' => 'Authentifizierung',
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ return [
|
|||
// Host login setup (Clusev host terminal)
|
||||
'host_setup_title' => 'Clusev host login',
|
||||
'host_setup_subtitle' => 'SSH access to the Clusev host. This makes the terminal a real login on the machine — not a container.',
|
||||
'host_field_host' => 'Host / IP',
|
||||
'host_field_host_hint' => 'Default: the machine Clusev runs on (the Docker host). Leave it — or enter another IP/host.',
|
||||
'host_field_port' => 'Port',
|
||||
'host_field_user' => 'User',
|
||||
'host_field_auth' => 'Authentication',
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-4 space-y-3">
|
||||
{{-- Target is fixed to the local machine (the Docker host) — only the login is asked for. --}}
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
<div class="sm:col-span-2">
|
||||
<label class="mb-1 block font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('terminal.host_field_host') }}</label>
|
||||
<input wire:model="host" type="text" placeholder="host.docker.internal"
|
||||
<label class="mb-1 block font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('terminal.host_field_user') }}</label>
|
||||
<input wire:model="username" type="text" placeholder="root"
|
||||
class="h-9 w-full rounded-md border border-line bg-inset px-3 font-mono text-sm text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none" />
|
||||
@error('host')<p class="mt-1 flex items-center gap-1.5 font-mono text-[11px] text-offline"><x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $message }}</p>@enderror
|
||||
<p class="mt-1.5 font-mono text-[11px] leading-relaxed text-ink-4">{{ __('terminal.host_field_host_hint') }}</p>
|
||||
@error('username')<p class="mt-1 flex items-center gap-1.5 font-mono text-[11px] text-offline"><x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $message }}</p>@enderror
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('terminal.host_field_port') }}</label>
|
||||
|
|
@ -26,13 +26,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1 block font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('terminal.host_field_user') }}</label>
|
||||
<input wire:model="username" type="text" placeholder="root"
|
||||
class="h-9 w-full rounded-md border border-line bg-inset px-3 font-mono text-sm text-ink placeholder:text-ink-4 focus:border-accent/40 focus:outline-none" />
|
||||
@error('username')<p class="mt-1 flex items-center gap-1.5 font-mono text-[11px] text-offline"><x-icon name="alert" class="h-3.5 w-3.5 shrink-0" />{{ $message }}</p>@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1 block font-mono text-[11px] uppercase tracking-wider text-ink-3">{{ __('terminal.host_field_auth') }}</label>
|
||||
<select wire:model.live="authType"
|
||||
|
|
|
|||
|
|
@ -178,7 +178,6 @@ class TerminalTest extends TestCase
|
|||
public function test_host_shell_modal_saves_the_singleton(): void
|
||||
{
|
||||
Livewire::test(HostShell::class)
|
||||
->set('host', '10.0.0.1')
|
||||
->set('port', 22)
|
||||
->set('username', 'root')
|
||||
->set('authType', 'password')
|
||||
|
|
@ -188,7 +187,7 @@ class TerminalTest extends TestCase
|
|||
|
||||
$hc = HostCredential::current();
|
||||
$this->assertNotNull($hc);
|
||||
$this->assertSame('10.0.0.1', $hc->host);
|
||||
$this->assertSame('host.docker.internal', $hc->host); // fixed to the local machine, not operator-set
|
||||
$this->assertSame('root', $hc->username);
|
||||
$this->assertSame('hunter2pw', $hc->secret); // round-trips through the encrypted cast
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue