<?phpnamespace App\Feature;use App\Feature\Updates\RollingUpdate;use App\Feature\Updates\Updates;use App\Feature\Xva\XvaTransfers;use App\Form\AlertsForm;use App\Form\ConfirmForm;use App\Form\ExportForm;use App\Form\ImportForm;use App\Form\MainForm;use App\Model\Connection;use App\View\Commands;use JumPHP\Kernel\Engine;/** * The commands of the updates, the XVA export / import and the alerts, kept apart from View\Commands and Form\MainForm (which only * call here): their menu items, their confirmations, what they do, what their dialogs answer. * updates.check Check for Updates (a pool: every server; a server: it) * updates.install Install Updates… on one server * updates.rolling Rolling Pool Update… (master first, maintenance mode, update, reboot, enable) * vm.export Export… (a halted VM, an XVA file) * file.import Import… (an XVA file into the pool) * object.alerts Alerts… (perfmon alarms of a VM / server / SR, e-mail of a pool) */final class FeatureCommands{ public const IDS = ['updates.check', 'updates.install', 'updates.rolling', 'vm.export', 'file.import', 'object.alerts']; // menu items --------------------------------------------------------------------------------------------------------------- /** @return list<array<string, mixed>|string> */ public static function vmItems(?Connection $c, string $vm): array { $halted = ($c?->cache->field($vm, 'power_state', '') ?? '') === 'Halted'; return [Commands::item('vm.export', 'Export…', $halted), Commands::item('object.alerts', 'Alerts…')]; } /** @return list<array<string, mixed>|string> */ public static function hostItems(?Connection $c, string $host): array { $pending = $c !== null ? Updates::pending($c, $host) : 0; return [Commands::item('updates.check', 'Check for Updates'), Commands::item('updates.install', $pending > 0 ? "Install $pending Updates…" : 'Install Updates…', $pending > 0), Commands::item('object.alerts', 'Alerts…')]; } /** @return list<array<string, mixed>|string> */ public static function poolItems(?Connection $c, string $pool): array { return [Commands::item('updates.check', 'Check for Updates'), Commands::item('updates.rolling', 'Rolling Pool Update…', $c !== null && RollingUpdate::order($c) !== []), Commands::item('file.import', 'Import…'), Commands::item('object.alerts', 'E-mail Alerts…')]; } /** @return list<array<string, mixed>|string> */ public static function srItems(): array { return [Commands::item('object.alerts', 'Alerts…')]; } /** @return list<array<string, mixed>|string> */ public static function fileItems(?Connection $c): array { return [Commands::item('file.import', 'Import…', $c !== null && $c->isConnected())]; } // running them ------------------------------------------------------------------------------------------------------------- public static function confirmation(string $id, Connection $c, string $target): ?string { $name = $c->cache->name($target); return match ($id) { 'updates.install' => 'Install ' . Updates::pending($c, $target) . " updates on server '$name'?\n\n" . (Updates::needsReboot(Updates::of($c, $target)->packages ?? []) ? "A new kernel, Xen or XAPI is among them: the server needs a reboot afterwards (move its VMs away first, or use the Rolling Pool Update)." : 'Its VMs keep running.'), 'updates.rolling' => "Update the servers of '" . $c->name() . "' one after the other?\n\n" . implode(', ', array_map(fn ($h) => $c->cache->name($h), RollingUpdate::order($c))) . "\n\nThe pool master first. Each is put into maintenance mode (its VMs migrate to the others), updated, rebooted if needed, then enabled again.", default => null, }; } /** One of these commands from MainForm::command(): true when it was one (done, or a dialog opened). */ public static function run(MainForm $form, string $id, ?Connection $c, string $target, bool $confirmed): bool { if (!in_array($id, self::IDS, true)) { return false; } if ($c === null || !$c->isConnected()) { return true; } $class = (string)$c->cache->classOf($target); if (!$confirmed && ($question = self::confirmation($id, $c, $id === 'updates.rolling' ? (string)$c->cache->poolRef() : $target)) !== null) { Engine::get()->scenes->push(ConfirmForm::class, ['message' => $question, 'command' => $id, 'target' => $target, 'ok' => $id === 'updates.rolling' ? 'Update' : 'Install']); return true; } switch ($id) { case 'updates.check': Updates::check($c, $class === 'host' ? $target : null); break; case 'updates.install': if ($class === 'host') { Updates::install($c, $target)->then(fn () => null, fn () => null); } break; case 'updates.rolling': RollingUpdate::run($form->session, $c)->then(fn () => null, fn () => null); break; case 'vm.export': Engine::get()->scenes->push(ExportForm::class, ExportForm::paramsFor($c, $target)); break; case 'file.import': Engine::get()->scenes->push(ImportForm::class, ImportForm::paramsFor($c, $class === 'host' ? $target : null)); break; case 'object.alerts': $ref = in_array($class, ['vm', 'host', 'sr', 'pool'], true) ? $target : (string)$c->cache->poolRef(); Engine::get()->scenes->push(AlertsForm::class, AlertsForm::paramsFor($c, $ref)); break; } $form->refresh(true); return true; } /** What their dialogs answered (MainForm::resume()): true when it was theirs. @param array<string, mixed> $result */ public static function result(MainForm $form, array $result): bool { if (isset($result['alerts'])) { AlertsForm::apply($form->session, $result['alerts']); return true; } if (isset($result['open'])) { // Properties > Alerts… $form->command((string)$result['open'], (string)($result['target'] ?? '')); return true; } if (!isset($result['xva'])) { return false; } $x = $result['xva']; if ($x['op'] === 'export') { $c = $form->session->connectionOf((string)$x['target']); if ($c !== null) { XvaTransfers::export($form->session, $c, (string)$x['target'], (string)$x['file'], !empty($x['compress']))->then(fn () => null, fn () => null); } } else { $c = $form->session->connectionOf((string)$x['sr']); if ($c !== null) { XvaTransfers::import($form->session, $c, (string)$x['file'], (string)$x['sr'], (string)$x['host'], (string)$x['label'])->then(fn () => null, fn () => null); } } return true; }}