Connexion
FR
games/xencenter-xenadmin-xcp-jumphp / src / Form / NewSrForm.php · commit fb1b7c9
<?phpnamespace App\Form;use App\Feature\Storage\Actions;use App\Gui\Units;use App\Model\Connection;use JumPHP\Gui\Button;use JumPHP\Gui\ComboBox;use JumPHP\Gui\Container;use JumPHP\Gui\Form;use JumPHP\Gui\Label;use JumPHP\Gui\TextBox;/** * The New Storage Repository wizard of XenCenter, on one page: the type, then where the storage is. What XAPI is asked * (Feature\Storage\Actions::createSr): SR.create(host, device_config, 0, name, description, type, content_type, shared, sm_config) * - NFS: {server, serverpath, nfsversion}, "nfs", "user", shared;   SMB: {server: //nas/share, username, password}, "smb"; * - ISO library: {location: server:/path | //nas/share (+ type cifs, username, cifspassword)}, "iso", "iso", sm_config iso_type; * - iSCSI: SR.probe(master, {target, port, targetIQN}) lists the LUNs (Scan), then {target, port, targetIQN, SCSIid}, "lvmoiscsi"; * - local EXT / LVM: {device: /dev/sdb} on the chosen server, not shared. * Closes with ['newSr' => [...]]. * * @property Label $where * @property ComboBox $kind * @property TextBox $name * @property TextBox $description * @property Container $nfsPanel * @property TextBox $nfsServer * @property TextBox $nfsPath * @property ComboBox $nfsVersion * @property Button $nfsScan * @property Label $nfsFound * @property Container $smbPanel * @property TextBox $smbServer * @property TextBox $smbUser * @property TextBox $smbPassword * @property Container $iscsiPanel * @property TextBox $iscsiTarget * @property TextBox $iscsiPort * @property TextBox $iscsiIqn * @property ComboBox $iscsiLun * @property Button $iscsiScan * @property Label $iscsiFound * @property Container $localPanel * @property ComboBox $localHost * @property TextBox $localDevice * @property Label $error * @property Button $create * @property Button $cancel */final class NewSrForm extends Form{    /** the types the wizard makes, and the name it proposes for each */    public const KINDS = ['nfs' => ['NFS', 'NFS virtual disk storage'], 'smb' => ['SMB / CIFS', 'SMB virtual disk storage'],        'iso-nfs' => ['ISO library (NFS)', 'NFS ISO library'], 'iso-smb' => ['ISO library (SMB / CIFS)', 'SMB ISO library'],        'iscsi' => ['iSCSI (software initiator)', 'iSCSI virtual disk storage'], 'ext' => ['Local EXT (on a device)', 'Local EXT storage'],        'lvm' => ['Local LVM (on a device)', 'Local LVM storage']];    public function __construct(private \App\Model\Session $session)    {        parent::__construct();    }    /** XAPI's answers (the scans), events and the console go on while this dialog is open. */    protected function tick(float $dt): void    {        $this->session->tick($dt);    }    private array $params = [];    /** @var list<array{lun: string, scsiId: string, size: float, vendor: string}> what the last iSCSI scan found */    public array $luns = [];    /** @return array<string, mixed> */    public static function paramsFor(Connection $c, ?string $host = null): array    {        $hosts = [];        foreach ($c->cache->hosts() as $ref => $h) {            $hosts[] = ['ref' => $ref, 'name' => (string)$h['name_label']];        }        return ['server' => $c->name(), 'pool' => (string)$c->cache->poolRef(), 'master' => (string)$c->cache->master(), 'hosts' => $hosts, 'host' => $host];    }    protected function loaded(array $params = []): void    {        $this->params = $params;        $this->luns = [];        $this->where->text = "In '" . ($params['server'] ?? '') . "'";        $this->kind->items = array_column(array_values(self::KINDS), 0);        $this->kind->selected = 0;        $this->localHost->items = array_column($params['hosts'] ?? [], 'name');        $at = array_search($params['host'] ?? null, array_column($params['hosts'] ?? [], 'ref'), true);        $this->localHost->selected = $at === false ? 0 : (int)$at;        foreach ([$this->name, $this->description, $this->nfsServer, $this->nfsPath, $this->smbServer, $this->smbUser, $this->smbPassword, $this->iscsiTarget,            $this->iscsiIqn, $this->localDevice] as $box) {            $box->text = '';        }        $this->iscsiPort->text = '3260';        $this->iscsiLun->items = [];        $this->iscsiLun->selected = -1;        $this->nfsVersion->selected = 0;        $this->nfsFound->text = $this->iscsiFound->text = $this->error->text = '';        $this->onKindChange($this->kind, 0, '');        $this->setFocus($this->name);    }    public function kind(): string    {        return array_keys(self::KINDS)[(int)$this->kind->selected] ?? 'nfs';    }    public function onKindChange(ComboBox $sender, int $index, string $item): void    {        $kind = $this->kind();        $this->nfsPanel->visible = $kind === 'nfs' || $kind === 'iso-nfs';        $this->smbPanel->visible = $kind === 'smb' || $kind === 'iso-smb';        $this->iscsiPanel->visible = $kind === 'iscsi';        $this->localPanel->visible = $kind === 'ext' || $kind === 'lvm';        // the proposed name follows the type until the user writes one        $defaults = array_column(array_values(self::KINDS), 1);        if ($this->name->text === '' || in_array($this->name->text, $defaults, true)) {            $this->name->text = self::KINDS[$kind][1];        }        $this->error->text = '';    }    public function onNfsScanClick(Button $sender): void    {        $c = $this->connection();        $server = trim($this->nfsServer->text);        if ($c === null || $server === '') {            $this->nfsFound->text = 'Enter the address of the NFS server first.';            return;        }        $this->nfsFound->text = 'Scanning…';        $this->error->text = '';        Actions::probe($c, (string)$this->params['master'], ['server' => $server], 'nfs')->then(function (string $xml): void {            $paths = Actions::values($xml, 'Path');            $this->nfsFound->text = $paths === [] ? 'No export found.' : 'Exports: ' . implode(', ', $paths);            if ($paths !== [] && trim($this->nfsPath->text) === '') {                $this->nfsPath->text = $paths[0];            }        }, fn (\Throwable $e) => $this->nfsFound->text = 'Scan failed: ' . $e->getMessage());    }    /** SR.probe on the pool master: the target's IQN (when none is typed), then its LUNs. */    public function onIscsiScanClick(Button $sender): void    {        $c = $this->connection();        $target = trim($this->iscsiTarget->text);        if ($c === null || $target === '') {            $this->iscsiFound->text = 'Enter the address of the iSCSI target first.';            return;        }        $this->iscsiFound->text = 'Scanning…';        $this->error->text = '';        $dc = array_filter(['target' => $target, 'port' => trim($this->iscsiPort->text) ?: '3260', 'targetIQN' => trim($this->iscsiIqn->text)], fn ($v) => $v !== '');        Actions::probe($c, (string)$this->params['master'], $dc, 'lvmoiscsi')->then(function (string $xml) use ($sender): void {            $iqns = Actions::values($xml, 'TargetIQN');            if (trim($this->iscsiIqn->text) === '' && $iqns !== []) {                $this->iscsiIqn->text = $iqns[0];                $this->onIscsiScanClick($sender);     // and now its LUNs                return;            }            $this->luns = Actions::luns($xml);            $this->iscsiLun->items = array_map(fn ($l) => 'LUN ' . $l['lun'] . ': ' . Units::bytes($l['size']) . ' (' . $l['vendor'] . ')', $this->luns);            $this->iscsiLun->selected = $this->luns === [] ? -1 : 0;            $this->iscsiFound->text = $this->luns !== [] ? count($this->luns) . ' LUN(s) found.'                : ($iqns !== [] ? 'Unknown IQN: this target is ' . implode(', ', $iqns) . '.' : 'No LUN on this target.');        }, fn (\Throwable $e) => $this->iscsiFound->text = 'Scan failed: ' . $e->getMessage());    }    private function connection(): ?Connection    {        return $this->session->connectionOf((string)($this->params['master'] ?? ''));    }    public function onCreateClick(mixed $sender = null): void    {        $kind = $this->kind();        $name = trim($this->name->text);        if ($name === '') {            $this->error->text = 'Enter a name for the storage repository.';            return;        }        $master = (string)($this->params['master'] ?? '');        $spec = ['pool' => $this->params['pool'] ?? '', 'host' => $master, 'name' => $name, 'description' => trim($this->description->text), 'shared' => true,            'content' => 'user', 'smConfig' => []];        switch ($kind) {            case 'nfs':            case 'iso-nfs':                $server = trim($this->nfsServer->text);                $path = trim($this->nfsPath->text);                if ($server === '' || !str_starts_with($path, '/')) {                    $this->error->text = 'Enter the NFS server and its export path (it starts with /).';                    return;                }                $version = (string)($this->nfsVersion->value() ?? '3');                $spec += $kind === 'nfs'                    ? ['type' => 'nfs', 'deviceConfig' => ['server' => $server, 'serverpath' => $path, 'nfsversion' => $version]]                    : ['type' => 'iso', 'deviceConfig' => ['location' => "$server:$path", 'nfsversion' => $version]];                if ($kind === 'iso-nfs') {                    $spec['content'] = 'iso';                    $spec['smConfig'] = ['iso_type' => 'nfs_iso'];                }                break;            case 'smb':            case 'iso-smb':                $share = str_replace('\\', '/', trim($this->smbServer->text));                if (!preg_match('#^//[^/]+/.+#', $share)) {                    $this->error->text = 'Enter the share as //server/share.';                    return;                }                $user = trim($this->smbUser->text);                $spec += $kind === 'smb'                    ? ['type' => 'smb', 'deviceConfig' => array_filter(['server' => $share, 'username' => $user, 'password' => $this->smbPassword->text], fn ($v) => $v !== '')]                    : ['type' => 'iso', 'deviceConfig' => array_filter(['location' => $share, 'type' => 'cifs', 'username' => $user, 'cifspassword' => $this->smbPassword->text], fn ($v) => $v !== '')];                if ($kind === 'iso-smb') {                    $spec['content'] = 'iso';                    $spec['smConfig'] = ['iso_type' => 'cifs'];                }                break;            case 'iscsi':                $lun = $this->luns[(int)$this->iscsiLun->selected] ?? null;                if ($lun === null) {                    $this->error->text = 'Scan the target, then choose a LUN.';                    return;                }                $spec += ['type' => 'lvmoiscsi', 'deviceConfig' => ['target' => trim($this->iscsiTarget->text), 'port' => trim($this->iscsiPort->text) ?: '3260',                    'targetIQN' => trim($this->iscsiIqn->text), 'SCSIid' => $lun['scsiId']]];                break;            default:                $device = trim($this->localDevice->text);                if (!str_starts_with($device, '/dev/')) {                    $this->error->text = 'Enter the device of the server (/dev/sdb…).';                    return;                }                $spec['host'] = (string)($this->params['hosts'][(int)$this->localHost->selected]['ref'] ?? $master);                $spec['shared'] = false;                $spec += ['type' => $kind, 'deviceConfig' => ['device' => $device]];        }        $this->close(['newSr' => $spec]);    }    public function onCancelClick(Button $sender): void    {        $this->close();    }}