games/xencenter-xenadmin-xcp-jumphp
/ src / Feature / Organize / OrganizeCommands.php · commit fb1b7c9
<?phpnamespace App\Feature\Organize;use App\Form\ConfirmForm;use App\Form\FolderForm;use App\Form\MainForm;use App\Form\TagsForm;use App\Gui\Icons;use App\Model\Connection;use App\Model\Session;use App\View\Commands;use App\View\Page;use JumPHP\Kernel\Engine;/** * The commands of the Organization views ("organize.*"): Move to Folder…, Edit Tags… on an object; New Folder, Rename, Delete on a * folder; Rename / Delete on a tag. MainForm hands them over here (command(), resume(), the right click's menu, the page shown). */final class OrganizeCommands{ /** The kinds of the tree that can be put in a folder and tagged. */ public const KINDS = ['pool', 'host', 'vm', 'template', 'sr', 'network']; /** * The right click's menu: a folder's or a tag's own, else the object's ($items, from Commands::context) with Move to Folder… and * Edit Tags… before its Properties. * @param list<array<string, mixed>|string> $items @return list<array<string, mixed>|string> */ public static function context(string $ref, string $kind, array $items): array { if (OrganizeTree::isFolder($ref)) { return [Commands::item('organize.newFolder', 'New Folder…', true, 'folder'), Commands::item('organize.renameFolder', 'Rename Folder…'), '-', Commands::item('organize.deleteFolder', 'Delete Folder…')]; } if (OrganizeTree::isTag($ref)) { return [Commands::item('organize.renameTag', 'Rename Tag…'), Commands::item('organize.deleteTag', 'Delete Tag…')]; } if ($ref === \App\View\TreeBuilder::ROOT && $kind === 'root') { return [...$items, '-', Commands::item('organize.newFolder', 'New Folder…', true, 'folder')]; } if (!in_array($kind, self::KINDS, true)) { return $items; } $mine = [Commands::item('organize.move', 'Move to Folder…', true, 'folder'), Commands::item('organize.tags', 'Edit Tags…', true, 'tag')]; $at = count($items); if ($at > 0 && is_array($items[$at - 1]) && ($items[$at - 1]['id'] ?? '') === 'object.properties') { $at--; } return [...array_slice($items, 0, $at), ...$mine, '-', ...array_slice($items, $at)]; } /** Runs a command of a menu: opens its dialog (or a confirmation first). */ public static function run(MainForm $form, string $id, ?Connection $c, string $target, bool $confirmed): void { $session = $form->session; $name = OrganizeTree::nameOf($target); switch ($id) { case 'organize.move': if ($c !== null && $c->cache->get($target) !== null) { Engine::get()->scenes->push(FolderForm::class, ['mode' => 'move', 'target' => $target, 'name' => $c->cache->name($target), 'folder' => Organizer::folderOf($c->cache, $target), 'folders' => Organizer::folders($session)]); } return; case 'organize.tags': if ($c !== null && $c->cache->get($target) !== null) { Engine::get()->scenes->push(TagsForm::class, ['target' => $target, 'name' => $c->cache->name($target), 'tags' => Organizer::tagsOf($c->cache, $target), 'known' => Organizer::allTags($session)]); } return; case 'organize.newFolder': Engine::get()->scenes->push(FolderForm::class, ['mode' => 'new', 'parent' => OrganizeTree::isFolder($target) ? $name : '']); return; case 'organize.renameFolder': Engine::get()->scenes->push(FolderForm::class, ['mode' => 'rename', 'path' => $name, 'value' => Organizer::leaf($name)]); return; case 'organize.renameTag': Engine::get()->scenes->push(FolderForm::class, ['mode' => 'renameTag', 'path' => $name, 'value' => $name]); return; case 'organize.deleteFolder': case 'organize.deleteTag': if (!$confirmed) { Engine::get()->scenes->push(ConfirmForm::class, ['command' => $id, 'target' => $target, 'ok' => 'Delete', 'message' => $id === 'organize.deleteFolder' ? "Delete the folder '$name'?\n\nIts objects and those of its subfolders go back to the top level: nothing is deleted on the servers." : "Delete the tag '$name'?\n\nIt is removed from every object that has it."]); return; } $id === 'organize.deleteFolder' ? Organizer::deleteFolder($session, $name) : Organizer::renameTag($session, $name, ''); $form->navigate(\App\View\TreeBuilder::ROOT); return; } } /** What FolderForm / TagsForm answered. @param array<string, mixed> $r */ public static function apply(Session $session, array $r): void { $ref = (string)($r['target'] ?? ''); $c = $ref === '' ? null : $session->connectionOf($ref); switch ((string)($r['mode'] ?? '')) { case 'move': if ($c !== null) { Organizer::setFolder($c, $ref, (string)$r['folder'])->then(fn () => null, fn () => null); } return; case 'tags': if ($c !== null) { Organizer::setTags($c, $ref, array_values(array_map('strval', (array)$r['tags'])))->then(fn () => null, fn () => null); } return; case 'new': foreach ($session->connections as $conn) { if ($conn->isConnected()) { Organizer::newFolder($conn, (string)$r['folder'])->then(fn () => null, fn () => null); // the first pool keeps it return; } } return; case 'rename': Organizer::renameFolder($session, (string)$r['path'], (string)$r['folder']); return; case 'renameTag': Organizer::renameTag($session, (string)$r['path'], (string)$r['folder']); return; } } /** The page of a folder or a tag: what is in it, with its commands. Null for any other node. */ public static function page(Session $session, string $id): ?Page { $folder = OrganizeTree::isFolder($id); if (!$folder && !OrganizeTree::isTag($id)) { return null; } $name = OrganizeTree::nameOf($id); $rows = []; foreach ($session->connections as $c) { if (!$c->isConnected()) { continue; } foreach (Organizer::objects($c->cache) as $ref => $class) { $in = $folder ? Organizer::inside(Organizer::folderOf($c->cache, $ref), $name) : in_array($name, Organizer::tagsOf($c->cache, $ref), true); if ($in) { $r = $c->cache->get($ref) ?? []; $rows[] = ['id' => $ref, 'icon' => Icons::of($class, $r, $c->cache), 'cells' => [$c->cache->name($ref), self::label($class, $r), Organizer::folderOf($c->cache, $ref) ?: '(top level)', implode(', ', Organizer::tagsOf($c->cache, $ref)), $c->name()]]; } } } usort($rows, fn ($a, $b) => strnatcasecmp((string)$a['cells'][0], (string)$b['cells'][0])); $actions = $folder ? [['id' => 'organize.newFolder', 'text' => 'New Folder…', 'style' => 'primary'], ['id' => 'organize.renameFolder', 'text' => 'Rename…'], ['id' => 'organize.deleteFolder', 'text' => 'Delete…']] : [['id' => 'organize.renameTag', 'text' => 'Rename Tag…'], ['id' => 'organize.deleteTag', 'text' => 'Delete Tag…']]; return Page::grid([['title' => 'Name', 'width' => 2.2], ['title' => 'Type', 'width' => 1.1], ['title' => 'Folder', 'width' => 1.8], ['title' => 'Tags', 'width' => 1.8], ['title' => 'Server', 'width' => 1.2]], $rows, $actions, $folder ? 'This folder is empty: right click an object, Move to Folder…' : 'No object has this tag.'); } /** @param array<string, mixed> $r */ private static function label(string $class, array $r): string { return match ($class) { 'pool' => 'Pool', 'host' => 'Server', 'sr' => 'Storage', 'network' => 'Network', default => !empty($r['is_a_template']) ? 'Template' : 'VM', }; }}