UK
games/xencenter-xenadmin-xcp-jumphp / src / View / Commands.php · commit fb1b7c9
<?phpnamespace App\View;use App\Model\Cache;use App\Feature\FeatureCommands;use App\Model\Connection;/** * What can be done to the selected object, as XenCenter's menus offer it: the right click's menu, the Server / VM / Storage... * menus of the menu bar and the toolbar's buttons all ask here, so they agree. An item: ['id' => 'vm.start', 'text' => 'Start', * 'enabled' => bool, 'icon' => ..., 'shortcut' => ...] or '-'. */final class Commands{    /** @return list<array<string, mixed>|string> the right click's menu */    public static function context(?Connection $c, string $ref, string $kind): array    {        // + New Storage / New Network, SR actions, cross-pool migration (src/Feature/Storage)        return \App\Feature\Storage\Menus::context($c, $ref, $kind, match ($kind) {            'root' => [self::item('server.add', 'Add…', true, 'add-server'), self::item('server.connectAll', 'Connect All'), self::item('server.disconnectAll', 'Disconnect All'),                ...\App\Feature\Pools\PoolCommands::rootItems()],            'disconnected' => [self::item('server.connect', 'Connect', $c?->state !== Connection::CONNECTING), self::item('server.remove', 'Remove from XenCenter')],            'pool' => [self::item('vm.new', 'New VM…', true, 'new-vm'), '-', ...\App\Feature\Pools\PoolCommands::poolItems($c), self::item('server.disconnect', 'Disconnect'), self::item('server.reconnect', 'Reconnect'),                self::item('server.remove', 'Remove from XenCenter'), '-', ...FeatureCommands::poolItems($c, $ref), '-', self::item('object.properties', 'Properties')],            'host' => self::hostItems($c, $ref),            'vm' => self::vmItems($c, $ref),            'template' => [self::item('template.newVm', 'New VM wizard…', true, 'new-vm'), '-', self::item('template.delete', 'Delete Template…', empty($c?->cache->field($ref, 'is_default_template'))),                '-', self::item('object.properties', 'Properties')],            'snapshot' => [self::item('snapshot.revert', 'Revert To…'), self::item('snapshot.delete', 'Delete Snapshot…')],            'sr' => [self::item('sr.rescan', 'Rescan'), '-', ...FeatureCommands::srItems(), self::item('object.properties', 'Properties')],            'network' => [self::item('object.properties', 'Properties')],            default => [],        });    }    /** A demo pool is in the tree (File > Remove the Demo Pool). */    public static function hasDemo(): bool    {        foreach (\App\Model\Session::$current->connections ?? [] as $c) {            if ($c->profile->method === 'demo') {                return true;            }        }        return false;    }    /** @return list<array<string, mixed>|string> */    public static function hostItems(?Connection $c, string $host): array    {        $r = $c?->cache->get($host) ?? [];        $enabled = (bool)($r['enabled'] ?? true);        $busy = $c !== null && $c->cache->vmsOn($host) !== [];        return [            self::item('vm.new', 'New VM…', true, 'new-vm'),            '-',            self::item('host.maintenance', $enabled ? 'Enter Maintenance Mode…' : 'Exit Maintenance Mode'),            self::item('host.reboot', 'Reboot', !$busy, 'reboot'),            self::item('host.shutdown', 'Shut Down', !$busy, 'power-off'),            ...\App\Feature\Pools\PoolCommands::hostItems($c, $host),            '-',            ...FeatureCommands::hostItems($c, $host),            '-',            self::item('server.disconnect', 'Disconnect'),            self::item('object.properties', 'Properties'),        ];    }    /** @return list<array<string, mixed>|string> */    public static function vmItems(?Connection $c, string $vm): array    {        $r = $c?->cache->get($vm) ?? [];        $state = (string)($r['power_state'] ?? '');        $running = $state === 'Running';        $items = [            self::item('vm.start', 'Start', $state === 'Halted', 'power-on'),            self::item('vm.shutdown', 'Shut Down', $running, 'power-off'),            self::item('vm.reboot', 'Reboot', $running, 'reboot'),            self::item('vm.suspend', 'Suspend', $running, 'suspend'),            self::item('vm.resume', 'Resume', $state === 'Suspended', 'resume'),            self::item($state === 'Paused' ? 'vm.unpause' : 'vm.pause', $state === 'Paused' ? 'Unpause' : 'Pause', $running || $state === 'Paused'),            '-',            self::item('vm.forceShutdown', 'Force Shut Down', $running || $state === 'Paused'),            self::item('vm.forceReboot', 'Force Reboot', $running),            '-',        ];        if ($c !== null) {            foreach ($c->cache->hosts() as $host => $h) {                if ($running && $host !== ($r['resident_on'] ?? '') && ($h['enabled'] ?? true)) {                    $items[] = self::item('vm.migrate:' . $host, 'Migrate to ' . $h['name_label'], true, 'migrate');                } elseif ($state === 'Halted' && ($h['enabled'] ?? true)) {                    $items[] = self::item('vm.startOn:' . $host, 'Start on ' . $h['name_label'], true, 'power-on');                }            }        }        return [...$items, '-',            self::item('vm.snapshot', 'Take a Snapshot…', true, 'snapshot'),            self::item('vm.copy', 'Copy VM…', $state === 'Halted'),            self::item('vm.template', 'Convert to Template…', $state === 'Halted'),            self::item('vm.delete', 'Delete VM…', $state === 'Halted'),            '-',            ...FeatureCommands::vmItems($c, $vm),            self::item('object.properties', 'Properties'),        ];    }    /** @return list<array<string, mixed>|string> a menu of the menu bar */    public static function menu(string $menu, ?Connection $c, string $ref, string $kind, array $view): array    {        $connected = $c !== null && $c->isConnected();        return \App\Feature\Storage\Menus::menu($menu, $c, $ref, $kind, match ($menu) {            'File' => [self::item('server.add', 'Add New Server…', true, 'add-server'), self::item('server.demo', 'Open the Demo Pool'),                self::item('server.removeDemo', 'Remove the Demo Pool', self::hasDemo()), ...FeatureCommands::fileItems($c), '-',                self::item('server.connect', 'Connect', $c !== null && !$connected), self::item('server.disconnect', 'Disconnect', $connected),                self::item('server.remove', 'Remove from XenCenter', $c !== null), '-', self::item('app.exit', 'Exit', \JumPHP\Kernel\Platform::isDesktop())],            'View' => [self::check('view.infrastructure', 'Infrastructure', $view['mode'] === 'infrastructure'), self::check('view.objects', 'Objects', $view['mode'] === 'objects'),                self::check('view.notifications', 'Notifications', $view['mode'] === 'notifications'),                self::check('view.folders', 'Folders', $view['mode'] === 'folders'), self::check('view.tags', 'Tags', $view['mode'] === 'tags'), '-',                self::check('view.templates', 'Show Templates', $view['templates']), self::check('view.networks', 'Show Networks', $view['networks']), '-',                self::item('view.refresh', 'Refresh', $connected)],            'Pool' => \App\Feature\Pools\PoolCommands::poolMenu($c, $ref, $kind),            'Server' => $kind === 'host' ? self::hostItems($c, $ref) : [self::item('server.add', 'Add…', true, 'add-server'), self::item('server.connectAll', 'Connect All'), self::item('server.disconnectAll', 'Disconnect All')],            'VM' => $kind === 'vm' ? self::vmItems($c, $ref) : [self::item('vm.new', 'New VM…', $connected, 'new-vm')],            'Storage' => $kind === 'sr' ? self::context($c, $ref, 'sr') : [self::item('sr.none', 'Select a storage repository first', false)],            'Templates' => [self::item('template.newVm', 'New VM wizard…', $kind === 'template', 'new-vm'), self::check('view.templates', 'Show Templates', $view['templates'])],            'Tools' => [self::check('tools.darkTitle', 'Compact toolbar', $view['compact'] ?? false), '-', self::item('tools.masterPassword', 'Master Password…')],            'Help' => [self::item('help.about', 'About XenCenter (JumPHP)…'), self::item('help.xcp', 'XCP-ng documentation')],            default => [],        });    }    /** The toolbar: what each button does on this selection, and whether it can. @return array<string, array{text: string, enabled: bool, id: string}> */    public static function toolbar(?Connection $c, string $ref, string $kind): array    {        $connected = $c !== null && $c->isConnected();        $state = $kind === 'vm' ? (string)$c?->cache->field($ref, 'power_state', '') : '';        $host = $kind === 'host';        // a server reboots / shuts down only with no VM running on it (maintenance mode moves them away first)        $hostIdle = $host && $c !== null && $c->cache->vmsOn($ref) === [];        return [            'shutDown' => ['id' => $host ? 'host.shutdown' : 'vm.shutdown', 'text' => 'Shut Down', 'enabled' => $state === 'Running' || $hostIdle],            'reboot' => ['id' => $host ? 'host.reboot' : 'vm.reboot', 'text' => 'Reboot', 'enabled' => $state === 'Running' || $hostIdle],            'startResume' => $state === 'Suspended' ? ['id' => 'vm.resume', 'text' => 'Resume', 'enabled' => true] : ['id' => 'vm.start', 'text' => 'Start', 'enabled' => $state === 'Halted'],            'suspend' => ['id' => 'vm.suspend', 'text' => 'Suspend', 'enabled' => $state === 'Running'],            'newVm' => ['id' => 'vm.new', 'text' => 'New VM', 'enabled' => $connected],        ] + \App\Feature\Storage\Menus::toolbar($c);    }    /** @return array<string, mixed> */    public static function item(string $id, string $text, bool $enabled = true, string $icon = '', ?string $shortcut = null): array    {        return array_filter(['id' => $id, 'text' => $text, 'enabled' => $enabled, 'icon' => $icon, 'shortcut' => $shortcut], fn ($v) => $v !== null && $v !== '');    }    /** @return array<string, mixed> */    private static function check(string $id, string $text, bool $checked): array    {        return ['id' => $id, 'text' => $text, 'enabled' => true, 'checked' => $checked];    }    /** The XAPI call of a power command: [title, method, extra params]. */    public static function power(string $command): ?array    {        return match ($command) {            'vm.start' => ['Starting VM', 'VM.start', [false, false]],            'vm.shutdown' => ['Shutting down VM', 'VM.clean_shutdown', []],            'vm.forceShutdown' => ['Forcing VM shut down', 'VM.hard_shutdown', []],            'vm.reboot' => ['Rebooting VM', 'VM.clean_reboot', []],            'vm.forceReboot' => ['Forcing VM reboot', 'VM.hard_reboot', []],            'vm.suspend' => ['Suspending VM', 'VM.suspend', []],            'vm.resume' => ['Resuming VM', 'VM.resume', [false, false]],            'vm.pause' => ['Pausing VM', 'VM.pause', []],            'vm.unpause' => ['Unpausing VM', 'VM.unpause', []],            default => null,        };    }    /** Commands XenCenter asks a confirmation for: the question. */    public static function confirmation(string $command, string $name, ?Cache $cache = null): ?string    {        return match ($command) {            'vm.shutdown' => "Are you sure you want to shut down VM '$name'?",            'vm.forceShutdown' => "Are you sure you want to force VM '$name' to shut down?\n\nThis is like pulling the plug: unsaved work in the VM is lost.",            'vm.reboot' => "Are you sure you want to reboot VM '$name'?",            'vm.forceReboot' => "Are you sure you want to force VM '$name' to reboot?\n\nThis is like pressing the reset button: unsaved work is lost.",            'vm.suspend' => "Are you sure you want to suspend VM '$name'?",            'vm.delete' => "Are you sure you want to delete VM '$name'?\n\nIts virtual disks are deleted with it. This cannot be undone.",            'template.delete' => "Are you sure you want to delete the template '$name'?",            'snapshot.delete' => "Are you sure you want to delete the snapshot '$name'?",            'snapshot.revert' => "Reverting to snapshot '$name' discards the current state of the VM.\n\nDo you want to revert?",            'host.reboot' => "Are you sure you want to reboot server '$name'?",            'host.shutdown' => "Are you sure you want to shut down server '$name'?\n\nYou will have to power it on again by hand (or by IPMI).",            'host.maintenance' => $cache !== null && !$cache->field((string)$cache->byName('host', $name), 'enabled', true) ? null                : "Put server '$name' into maintenance mode?\n\nIts running VMs are migrated to the other servers of the pool.",            'vm.template' => "Convert VM '$name' to a template?\n\nA template cannot be started; new VMs are made from it. This cannot be undone here.",            'server.remove' => "Remove '$name' from XenCenter?\n\nNothing changes on the server.",            'server.removeDemo' => "Remove the demo pool from XenCenter?\n\nFile > Open the Demo Pool brings it back.",            default => \App\Feature\Pools\PoolCommands::confirmation($command, $name, $cache),        };    }}