<?phpnamespace App\Form;use App\Model\LocalNetwork;use App\Model\ServerProfile;use JumPHP\Gui\Button;use JumPHP\Gui\CheckBox;use JumPHP\Gui\ComboBox;use JumPHP\Gui\Container;use JumPHP\Gui\Form;use JumPHP\Gui\Label;use JumPHP\Gui\TextBox;use JumPHP\Kernel\Platform;use JumPHP\Native\FilePicker;use JumPHP\Native\Native;/** * The Add New Server dialog: the server, how to reach it (HTTP, HTTPS on the desktop, an SSH tunnel), the account. * Closes with ['profile' => ServerProfile]: MainForm adds it and connects. * * @property TextBox $host * @property ComboBox $method * @property Label $portLabel * @property TextBox $port * @property TextBox $user * @property TextBox $password * @property Container $sshPanel * @property TextBox $sshUser * @property TextBox $sshPort * @property Button $chooseKey * @property Label $keyState * @property CheckBox $remember * @property Label $hint * @property Container $localPanel * @property Label $localWarning * @property Button $localFix * @property Label $error * @property Button $demo * @property Button $add * @property Button $cancel */final class AddServerForm extends Form{ public function __construct(private \App\Model\Session $session) { parent::__construct(); } /** XAPI's answers, events and the console go on while this dialog is open. */ protected function tick(float $dt): void { $this->session->tick($dt); $this->showLocalProblem(); } /** * Browser + a local address (192.168.x.x, xcp.lan...) + the local network off: the JumPHP server cannot reach it, the runtime is * required. Shown under the fields as soon as the address is typed, with the button that fixes it. Returns the problem. */ public function showLocalProblem(): ?string { $host = (string)preg_replace('#^https?://#', '', rtrim(trim($this->host->text), '/')); $problem = LocalNetwork::wasm() ? LocalNetwork::check($host, $this->method()) : null; $this->localPanel->visible = $problem !== null; if ($problem !== null) { $this->localWarning->text = $problem; $this->localFix->text = LocalNetwork::buttonText($this->engine->runtime()->state()); } return $problem; } public function onLocalFixClick(mixed $sender = null): void { LocalNetwork::fix(); $this->showLocalProblem(); } /** @var list<string> the methods in the order of the list */ private array $methods = []; private string $key = ''; protected function loaded(array $params = []): void { $this->methods = array_values(array_filter(['http', 'https', 'ssh'], fn ($m) => $m !== 'https' || !Platform::isWasm())); $this->method->items = array_map(fn ($m) => ServerProfile::METHODS[$m], $this->methods); $this->method->selected = 0; $this->key = ''; $this->error->text = ''; $this->localPanel->visible = false; $this->host->text = (string)($params['host'] ?? ''); $this->onMethodChange($this->method, 0, ''); $this->setFocus($this->host); } public function method(): string { return $this->methods[(int)$this->method->selected] ?? 'http'; } public function onMethodChange(ComboBox $sender, int $index, string $item): void { $method = $this->method(); $this->sshPanel->visible = $method === 'ssh'; $this->portLabel->text = $method === 'ssh' ? 'XAPI port:' : 'Port:'; $this->port->placeholder = $method === 'https' ? '443' : '80'; $this->hint->text = match ($method) { 'http' => Platform::isWasm() ? 'In a browser the connection leaves from your own network when the JumPHP runtime runs on this computer, else through the JumPHP server (public addresses only: a local address such as 192.168.x.x needs the runtime).' : 'Plain HTTP on port 80: the password crosses the network unencrypted. Prefer HTTPS or the SSH tunnel outside a trusted network.', 'https' => 'HTTPS on port 443. The server\'s certificate is accepted as XenCenter does on a first connection (XCP-ng servers have self-signed ones).', default => Platform::isWasm() ? 'OpenSSH runs in your browser (WebAssembly): the key never leaves it.' : 'The ssh program of this computer is used (your ~/.ssh and agent work too).', }; } public function onHostSubmit(TextBox $sender, string $text): void { $this->setFocus($this->user->text === '' ? $this->user : $this->password); } public function onChooseKeyClick(Button $sender): void { if (!Native::supports('open')) { $this->keyState->text = 'No file chooser here: use the password.'; return; } FilePicker::open(['title' => 'SSH private key'])->then(function (?string $file): void { if ($file === null) { return; } $text = (string)@file_get_contents($file); if (Platform::isWasm()) { @unlink($file); } if (!str_contains($text, 'PRIVATE KEY')) { $this->keyState->text = 'This is not a private key.'; return; } $this->key = Platform::isWasm() ? $text : $file; $this->keyState->text = 'Key: ' . basename($file); }); } public function onDemoClick(Button $sender): void { $this->close(['demo' => true]); } public function onAddClick(mixed $sender = null): void { $host = trim($this->host->text); if ($host === '' || preg_match('/\s/', $host)) { $this->error->text = 'Enter the address of the server.'; $this->setFocus($this->host); return; } $host = (string)preg_replace('#^https?://#', '', rtrim($host, '/')); $port = trim($this->port->text); if ($port !== '' && (!ctype_digit($port) || (int)$port < 1 || (int)$port > 65535)) { $this->error->text = 'The port is a number from 1 to 65535.'; return; } if ($this->showLocalProblem() !== null) { $this->error->text = 'This server is on your local network: the JumPHP runtime is required (see above).'; return; } if ($this->user->text === '') { $this->error->text = 'Enter a user name.'; return; } $this->close(['profile' => new ServerProfile($host, trim($this->user->text), $this->password->text, $this->method(), (int)$port, $this->remember->checked, trim($this->sshUser->text) ?: 'root', (int)($this->sshPort->text ?: 22), $this->key)]); } public function onCancelClick(Button $sender): void { $this->close(); }}