pvectl 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.claude/rules/branch-before-changes.md +52 -0
- data/.claude/rules/documentation-updates.md +104 -0
- data/.claude/rules/git-workflow.md +84 -0
- data/.claude/rules/proxmox-api-docs.md +58 -0
- data/.claude/rules/rbs-signatures.md +80 -0
- data/.claude/rules/refactoring-as-design-option.md +35 -0
- data/.claude/scheduled_tasks.lock +1 -0
- data/.claude/settings.json +51 -0
- data/.mcp.json +8 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +138 -0
- data/CLAUDE.md +211 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +143 -0
- data/Rakefile +8 -0
- data/docs/proxmox-api-update.sh +96 -0
- data/exe/pvectl +5 -0
- data/lib/pvectl/argv_preprocessor.rb +334 -0
- data/lib/pvectl/cli.rb +102 -0
- data/lib/pvectl/commands/apt.rb +389 -0
- data/lib/pvectl/commands/clone_container.rb +230 -0
- data/lib/pvectl/commands/clone_vm.rb +331 -0
- data/lib/pvectl/commands/cloudinit/command.rb +122 -0
- data/lib/pvectl/commands/cloudinit/dump.rb +94 -0
- data/lib/pvectl/commands/cloudinit/pending.rb +137 -0
- data/lib/pvectl/commands/cloudinit/regenerate.rb +79 -0
- data/lib/pvectl/commands/config/command.rb +65 -0
- data/lib/pvectl/commands/config/get_contexts.rb +68 -0
- data/lib/pvectl/commands/config/set_cluster.rb +103 -0
- data/lib/pvectl/commands/config/set_context.rb +136 -0
- data/lib/pvectl/commands/config/set_credentials.rb +181 -0
- data/lib/pvectl/commands/config/use_context.rb +69 -0
- data/lib/pvectl/commands/config/view.rb +67 -0
- data/lib/pvectl/commands/console.rb +93 -0
- data/lib/pvectl/commands/console_ct.rb +187 -0
- data/lib/pvectl/commands/console_vm.rb +187 -0
- data/lib/pvectl/commands/container_lifecycle_command.rb +77 -0
- data/lib/pvectl/commands/create_backup.rb +173 -0
- data/lib/pvectl/commands/create_container.rb +141 -0
- data/lib/pvectl/commands/create_resource_command.rb +244 -0
- data/lib/pvectl/commands/create_snapshot.rb +242 -0
- data/lib/pvectl/commands/create_vm.rb +267 -0
- data/lib/pvectl/commands/delete_backup.rb +139 -0
- data/lib/pvectl/commands/delete_command.rb +119 -0
- data/lib/pvectl/commands/delete_container.rb +30 -0
- data/lib/pvectl/commands/delete_snapshot.rb +248 -0
- data/lib/pvectl/commands/delete_vm.rb +127 -0
- data/lib/pvectl/commands/describe/command.rb +251 -0
- data/lib/pvectl/commands/edit_container.rb +56 -0
- data/lib/pvectl/commands/edit_dns.rb +149 -0
- data/lib/pvectl/commands/edit_hosts.rb +135 -0
- data/lib/pvectl/commands/edit_node.rb +54 -0
- data/lib/pvectl/commands/edit_resource_command.rb +180 -0
- data/lib/pvectl/commands/edit_vm.rb +154 -0
- data/lib/pvectl/commands/edit_volume.rb +189 -0
- data/lib/pvectl/commands/feature_command.rb +230 -0
- data/lib/pvectl/commands/feature_container.rb +21 -0
- data/lib/pvectl/commands/feature_vm.rb +94 -0
- data/lib/pvectl/commands/get/command.rb +360 -0
- data/lib/pvectl/commands/get/handlers/backups.rb +76 -0
- data/lib/pvectl/commands/get/handlers/capabilities.rb +107 -0
- data/lib/pvectl/commands/get/handlers/containers.rb +148 -0
- data/lib/pvectl/commands/get/handlers/disks.rb +107 -0
- data/lib/pvectl/commands/get/handlers/dns.rb +94 -0
- data/lib/pvectl/commands/get/handlers/hosts.rb +94 -0
- data/lib/pvectl/commands/get/handlers/nodes.rb +162 -0
- data/lib/pvectl/commands/get/handlers/services.rb +81 -0
- data/lib/pvectl/commands/get/handlers/snapshots.rb +97 -0
- data/lib/pvectl/commands/get/handlers/storage.rb +118 -0
- data/lib/pvectl/commands/get/handlers/subscription.rb +69 -0
- data/lib/pvectl/commands/get/handlers/tasks.rb +89 -0
- data/lib/pvectl/commands/get/handlers/templates.rb +175 -0
- data/lib/pvectl/commands/get/handlers/time.rb +118 -0
- data/lib/pvectl/commands/get/handlers/vms.rb +145 -0
- data/lib/pvectl/commands/get/handlers/volume.rb +134 -0
- data/lib/pvectl/commands/get/resource_handler.rb +63 -0
- data/lib/pvectl/commands/get/resource_registry.rb +18 -0
- data/lib/pvectl/commands/get/watch_loop.rb +129 -0
- data/lib/pvectl/commands/irreversible_command.rb +265 -0
- data/lib/pvectl/commands/logs/command.rb +275 -0
- data/lib/pvectl/commands/logs/handlers/journal.rb +46 -0
- data/lib/pvectl/commands/logs/handlers/syslog.rb +53 -0
- data/lib/pvectl/commands/logs/handlers/task_detail.rb +52 -0
- data/lib/pvectl/commands/logs/handlers/task_logs.rb +115 -0
- data/lib/pvectl/commands/logs/resource_handler.rb +46 -0
- data/lib/pvectl/commands/logs/resource_registry.rb +22 -0
- data/lib/pvectl/commands/migrate_command.rb +282 -0
- data/lib/pvectl/commands/migrate_container.rb +23 -0
- data/lib/pvectl/commands/migrate_vm.rb +122 -0
- data/lib/pvectl/commands/move_disk_command.rb +239 -0
- data/lib/pvectl/commands/move_disk_container.rb +21 -0
- data/lib/pvectl/commands/move_disk_vm.rb +127 -0
- data/lib/pvectl/commands/ping.rb +249 -0
- data/lib/pvectl/commands/pull.rb +342 -0
- data/lib/pvectl/commands/push.rb +352 -0
- data/lib/pvectl/commands/reset.rb +64 -0
- data/lib/pvectl/commands/resource_lifecycle_command.rb +277 -0
- data/lib/pvectl/commands/resource_registry.rb +73 -0
- data/lib/pvectl/commands/restart.rb +70 -0
- data/lib/pvectl/commands/restart_container.rb +18 -0
- data/lib/pvectl/commands/restore_backup.rb +236 -0
- data/lib/pvectl/commands/resume.rb +57 -0
- data/lib/pvectl/commands/rollback_snapshot.rb +228 -0
- data/lib/pvectl/commands/sendkey_vm.rb +205 -0
- data/lib/pvectl/commands/service.rb +293 -0
- data/lib/pvectl/commands/set_container.rb +50 -0
- data/lib/pvectl/commands/set_node.rb +52 -0
- data/lib/pvectl/commands/set_resource_command.rb +185 -0
- data/lib/pvectl/commands/set_vm.rb +136 -0
- data/lib/pvectl/commands/set_volume.rb +212 -0
- data/lib/pvectl/commands/shared_config_parsers.rb +126 -0
- data/lib/pvectl/commands/shared_flags.rb +155 -0
- data/lib/pvectl/commands/shutdown.rb +73 -0
- data/lib/pvectl/commands/shutdown_container.rb +18 -0
- data/lib/pvectl/commands/start.rb +79 -0
- data/lib/pvectl/commands/start_container.rb +18 -0
- data/lib/pvectl/commands/stop.rb +75 -0
- data/lib/pvectl/commands/stop_container.rb +18 -0
- data/lib/pvectl/commands/suspend.rb +64 -0
- data/lib/pvectl/commands/template_command.rb +205 -0
- data/lib/pvectl/commands/template_container.rb +27 -0
- data/lib/pvectl/commands/template_vm.rb +106 -0
- data/lib/pvectl/commands/top/command.rb +206 -0
- data/lib/pvectl/commands/top/handlers/containers.rb +61 -0
- data/lib/pvectl/commands/top/handlers/nodes.rb +61 -0
- data/lib/pvectl/commands/top/handlers/vms.rb +61 -0
- data/lib/pvectl/commands/top/resource_handler.rb +46 -0
- data/lib/pvectl/commands/top/resource_registry.rb +22 -0
- data/lib/pvectl/commands/unlink_disk_vm.rb +232 -0
- data/lib/pvectl/commands/vm_lifecycle_command.rb +77 -0
- data/lib/pvectl/commands/wakeonlan_node.rb +153 -0
- data/lib/pvectl/config/errors.rb +62 -0
- data/lib/pvectl/config/models/cluster.rb +180 -0
- data/lib/pvectl/config/models/context.rb +100 -0
- data/lib/pvectl/config/models/resolved_config.rb +171 -0
- data/lib/pvectl/config/models/user.rb +133 -0
- data/lib/pvectl/config/provider.rb +297 -0
- data/lib/pvectl/config/service.rb +300 -0
- data/lib/pvectl/config/store.rb +161 -0
- data/lib/pvectl/config/wizard.rb +309 -0
- data/lib/pvectl/config_serializer.rb +1034 -0
- data/lib/pvectl/connection/retry_handler.rb +161 -0
- data/lib/pvectl/connection.rb +157 -0
- data/lib/pvectl/console/terminal_session.rb +449 -0
- data/lib/pvectl/editor_session.rb +157 -0
- data/lib/pvectl/exit_codes.rb +43 -0
- data/lib/pvectl/formatters/base.rb +55 -0
- data/lib/pvectl/formatters/color_support.rb +90 -0
- data/lib/pvectl/formatters/json.rb +45 -0
- data/lib/pvectl/formatters/output_helper.rb +77 -0
- data/lib/pvectl/formatters/registry.rb +72 -0
- data/lib/pvectl/formatters/table.rb +235 -0
- data/lib/pvectl/formatters/wide.rb +93 -0
- data/lib/pvectl/formatters/yaml.rb +49 -0
- data/lib/pvectl/manifest_serializer.rb +142 -0
- data/lib/pvectl/models/apt_package.rb +107 -0
- data/lib/pvectl/models/backup.rb +173 -0
- data/lib/pvectl/models/base.rb +49 -0
- data/lib/pvectl/models/capability.rb +62 -0
- data/lib/pvectl/models/container.rb +205 -0
- data/lib/pvectl/models/container_operation_result.rb +27 -0
- data/lib/pvectl/models/dns_config.rb +54 -0
- data/lib/pvectl/models/hosts_file.rb +47 -0
- data/lib/pvectl/models/journal_entry.rb +16 -0
- data/lib/pvectl/models/network_interface.rb +85 -0
- data/lib/pvectl/models/node.rb +195 -0
- data/lib/pvectl/models/node_operation_result.rb +45 -0
- data/lib/pvectl/models/operation_result.rb +110 -0
- data/lib/pvectl/models/physical_disk.rb +193 -0
- data/lib/pvectl/models/service.rb +80 -0
- data/lib/pvectl/models/snapshot.rb +101 -0
- data/lib/pvectl/models/snapshot_description.rb +39 -0
- data/lib/pvectl/models/storage.rb +180 -0
- data/lib/pvectl/models/subscription.rb +87 -0
- data/lib/pvectl/models/syslog_entry.rb +17 -0
- data/lib/pvectl/models/task.rb +95 -0
- data/lib/pvectl/models/task_entry.rb +52 -0
- data/lib/pvectl/models/task_log_line.rb +17 -0
- data/lib/pvectl/models/time_config.rb +47 -0
- data/lib/pvectl/models/vm.rb +137 -0
- data/lib/pvectl/models/vm_operation_result.rb +27 -0
- data/lib/pvectl/models/volume.rb +133 -0
- data/lib/pvectl/models/volume_operation_result.rb +26 -0
- data/lib/pvectl/parsers/cloud_init_config.rb +92 -0
- data/lib/pvectl/parsers/disk_config.rb +97 -0
- data/lib/pvectl/parsers/lxc_mount_config.rb +98 -0
- data/lib/pvectl/parsers/lxc_net_config.rb +97 -0
- data/lib/pvectl/parsers/net_config.rb +95 -0
- data/lib/pvectl/parsers/smart_text.rb +42 -0
- data/lib/pvectl/plugin_loader.rb +157 -0
- data/lib/pvectl/presenters/apt_package.rb +99 -0
- data/lib/pvectl/presenters/backup.rb +128 -0
- data/lib/pvectl/presenters/base.rb +283 -0
- data/lib/pvectl/presenters/capability.rb +104 -0
- data/lib/pvectl/presenters/config/context.rb +80 -0
- data/lib/pvectl/presenters/container.rb +574 -0
- data/lib/pvectl/presenters/container_operation_result.rb +109 -0
- data/lib/pvectl/presenters/disk.rb +184 -0
- data/lib/pvectl/presenters/dns_config.rb +68 -0
- data/lib/pvectl/presenters/hosts_file.rb +61 -0
- data/lib/pvectl/presenters/journal_entry.rb +20 -0
- data/lib/pvectl/presenters/node.rb +762 -0
- data/lib/pvectl/presenters/node_operation_result.rb +50 -0
- data/lib/pvectl/presenters/operation_result.rb +61 -0
- data/lib/pvectl/presenters/service.rb +76 -0
- data/lib/pvectl/presenters/snapshot.rb +239 -0
- data/lib/pvectl/presenters/snapshot_operation_result.rb +125 -0
- data/lib/pvectl/presenters/storage.rb +329 -0
- data/lib/pvectl/presenters/subscription.rb +189 -0
- data/lib/pvectl/presenters/syslog_entry.rb +20 -0
- data/lib/pvectl/presenters/task_entry.rb +69 -0
- data/lib/pvectl/presenters/task_log_line.rb +20 -0
- data/lib/pvectl/presenters/template.rb +76 -0
- data/lib/pvectl/presenters/time_config.rb +86 -0
- data/lib/pvectl/presenters/top_container.rb +112 -0
- data/lib/pvectl/presenters/top_node.rb +115 -0
- data/lib/pvectl/presenters/top_presenter.rb +59 -0
- data/lib/pvectl/presenters/top_vm.rb +105 -0
- data/lib/pvectl/presenters/vm.rb +853 -0
- data/lib/pvectl/presenters/vm_operation_result.rb +109 -0
- data/lib/pvectl/presenters/volume.rb +136 -0
- data/lib/pvectl/presenters/volume_operation_result.rb +58 -0
- data/lib/pvectl/repositories/apt.rb +93 -0
- data/lib/pvectl/repositories/backup.rb +186 -0
- data/lib/pvectl/repositories/base.rb +110 -0
- data/lib/pvectl/repositories/capabilities.rb +96 -0
- data/lib/pvectl/repositories/container.rb +503 -0
- data/lib/pvectl/repositories/disk.rb +87 -0
- data/lib/pvectl/repositories/dns.rb +54 -0
- data/lib/pvectl/repositories/hosts.rb +63 -0
- data/lib/pvectl/repositories/journal.rb +23 -0
- data/lib/pvectl/repositories/node.rb +537 -0
- data/lib/pvectl/repositories/service.rb +139 -0
- data/lib/pvectl/repositories/snapshot.rb +133 -0
- data/lib/pvectl/repositories/storage.rb +302 -0
- data/lib/pvectl/repositories/subscription.rb +77 -0
- data/lib/pvectl/repositories/syslog.rb +25 -0
- data/lib/pvectl/repositories/task.rb +82 -0
- data/lib/pvectl/repositories/task_list.rb +30 -0
- data/lib/pvectl/repositories/task_log.rb +31 -0
- data/lib/pvectl/repositories/time_config.rb +53 -0
- data/lib/pvectl/repositories/vm.rb +616 -0
- data/lib/pvectl/repositories/volume.rb +306 -0
- data/lib/pvectl/selectors/base.rb +201 -0
- data/lib/pvectl/selectors/container.rb +116 -0
- data/lib/pvectl/selectors/disk.rb +59 -0
- data/lib/pvectl/selectors/vm.rb +116 -0
- data/lib/pvectl/selectors/volume.rb +59 -0
- data/lib/pvectl/services/backup.rb +209 -0
- data/lib/pvectl/services/clone_container.rb +260 -0
- data/lib/pvectl/services/clone_vm.rb +265 -0
- data/lib/pvectl/services/cloudinit.rb +96 -0
- data/lib/pvectl/services/console.rb +152 -0
- data/lib/pvectl/services/container_lifecycle.rb +124 -0
- data/lib/pvectl/services/create_container.rb +179 -0
- data/lib/pvectl/services/create_vm.rb +191 -0
- data/lib/pvectl/services/edit_container.rb +125 -0
- data/lib/pvectl/services/edit_dns.rb +159 -0
- data/lib/pvectl/services/edit_hosts.rb +78 -0
- data/lib/pvectl/services/edit_node.rb +147 -0
- data/lib/pvectl/services/edit_vm.rb +125 -0
- data/lib/pvectl/services/edit_volume.rb +224 -0
- data/lib/pvectl/services/get/resource_service.rb +98 -0
- data/lib/pvectl/services/move_disk.rb +132 -0
- data/lib/pvectl/services/pull_config.rb +94 -0
- data/lib/pvectl/services/push_config.rb +524 -0
- data/lib/pvectl/services/resize_volume.rb +253 -0
- data/lib/pvectl/services/resource_delete.rb +169 -0
- data/lib/pvectl/services/resource_migration.rb +170 -0
- data/lib/pvectl/services/sendkey.rb +108 -0
- data/lib/pvectl/services/service_lifecycle.rb +89 -0
- data/lib/pvectl/services/set_container.rb +128 -0
- data/lib/pvectl/services/set_node.rb +236 -0
- data/lib/pvectl/services/set_vm.rb +128 -0
- data/lib/pvectl/services/set_volume.rb +126 -0
- data/lib/pvectl/services/snapshot.rb +261 -0
- data/lib/pvectl/services/task_listing.rb +75 -0
- data/lib/pvectl/services/unlink_disk.rb +86 -0
- data/lib/pvectl/services/vm_lifecycle.rb +124 -0
- data/lib/pvectl/services/wakeonlan.rb +79 -0
- data/lib/pvectl/utils/resource_resolver.rb +80 -0
- data/lib/pvectl/version.rb +13 -0
- data/lib/pvectl/wizards/create_container.rb +105 -0
- data/lib/pvectl/wizards/create_vm.rb +98 -0
- data/lib/pvectl.rb +439 -0
- data/sig/external/gli.rbs +16 -0
- data/sig/external/proxmox_api.rbs +10 -0
- data/sig/pvectl/argv_preprocessor.rbs +53 -0
- data/sig/pvectl/cli.rbs +26 -0
- data/sig/pvectl/commands/apt.rbs +47 -0
- data/sig/pvectl/commands/clone_container.rbs +31 -0
- data/sig/pvectl/commands/clone_vm.rbs +33 -0
- data/sig/pvectl/commands/cloudinit/command.rbs +13 -0
- data/sig/pvectl/commands/cloudinit/dump.rbs +13 -0
- data/sig/pvectl/commands/cloudinit/pending.rbs +17 -0
- data/sig/pvectl/commands/cloudinit/regenerate.rbs +11 -0
- data/sig/pvectl/commands/config/command.rbs +9 -0
- data/sig/pvectl/commands/config/get_contexts.rbs +11 -0
- data/sig/pvectl/commands/config/set_cluster.rbs +11 -0
- data/sig/pvectl/commands/config/set_context.rbs +15 -0
- data/sig/pvectl/commands/config/set_credentials.rbs +15 -0
- data/sig/pvectl/commands/config/use_context.rbs +11 -0
- data/sig/pvectl/commands/config/view.rbs +11 -0
- data/sig/pvectl/commands/console.rbs +9 -0
- data/sig/pvectl/commands/console_ct.rbs +27 -0
- data/sig/pvectl/commands/console_vm.rbs +27 -0
- data/sig/pvectl/commands/container_lifecycle_command.rbs +25 -0
- data/sig/pvectl/commands/create_backup.rbs +29 -0
- data/sig/pvectl/commands/create_container.rbs +30 -0
- data/sig/pvectl/commands/create_resource_command.rbs +53 -0
- data/sig/pvectl/commands/create_snapshot.rbs +35 -0
- data/sig/pvectl/commands/create_vm.rbs +30 -0
- data/sig/pvectl/commands/delete_backup.rbs +25 -0
- data/sig/pvectl/commands/delete_command.rbs +45 -0
- data/sig/pvectl/commands/delete_container.rbs +11 -0
- data/sig/pvectl/commands/delete_snapshot.rbs +35 -0
- data/sig/pvectl/commands/delete_vm.rbs +13 -0
- data/sig/pvectl/commands/describe/command.rbs +27 -0
- data/sig/pvectl/commands/edit_container.rbs +17 -0
- data/sig/pvectl/commands/edit_dns.rbs +25 -0
- data/sig/pvectl/commands/edit_hosts.rbs +23 -0
- data/sig/pvectl/commands/edit_node.rbs +17 -0
- data/sig/pvectl/commands/edit_resource_command.rbs +35 -0
- data/sig/pvectl/commands/edit_vm.rbs +19 -0
- data/sig/pvectl/commands/edit_volume.rbs +24 -0
- data/sig/pvectl/commands/feature_command.rbs +43 -0
- data/sig/pvectl/commands/feature_container.rbs +10 -0
- data/sig/pvectl/commands/feature_vm.rbs +12 -0
- data/sig/pvectl/commands/get/command.rbs +42 -0
- data/sig/pvectl/commands/get/handlers/backups.rbs +23 -0
- data/sig/pvectl/commands/get/handlers/capabilities.rbs +29 -0
- data/sig/pvectl/commands/get/handlers/containers.rbs +35 -0
- data/sig/pvectl/commands/get/handlers/disks.rbs +27 -0
- data/sig/pvectl/commands/get/handlers/dns.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/hosts.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/nodes.rbs +33 -0
- data/sig/pvectl/commands/get/handlers/services.rbs +23 -0
- data/sig/pvectl/commands/get/handlers/snapshots.rbs +27 -0
- data/sig/pvectl/commands/get/handlers/storage.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/subscription.rbs +25 -0
- data/sig/pvectl/commands/get/handlers/tasks.rbs +28 -0
- data/sig/pvectl/commands/get/handlers/templates.rbs +35 -0
- data/sig/pvectl/commands/get/handlers/time.rbs +29 -0
- data/sig/pvectl/commands/get/handlers/vms.rbs +35 -0
- data/sig/pvectl/commands/get/handlers/volume.rbs +27 -0
- data/sig/pvectl/commands/get/resource_handler.rbs +13 -0
- data/sig/pvectl/commands/get/resource_registry.rbs +8 -0
- data/sig/pvectl/commands/get/watch_loop.rbs +33 -0
- data/sig/pvectl/commands/irreversible_command.rbs +32 -0
- data/sig/pvectl/commands/logs/command.rbs +35 -0
- data/sig/pvectl/commands/logs/handlers/journal.rbs +21 -0
- data/sig/pvectl/commands/logs/handlers/syslog.rbs +21 -0
- data/sig/pvectl/commands/logs/handlers/task_detail.rbs +21 -0
- data/sig/pvectl/commands/logs/handlers/task_logs.rbs +35 -0
- data/sig/pvectl/commands/logs/resource_handler.rbs +11 -0
- data/sig/pvectl/commands/logs/resource_registry.rbs +8 -0
- data/sig/pvectl/commands/migrate_command.rbs +45 -0
- data/sig/pvectl/commands/migrate_container.rbs +11 -0
- data/sig/pvectl/commands/migrate_vm.rbs +13 -0
- data/sig/pvectl/commands/move_disk_command.rbs +43 -0
- data/sig/pvectl/commands/move_disk_container.rbs +11 -0
- data/sig/pvectl/commands/move_disk_vm.rbs +13 -0
- data/sig/pvectl/commands/ping.rbs +39 -0
- data/sig/pvectl/commands/pull.rbs +33 -0
- data/sig/pvectl/commands/push.rbs +32 -0
- data/sig/pvectl/commands/reset.rbs +11 -0
- data/sig/pvectl/commands/resource_lifecycle_command.rbs +55 -0
- data/sig/pvectl/commands/resource_registry.rbs +19 -0
- data/sig/pvectl/commands/restart.rbs +11 -0
- data/sig/pvectl/commands/restart_container.rbs +9 -0
- data/sig/pvectl/commands/restore_backup.rbs +27 -0
- data/sig/pvectl/commands/resume.rbs +11 -0
- data/sig/pvectl/commands/rollback_snapshot.rbs +31 -0
- data/sig/pvectl/commands/sendkey_vm.rbs +25 -0
- data/sig/pvectl/commands/service.rbs +38 -0
- data/sig/pvectl/commands/set_container.rbs +13 -0
- data/sig/pvectl/commands/set_node.rbs +13 -0
- data/sig/pvectl/commands/set_resource_command.rbs +25 -0
- data/sig/pvectl/commands/set_vm.rbs +15 -0
- data/sig/pvectl/commands/set_volume.rbs +24 -0
- data/sig/pvectl/commands/shared_config_parsers.rbs +19 -0
- data/sig/pvectl/commands/shared_flags.rbs +10 -0
- data/sig/pvectl/commands/shutdown.rbs +11 -0
- data/sig/pvectl/commands/shutdown_container.rbs +9 -0
- data/sig/pvectl/commands/start.rbs +11 -0
- data/sig/pvectl/commands/start_container.rbs +9 -0
- data/sig/pvectl/commands/stop.rbs +11 -0
- data/sig/pvectl/commands/stop_container.rbs +9 -0
- data/sig/pvectl/commands/suspend.rbs +11 -0
- data/sig/pvectl/commands/template_command.rbs +21 -0
- data/sig/pvectl/commands/template_container.rbs +10 -0
- data/sig/pvectl/commands/template_vm.rbs +12 -0
- data/sig/pvectl/commands/top/command.rbs +31 -0
- data/sig/pvectl/commands/top/handlers/containers.rbs +21 -0
- data/sig/pvectl/commands/top/handlers/nodes.rbs +21 -0
- data/sig/pvectl/commands/top/handlers/vms.rbs +21 -0
- data/sig/pvectl/commands/top/resource_handler.rbs +11 -0
- data/sig/pvectl/commands/top/resource_registry.rbs +8 -0
- data/sig/pvectl/commands/unlink_disk_vm.rbs +27 -0
- data/sig/pvectl/commands/vm_lifecycle_command.rbs +25 -0
- data/sig/pvectl/commands/wakeonlan_node.rbs +21 -0
- data/sig/pvectl/config/errors.rbs +24 -0
- data/sig/pvectl/config/models/cluster.rbs +39 -0
- data/sig/pvectl/config/models/context.rbs +23 -0
- data/sig/pvectl/config/models/resolved_config.rbs +51 -0
- data/sig/pvectl/config/models/user.rbs +31 -0
- data/sig/pvectl/config/provider.rbs +40 -0
- data/sig/pvectl/config/service.rbs +65 -0
- data/sig/pvectl/config/store.rbs +14 -0
- data/sig/pvectl/config/wizard.rbs +48 -0
- data/sig/pvectl/config_serializer.rbs +121 -0
- data/sig/pvectl/connection/retry_handler.rbs +31 -0
- data/sig/pvectl/connection.rbs +35 -0
- data/sig/pvectl/console/terminal_session.rbs +63 -0
- data/sig/pvectl/editor_session.rbs +33 -0
- data/sig/pvectl/exit_codes.rbs +19 -0
- data/sig/pvectl/formatters/base.rbs +13 -0
- data/sig/pvectl/formatters/color_support.rbs +13 -0
- data/sig/pvectl/formatters/json.rbs +7 -0
- data/sig/pvectl/formatters/output_helper.rbs +9 -0
- data/sig/pvectl/formatters/registry.rbs +13 -0
- data/sig/pvectl/formatters/table.rbs +25 -0
- data/sig/pvectl/formatters/wide.rbs +15 -0
- data/sig/pvectl/formatters/yaml.rbs +7 -0
- data/sig/pvectl/manifest_serializer.rbs +18 -0
- data/sig/pvectl/models/apt_package.rbs +26 -0
- data/sig/pvectl/models/backup.rbs +31 -0
- data/sig/pvectl/models/base.rbs +11 -0
- data/sig/pvectl/models/capability.rbs +16 -0
- data/sig/pvectl/models/container.rbs +44 -0
- data/sig/pvectl/models/container_operation_result.rbs +9 -0
- data/sig/pvectl/models/dns_config.rbs +15 -0
- data/sig/pvectl/models/hosts_file.rbs +13 -0
- data/sig/pvectl/models/journal_entry.rbs +10 -0
- data/sig/pvectl/models/network_interface.rbs +20 -0
- data/sig/pvectl/models/node.rbs +47 -0
- data/sig/pvectl/models/node_operation_result.rbs +12 -0
- data/sig/pvectl/models/operation_result.rbs +21 -0
- data/sig/pvectl/models/physical_disk.rbs +35 -0
- data/sig/pvectl/models/service.rbs +18 -0
- data/sig/pvectl/models/snapshot.rbs +21 -0
- data/sig/pvectl/models/snapshot_description.rbs +18 -0
- data/sig/pvectl/models/storage.rbs +39 -0
- data/sig/pvectl/models/subscription.rbs +24 -0
- data/sig/pvectl/models/syslog_entry.rbs +10 -0
- data/sig/pvectl/models/task.rbs +22 -0
- data/sig/pvectl/models/task_entry.rbs +24 -0
- data/sig/pvectl/models/task_log_line.rbs +10 -0
- data/sig/pvectl/models/time_config.rbs +12 -0
- data/sig/pvectl/models/vm.rbs +32 -0
- data/sig/pvectl/models/vm_operation_result.rbs +9 -0
- data/sig/pvectl/models/volume.rbs +29 -0
- data/sig/pvectl/models/volume_operation_result.rbs +9 -0
- data/sig/pvectl/parsers/cloud_init_config.rbs +15 -0
- data/sig/pvectl/parsers/disk_config.rbs +19 -0
- data/sig/pvectl/parsers/lxc_mount_config.rbs +19 -0
- data/sig/pvectl/parsers/lxc_net_config.rbs +19 -0
- data/sig/pvectl/parsers/net_config.rbs +19 -0
- data/sig/pvectl/parsers/smart_text.rbs +7 -0
- data/sig/pvectl/plugin_loader.rbs +25 -0
- data/sig/pvectl/presenters/apt_package.rbs +19 -0
- data/sig/pvectl/presenters/backup.rbs +25 -0
- data/sig/pvectl/presenters/base.rbs +41 -0
- data/sig/pvectl/presenters/capability.rbs +19 -0
- data/sig/pvectl/presenters/config/context.rbs +17 -0
- data/sig/pvectl/presenters/container.rbs +78 -0
- data/sig/pvectl/presenters/container_operation_result.rbs +19 -0
- data/sig/pvectl/presenters/disk.rbs +31 -0
- data/sig/pvectl/presenters/dns_config.rbs +13 -0
- data/sig/pvectl/presenters/hosts_file.rbs +13 -0
- data/sig/pvectl/presenters/journal_entry.rbs +11 -0
- data/sig/pvectl/presenters/node.rbs +118 -0
- data/sig/pvectl/presenters/node_operation_result.rbs +11 -0
- data/sig/pvectl/presenters/operation_result.rbs +17 -0
- data/sig/pvectl/presenters/service.rbs +15 -0
- data/sig/pvectl/presenters/snapshot.rbs +35 -0
- data/sig/pvectl/presenters/snapshot_operation_result.rbs +27 -0
- data/sig/pvectl/presenters/storage.rbs +59 -0
- data/sig/pvectl/presenters/subscription.rbs +36 -0
- data/sig/pvectl/presenters/syslog_entry.rbs +11 -0
- data/sig/pvectl/presenters/task_entry.rbs +21 -0
- data/sig/pvectl/presenters/task_log_line.rbs +11 -0
- data/sig/pvectl/presenters/template.rbs +15 -0
- data/sig/pvectl/presenters/time_config.rbs +19 -0
- data/sig/pvectl/presenters/top_container.rbs +17 -0
- data/sig/pvectl/presenters/top_node.rbs +17 -0
- data/sig/pvectl/presenters/top_presenter.rbs +13 -0
- data/sig/pvectl/presenters/top_vm.rbs +17 -0
- data/sig/pvectl/presenters/vm.rbs +91 -0
- data/sig/pvectl/presenters/vm_operation_result.rbs +19 -0
- data/sig/pvectl/presenters/volume.rbs +23 -0
- data/sig/pvectl/presenters/volume_operation_result.rbs +11 -0
- data/sig/pvectl/repositories/apt.rbs +17 -0
- data/sig/pvectl/repositories/backup.rbs +27 -0
- data/sig/pvectl/repositories/base.rbs +23 -0
- data/sig/pvectl/repositories/capabilities.rbs +20 -0
- data/sig/pvectl/repositories/container.rbs +63 -0
- data/sig/pvectl/repositories/disk.rbs +17 -0
- data/sig/pvectl/repositories/dns.rbs +13 -0
- data/sig/pvectl/repositories/hosts.rbs +13 -0
- data/sig/pvectl/repositories/journal.rbs +7 -0
- data/sig/pvectl/repositories/node.rbs +68 -0
- data/sig/pvectl/repositories/service.rbs +27 -0
- data/sig/pvectl/repositories/snapshot.rbs +19 -0
- data/sig/pvectl/repositories/storage.rbs +37 -0
- data/sig/pvectl/repositories/subscription.rbs +17 -0
- data/sig/pvectl/repositories/syslog.rbs +7 -0
- data/sig/pvectl/repositories/task.rbs +19 -0
- data/sig/pvectl/repositories/task_list.rbs +7 -0
- data/sig/pvectl/repositories/task_log.rbs +11 -0
- data/sig/pvectl/repositories/time_config.rbs +13 -0
- data/sig/pvectl/repositories/vm.rbs +85 -0
- data/sig/pvectl/repositories/volume.rbs +43 -0
- data/sig/pvectl/selectors/base.rbs +37 -0
- data/sig/pvectl/selectors/container.rbs +19 -0
- data/sig/pvectl/selectors/disk.rbs +13 -0
- data/sig/pvectl/selectors/vm.rbs +19 -0
- data/sig/pvectl/selectors/volume.rbs +13 -0
- data/sig/pvectl/services/backup.rbs +27 -0
- data/sig/pvectl/services/clone_container.rbs +35 -0
- data/sig/pvectl/services/clone_vm.rbs +35 -0
- data/sig/pvectl/services/cloudinit.rbs +19 -0
- data/sig/pvectl/services/console.rbs +23 -0
- data/sig/pvectl/services/container_lifecycle.rbs +26 -0
- data/sig/pvectl/services/create_container.rbs +64 -0
- data/sig/pvectl/services/create_vm.rbs +72 -0
- data/sig/pvectl/services/edit_container.rbs +17 -0
- data/sig/pvectl/services/edit_dns.rbs +23 -0
- data/sig/pvectl/services/edit_hosts.rbs +13 -0
- data/sig/pvectl/services/edit_node.rbs +21 -0
- data/sig/pvectl/services/edit_vm.rbs +17 -0
- data/sig/pvectl/services/edit_volume.rbs +18 -0
- data/sig/pvectl/services/get/resource_service.rbs +23 -0
- data/sig/pvectl/services/move_disk.rbs +21 -0
- data/sig/pvectl/services/pull_config.rbs +18 -0
- data/sig/pvectl/services/push_config.rbs +37 -0
- data/sig/pvectl/services/resize_volume.rbs +47 -0
- data/sig/pvectl/services/resource_delete.rbs +27 -0
- data/sig/pvectl/services/resource_migration.rbs +29 -0
- data/sig/pvectl/services/sendkey.rbs +19 -0
- data/sig/pvectl/services/service_lifecycle.rbs +17 -0
- data/sig/pvectl/services/set_container.rbs +14 -0
- data/sig/pvectl/services/set_node.rbs +23 -0
- data/sig/pvectl/services/set_vm.rbs +14 -0
- data/sig/pvectl/services/set_volume.rbs +12 -0
- data/sig/pvectl/services/snapshot.rbs +35 -0
- data/sig/pvectl/services/task_listing.rbs +13 -0
- data/sig/pvectl/services/unlink_disk.rbs +17 -0
- data/sig/pvectl/services/vm_lifecycle.rbs +26 -0
- data/sig/pvectl/services/wakeonlan.rbs +17 -0
- data/sig/pvectl/utils/resource_resolver.rbs +17 -0
- data/sig/pvectl/wizards/create_container.rbs +21 -0
- data/sig/pvectl/wizards/create_vm.rbs +21 -0
- data/sig/pvectl.rbs +9 -0
- metadata +675 -0
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Pvectl
|
|
6
|
+
module Presenters
|
|
7
|
+
# Presenter for QEMU virtual machines.
|
|
8
|
+
#
|
|
9
|
+
# Defines column layout and formatting for table output.
|
|
10
|
+
# Used by formatters to render VM data in various formats.
|
|
11
|
+
#
|
|
12
|
+
# Standard columns: NAME, VMID, STATUS, NODE, CPU, MEMORY
|
|
13
|
+
# Wide columns add: UPTIME, TEMPLATE, TAGS, DISK, IP, AGENT, HA, BACKUP
|
|
14
|
+
#
|
|
15
|
+
# @example Using with formatter
|
|
16
|
+
# presenter = Vm.new
|
|
17
|
+
# formatter = Formatters::Table.new
|
|
18
|
+
# output = formatter.format(vms, presenter)
|
|
19
|
+
#
|
|
20
|
+
# @see Pvectl::Models::Vm VM model
|
|
21
|
+
# @see Pvectl::Formatters::Table Table formatter
|
|
22
|
+
#
|
|
23
|
+
class Vm < Base
|
|
24
|
+
# Returns column headers for standard table output.
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<String>] column headers
|
|
27
|
+
def columns
|
|
28
|
+
%w[NAME VMID STATUS NODE CPU MEMORY]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns additional column headers for wide output.
|
|
32
|
+
#
|
|
33
|
+
# @return [Array<String>] extra column headers
|
|
34
|
+
def extra_columns
|
|
35
|
+
%w[UPTIME TEMPLATE TAGS DISK IP AGENT HA BACKUP]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Converts VM model to table row values.
|
|
39
|
+
#
|
|
40
|
+
# @param model [Models::Vm] VM model
|
|
41
|
+
# @param context [Hash] optional context
|
|
42
|
+
# @return [Array<String>] row values matching columns order
|
|
43
|
+
def to_row(model, **_context)
|
|
44
|
+
@vm = model
|
|
45
|
+
[
|
|
46
|
+
display_name,
|
|
47
|
+
vm.vmid.to_s,
|
|
48
|
+
vm.status,
|
|
49
|
+
vm.node,
|
|
50
|
+
cpu_percent,
|
|
51
|
+
memory_display
|
|
52
|
+
]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns additional values for wide output.
|
|
56
|
+
#
|
|
57
|
+
# Note: IP, AGENT, and BACKUP are placeholders for future implementation
|
|
58
|
+
# that would require additional API calls to the QEMU guest agent.
|
|
59
|
+
#
|
|
60
|
+
# @param model [Models::Vm] VM model
|
|
61
|
+
# @param context [Hash] optional context
|
|
62
|
+
# @return [Array<String>] extra values matching extra_columns order
|
|
63
|
+
def extra_values(model, **_context)
|
|
64
|
+
@vm = model
|
|
65
|
+
[
|
|
66
|
+
uptime_human,
|
|
67
|
+
template_display,
|
|
68
|
+
tags_display,
|
|
69
|
+
disk_display,
|
|
70
|
+
"-", # IP - requires QEMU agent (future enhancement)
|
|
71
|
+
"-", # AGENT status (future enhancement)
|
|
72
|
+
vm.hastate || "-",
|
|
73
|
+
"-" # BACKUP schedule (future enhancement)
|
|
74
|
+
]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Converts VM model to hash for JSON/YAML output.
|
|
78
|
+
#
|
|
79
|
+
# Returns a structured hash with nested objects for complex data
|
|
80
|
+
# like CPU, memory, disk, uptime, and network.
|
|
81
|
+
#
|
|
82
|
+
# @param model [Models::Vm] VM model
|
|
83
|
+
# @return [Hash] hash representation with string keys
|
|
84
|
+
def to_hash(model)
|
|
85
|
+
@vm = model
|
|
86
|
+
{
|
|
87
|
+
"vmid" => vm.vmid,
|
|
88
|
+
"name" => vm.name,
|
|
89
|
+
"status" => vm.status,
|
|
90
|
+
"node" => vm.node,
|
|
91
|
+
"template" => vm.template?,
|
|
92
|
+
"cpu" => {
|
|
93
|
+
"usage_percent" => vm.cpu.nil? ? nil : (vm.cpu * 100).round,
|
|
94
|
+
"cores" => vm.maxcpu
|
|
95
|
+
},
|
|
96
|
+
"memory" => {
|
|
97
|
+
"used_gb" => memory_used_gb,
|
|
98
|
+
"total_gb" => memory_total_gb,
|
|
99
|
+
"used_bytes" => vm.mem,
|
|
100
|
+
"total_bytes" => vm.maxmem
|
|
101
|
+
},
|
|
102
|
+
"disk" => {
|
|
103
|
+
"used_gb" => disk_used_gb,
|
|
104
|
+
"total_gb" => disk_total_gb,
|
|
105
|
+
"used_bytes" => vm.disk,
|
|
106
|
+
"total_bytes" => vm.maxdisk
|
|
107
|
+
},
|
|
108
|
+
"uptime" => {
|
|
109
|
+
"seconds" => vm.uptime,
|
|
110
|
+
"human" => uptime_human
|
|
111
|
+
},
|
|
112
|
+
"network" => {
|
|
113
|
+
"in_bytes" => vm.netin,
|
|
114
|
+
"out_bytes" => vm.netout
|
|
115
|
+
},
|
|
116
|
+
"ha" => {
|
|
117
|
+
"state" => vm.hastate
|
|
118
|
+
},
|
|
119
|
+
"tags" => tags_array
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Converts VM model to description format for describe command.
|
|
124
|
+
#
|
|
125
|
+
# Returns a structured Hash organized by Proxmox VE web UI tabs:
|
|
126
|
+
# Summary, Hardware, Cloud-Init, Options, Task History, Snapshots,
|
|
127
|
+
# Pending Changes. Nested Hashes create indented subsections.
|
|
128
|
+
# Arrays of Hashes render as inline tables.
|
|
129
|
+
#
|
|
130
|
+
# @param model [Models::Vm] VM model with describe details
|
|
131
|
+
# @return [Hash] structured hash for describe formatter
|
|
132
|
+
def to_description(model)
|
|
133
|
+
@vm = model
|
|
134
|
+
@consumed_keys = Set.new
|
|
135
|
+
data = vm.describe_data || {}
|
|
136
|
+
config = data[:config] || {}
|
|
137
|
+
status = data[:status] || {}
|
|
138
|
+
|
|
139
|
+
consume(:name, :description, :tags, :pool, :template)
|
|
140
|
+
|
|
141
|
+
{
|
|
142
|
+
"Name" => display_name,
|
|
143
|
+
"VMID" => vm.vmid,
|
|
144
|
+
"Status" => vm.status,
|
|
145
|
+
"Node" => vm.node,
|
|
146
|
+
"Tags" => tags_display,
|
|
147
|
+
"Description" => config[:description] || "-",
|
|
148
|
+
"Summary" => format_summary(config, status),
|
|
149
|
+
"Block Device Statistics" => format_blockstat(status),
|
|
150
|
+
"Hardware" => format_hardware(config, data),
|
|
151
|
+
"Cloud-Init" => format_cloud_init(config),
|
|
152
|
+
"Options" => format_options(config),
|
|
153
|
+
"Firewall" => format_firewall(data[:firewall]),
|
|
154
|
+
"Firewall Rules" => format_firewall_rules(data[:firewall]),
|
|
155
|
+
"Task History" => format_task_history(data[:tasks]),
|
|
156
|
+
"Snapshots" => format_snapshots(data[:snapshots]),
|
|
157
|
+
"Pending Changes" => format_pending_changes(data[:pending]),
|
|
158
|
+
"Additional Configuration" => format_remaining(config)
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# ---------------------------
|
|
163
|
+
# Display Methods (from Model)
|
|
164
|
+
# ---------------------------
|
|
165
|
+
|
|
166
|
+
# Returns display name, falling back to "VM-{vmid}" if name is nil.
|
|
167
|
+
#
|
|
168
|
+
# @return [String] display name
|
|
169
|
+
def display_name
|
|
170
|
+
vm.name || "VM-#{vm.vmid}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Returns CPU usage as percentage string.
|
|
174
|
+
#
|
|
175
|
+
# For running VMs, shows actual usage percentage.
|
|
176
|
+
# For stopped VMs, shows "-" for usage but includes core count if available.
|
|
177
|
+
#
|
|
178
|
+
# @return [String] CPU percentage (e.g., "12%") or "-/4" for stopped VMs
|
|
179
|
+
def cpu_percent
|
|
180
|
+
return "-" if vm.maxcpu.nil?
|
|
181
|
+
return "-/#{vm.maxcpu}" unless vm.running?
|
|
182
|
+
return "-/#{vm.maxcpu}" if vm.cpu.nil?
|
|
183
|
+
|
|
184
|
+
"#{(vm.cpu * 100).round}%/#{vm.maxcpu}"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Returns memory used in GB.
|
|
188
|
+
#
|
|
189
|
+
# @return [Float, nil] memory used in GB, or nil if unavailable
|
|
190
|
+
def memory_used_gb
|
|
191
|
+
return nil if vm.mem.nil?
|
|
192
|
+
|
|
193
|
+
(vm.mem.to_f / 1024 / 1024 / 1024).round(1)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Returns total memory in GB.
|
|
197
|
+
#
|
|
198
|
+
# @return [Float, nil] total memory in GB, or nil if unavailable
|
|
199
|
+
def memory_total_gb
|
|
200
|
+
return nil if vm.maxmem.nil?
|
|
201
|
+
|
|
202
|
+
(vm.maxmem.to_f / 1024 / 1024 / 1024).round(1)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Returns memory formatted as "used/total GB".
|
|
206
|
+
#
|
|
207
|
+
# For running VMs, shows actual usage and total.
|
|
208
|
+
# For stopped VMs, shows "-" for usage but includes total if available.
|
|
209
|
+
#
|
|
210
|
+
# @return [String] formatted memory (e.g., "2.1/4.0 GB") or "-/4.0 GB" for stopped VMs
|
|
211
|
+
def memory_display
|
|
212
|
+
return "-" if memory_total_gb.nil?
|
|
213
|
+
return "-/#{memory_total_gb} GB" unless vm.running?
|
|
214
|
+
return "-/#{memory_total_gb} GB" if memory_used_gb.nil?
|
|
215
|
+
|
|
216
|
+
"#{memory_used_gb}/#{memory_total_gb} GB"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Returns disk used in GB.
|
|
220
|
+
#
|
|
221
|
+
# @return [Integer, nil] disk used in GB, or nil if unavailable
|
|
222
|
+
def disk_used_gb
|
|
223
|
+
return nil if vm.disk.nil?
|
|
224
|
+
|
|
225
|
+
(vm.disk.to_f / 1024 / 1024 / 1024).round
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Returns total disk in GB.
|
|
229
|
+
#
|
|
230
|
+
# @return [Integer, nil] total disk in GB, or nil if unavailable
|
|
231
|
+
def disk_total_gb
|
|
232
|
+
return nil if vm.maxdisk.nil?
|
|
233
|
+
|
|
234
|
+
(vm.maxdisk.to_f / 1024 / 1024 / 1024).round
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Returns disk formatted as "used/total GB".
|
|
238
|
+
#
|
|
239
|
+
# @return [String] formatted disk (e.g., "15/50 GB") or "-" if unavailable
|
|
240
|
+
def disk_display
|
|
241
|
+
return "-" if disk_used_gb.nil?
|
|
242
|
+
|
|
243
|
+
"#{disk_used_gb}/#{disk_total_gb} GB"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
private
|
|
247
|
+
|
|
248
|
+
# @return [Models::Vm] current VM model
|
|
249
|
+
attr_reader :vm
|
|
250
|
+
|
|
251
|
+
alias resource vm
|
|
252
|
+
|
|
253
|
+
# Formats Summary section (PVE Summary tab).
|
|
254
|
+
#
|
|
255
|
+
# Shows HA state, resource usage, and (for running VMs) runtime info
|
|
256
|
+
# including uptime, PID, QEMU version, machine type, and I/O statistics.
|
|
257
|
+
#
|
|
258
|
+
# @param config [Hash] VM config
|
|
259
|
+
# @param status [Hash] VM status
|
|
260
|
+
# @return [Hash] summary info
|
|
261
|
+
def format_summary(config, status)
|
|
262
|
+
sockets = config[:sockets] || 1
|
|
263
|
+
cores = config[:cores] || 1
|
|
264
|
+
total_cpus = sockets * cores
|
|
265
|
+
cpu_usage = vm.running? && vm.cpu ? "#{(vm.cpu * 100).round(2)}% of #{total_cpus} CPU(s)" : "-"
|
|
266
|
+
|
|
267
|
+
mem_usage = if vm.running? && vm.mem && vm.maxmem && vm.maxmem > 0
|
|
268
|
+
pct = ((vm.mem.to_f / vm.maxmem) * 100).round(2)
|
|
269
|
+
"#{pct}% (#{format_bytes(vm.mem)} of #{format_bytes(vm.maxmem)})"
|
|
270
|
+
else
|
|
271
|
+
"-"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
bootdisk_size = find_bootdisk_size(config)
|
|
275
|
+
|
|
276
|
+
result = { "HA State" => vm.hastate || "-", "CPU Usage" => cpu_usage,
|
|
277
|
+
"Memory Usage" => mem_usage, "Bootdisk Size" => bootdisk_size }
|
|
278
|
+
if vm.running?
|
|
279
|
+
result["Uptime"] = uptime_human
|
|
280
|
+
result["PID"] = (status[:pid] || "-").to_s
|
|
281
|
+
result["QEMU Version"] = status[:"running-qemu"] || "-"
|
|
282
|
+
result["Machine Type"] = status[:"running-machine"] || "-"
|
|
283
|
+
result["Network In"] = format_bytes(vm.netin)
|
|
284
|
+
result["Network Out"] = format_bytes(vm.netout)
|
|
285
|
+
result["Disk Read"] = format_bytes(status[:diskread])
|
|
286
|
+
result["Disk Written"] = format_bytes(status[:diskwrite])
|
|
287
|
+
end
|
|
288
|
+
result
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Finds the bootdisk size from boot order config.
|
|
292
|
+
#
|
|
293
|
+
# Parses the boot order to find the first device, then extracts its
|
|
294
|
+
# size from the disk config string. Falls back to maxdisk from model.
|
|
295
|
+
#
|
|
296
|
+
# @param config [Hash] VM config
|
|
297
|
+
# @return [String] bootdisk size or "-"
|
|
298
|
+
def find_bootdisk_size(config)
|
|
299
|
+
boot = config[:boot]
|
|
300
|
+
if boot
|
|
301
|
+
first_dev = boot.to_s.sub(/^order=/, "").split(";").first
|
|
302
|
+
if first_dev && config[first_dev.to_sym]
|
|
303
|
+
disk_str = config[first_dev.to_sym].to_s
|
|
304
|
+
size_part = disk_str.split(",").find { |p| p.start_with?("size=") }
|
|
305
|
+
return size_part.sub("size=", "") if size_part
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
vm.maxdisk ? format_bytes(vm.maxdisk) : "-"
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Formats Block Device Statistics section.
|
|
312
|
+
#
|
|
313
|
+
# Renders per-disk I/O statistics from the running VM's status payload.
|
|
314
|
+
# Available only for running VMs — Proxmox populates +blockstat+ as
|
|
315
|
+
# part of +/status/current+ when the QEMU process is alive.
|
|
316
|
+
#
|
|
317
|
+
# @param status [Hash] VM status payload from /status/current
|
|
318
|
+
# @return [Array<Hash>, String] per-device I/O table or "-" when absent
|
|
319
|
+
def format_blockstat(status)
|
|
320
|
+
blockstat = status[:blockstat]
|
|
321
|
+
return "-" if blockstat.nil? || blockstat.empty?
|
|
322
|
+
|
|
323
|
+
blockstat.sort_by { |name, _| name.to_s }.map do |name, stats|
|
|
324
|
+
stats ||= {}
|
|
325
|
+
{
|
|
326
|
+
"DEVICE" => name.to_s,
|
|
327
|
+
"READ" => format_bytes(stats[:rd_bytes]),
|
|
328
|
+
"WRITTEN" => format_bytes(stats[:wr_bytes]),
|
|
329
|
+
"READ_IOPS" => (stats[:rd_operations] || 0).to_s,
|
|
330
|
+
"WRITE_IOPS" => (stats[:wr_operations] || 0).to_s
|
|
331
|
+
}
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Formats Hardware section (PVE Hardware tab).
|
|
336
|
+
#
|
|
337
|
+
# Shows memory, balloon, processors, BIOS, machine type, display,
|
|
338
|
+
# SCSI controller, disks, network, and peripheral devices.
|
|
339
|
+
#
|
|
340
|
+
# @param config [Hash] VM config
|
|
341
|
+
# @param data [Hash] full describe data (for agent_ips)
|
|
342
|
+
# @return [Hash] hardware info with mixed String values and Array sub-tables
|
|
343
|
+
def format_hardware(config, data)
|
|
344
|
+
consume(:bios, :machine, :scsihw, :memory, :balloon, :shares,
|
|
345
|
+
:sockets, :cores, :cpu, :vcpus, :cpulimit, :cpuunits, :vga)
|
|
346
|
+
|
|
347
|
+
# Memory line — flat string by default, expanded to a Hash with
|
|
348
|
+
# ballooning details when status payload contains ballooninfo.
|
|
349
|
+
total_mb = config[:memory] || (vm.maxmem ? vm.maxmem / 1024 / 1024 : nil)
|
|
350
|
+
memory_str = total_mb ? "#{(total_mb.to_f / 1024).round(2)} GiB" : "-"
|
|
351
|
+
memory_section = format_memory_section(memory_str, data[:status])
|
|
352
|
+
|
|
353
|
+
# Balloon line
|
|
354
|
+
balloon = config[:balloon]
|
|
355
|
+
balloon_str = if balloon && balloon > 0
|
|
356
|
+
"enabled (min: #{(balloon.to_f / 1024).round(1)} GiB)"
|
|
357
|
+
else
|
|
358
|
+
"disabled"
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Processors line: "4 (2 sockets, 2 cores) [host]"
|
|
362
|
+
sockets = config[:sockets] || 1
|
|
363
|
+
cores = config[:cores] || 1
|
|
364
|
+
cpu_type = config[:cpu] || "kvm64"
|
|
365
|
+
total = sockets * cores
|
|
366
|
+
processors_str = "#{total} (#{sockets} sockets, #{cores} cores) [#{cpu_type}]"
|
|
367
|
+
|
|
368
|
+
# BIOS
|
|
369
|
+
bios = config[:bios] || "seabios"
|
|
370
|
+
bios_display = bios == "ovmf" ? "UEFI (OVMF)" : "SeaBIOS"
|
|
371
|
+
|
|
372
|
+
# Machine
|
|
373
|
+
machine_str = config[:machine] || "i440fx"
|
|
374
|
+
|
|
375
|
+
{
|
|
376
|
+
"Memory" => memory_section,
|
|
377
|
+
"Balloon" => balloon_str,
|
|
378
|
+
"Processors" => processors_str,
|
|
379
|
+
"BIOS" => bios_display,
|
|
380
|
+
"Machine" => machine_str,
|
|
381
|
+
"Display" => config[:vga] || "Default",
|
|
382
|
+
"SCSI Controller" => config[:scsihw] || "lsi",
|
|
383
|
+
"EFI Disk" => format_efi_disk(config),
|
|
384
|
+
"TPM" => format_tpm(config),
|
|
385
|
+
"Disks" => parse_disks(config),
|
|
386
|
+
"Network" => format_network(config, data[:agent_ips]),
|
|
387
|
+
"USB Devices" => format_usb_devices(config),
|
|
388
|
+
"PCI Passthrough" => format_pci_passthrough(config),
|
|
389
|
+
"Serial Ports" => format_serial_ports(config),
|
|
390
|
+
"Audio" => format_audio(config)
|
|
391
|
+
}
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Formats Memory entry in the Hardware section.
|
|
395
|
+
#
|
|
396
|
+
# Returns a flat string with configured memory when ballooning info
|
|
397
|
+
# is not available (balloon driver inactive or VM stopped). When the
|
|
398
|
+
# status payload contains +ballooninfo+, returns a Hash with the
|
|
399
|
+
# configured value plus runtime balloon metrics from the guest:
|
|
400
|
+
# actual ballooned size, maximum, free memory inside the guest,
|
|
401
|
+
# and total guest-visible memory.
|
|
402
|
+
#
|
|
403
|
+
# @param memory_str [String] formatted configured memory
|
|
404
|
+
# @param status [Hash, nil] VM status payload
|
|
405
|
+
# @return [String, Hash] flat string or nested sub-section
|
|
406
|
+
def format_memory_section(memory_str, status)
|
|
407
|
+
info = status.is_a?(Hash) ? status[:ballooninfo] : nil
|
|
408
|
+
return memory_str unless info.is_a?(Hash)
|
|
409
|
+
|
|
410
|
+
{
|
|
411
|
+
"Configured" => memory_str,
|
|
412
|
+
"Actual" => format_bytes(info[:actual]),
|
|
413
|
+
"Max" => format_bytes(info[:max_mem]),
|
|
414
|
+
"Free (inside guest)" => format_bytes(info[:free_mem]),
|
|
415
|
+
"Total (guest visible)" => format_bytes(info[:total_mem])
|
|
416
|
+
}
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Formats Options section (PVE Options tab).
|
|
420
|
+
#
|
|
421
|
+
# Shows boot, startup, OS type, agent, security, and other VM options.
|
|
422
|
+
#
|
|
423
|
+
# @param config [Hash] VM config
|
|
424
|
+
# @return [Hash] options info
|
|
425
|
+
def format_options(config)
|
|
426
|
+
consume(:onboot, :startup, :ostype, :boot, :tablet, :hotplug,
|
|
427
|
+
:acpi, :kvm, :freeze, :localtime, :numa, :agent,
|
|
428
|
+
:protection, :firewall, :lock, :hookscript,
|
|
429
|
+
:args, :vmgenid, :meta, :ha)
|
|
430
|
+
consume_matching(config, /^numa\d+$/)
|
|
431
|
+
consume_matching(config, /^unused\d+$/)
|
|
432
|
+
|
|
433
|
+
on_boot = config[:onboot] == 1 ? "Yes" : "No"
|
|
434
|
+
|
|
435
|
+
startup = config[:startup]
|
|
436
|
+
startup_display = startup ? startup.to_s : "-"
|
|
437
|
+
|
|
438
|
+
ostype_display = format_ostype(config[:ostype])
|
|
439
|
+
|
|
440
|
+
boot = config[:boot]
|
|
441
|
+
boot_display = if boot
|
|
442
|
+
order = boot.to_s.sub(/^order=/, "").split(";").join(", ")
|
|
443
|
+
order.empty? ? "-" : order
|
|
444
|
+
else
|
|
445
|
+
"-"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
tablet = config[:tablet] == 0 ? "No" : "Yes"
|
|
449
|
+
hotplug_raw = config[:hotplug]
|
|
450
|
+
hotplug = if hotplug_raw
|
|
451
|
+
hotplug_raw.to_s == "0" ? "Disabled" : hotplug_raw.to_s.split(",").join(", ")
|
|
452
|
+
else
|
|
453
|
+
"disk, network, usb"
|
|
454
|
+
end
|
|
455
|
+
acpi = config[:acpi] == 0 ? "No" : "Yes"
|
|
456
|
+
kvm = config[:kvm] == 0 ? "No" : "Yes"
|
|
457
|
+
freeze_cpu = config[:freeze] == 1 ? "Yes" : "No"
|
|
458
|
+
localtime = config[:localtime] == 1 ? "Yes" : "Default"
|
|
459
|
+
numa = config[:numa] == 1 ? "Yes" : "No"
|
|
460
|
+
|
|
461
|
+
agent_display = format_agent_options(config)
|
|
462
|
+
protection = config[:protection] == 1 ? "Yes" : "No"
|
|
463
|
+
firewall = config[:firewall] == 1 ? "Yes" : "No"
|
|
464
|
+
hookscript = config[:hookscript] || "-"
|
|
465
|
+
|
|
466
|
+
{
|
|
467
|
+
"Start at Boot" => on_boot,
|
|
468
|
+
"Start/Shutdown Order" => startup_display,
|
|
469
|
+
"OS Type" => ostype_display,
|
|
470
|
+
"Boot Order" => boot_display,
|
|
471
|
+
"Use Tablet for Pointer" => tablet,
|
|
472
|
+
"Hotplug" => hotplug,
|
|
473
|
+
"ACPI Support" => acpi,
|
|
474
|
+
"KVM Hardware Virtualization" => kvm,
|
|
475
|
+
"Freeze CPU at Startup" => freeze_cpu,
|
|
476
|
+
"Use Local Time for RTC" => localtime,
|
|
477
|
+
"NUMA" => numa,
|
|
478
|
+
"QEMU Guest Agent" => agent_display,
|
|
479
|
+
"Protection" => protection,
|
|
480
|
+
"Firewall" => firewall,
|
|
481
|
+
"Hookscript" => hookscript
|
|
482
|
+
}
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Formats QEMU guest agent as sub-section Hash for Options.
|
|
486
|
+
#
|
|
487
|
+
# Matches PVE Options tab layout with separate fields for
|
|
488
|
+
# enable/disable, guest-trim, and freeze-fs-on-backup.
|
|
489
|
+
#
|
|
490
|
+
# @param config [Hash] VM config
|
|
491
|
+
# @return [Hash] agent options sub-section
|
|
492
|
+
def format_agent_options(config)
|
|
493
|
+
agent = config[:agent]
|
|
494
|
+
unless agent
|
|
495
|
+
return {
|
|
496
|
+
"Use QEMU Guest Agent" => "No",
|
|
497
|
+
"Run guest-trim after a disk move or VM migration" => "No",
|
|
498
|
+
"Freeze/thaw guest filesystems on backup for consistency" => "No"
|
|
499
|
+
}
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
parts = agent.to_s.split(",")
|
|
503
|
+
enabled = parts.first == "1"
|
|
504
|
+
|
|
505
|
+
opts = {}
|
|
506
|
+
parts[1..].each { |p| k, v = p.split("=", 2); opts[k] = v }
|
|
507
|
+
|
|
508
|
+
result = {
|
|
509
|
+
"Use QEMU Guest Agent" => enabled ? "Yes" : "No",
|
|
510
|
+
"Run guest-trim after a disk move or VM migration" => opts["fstrim_cloned_disks"] == "1" ? "Yes" : "No",
|
|
511
|
+
"Freeze/thaw guest filesystems on backup for consistency" => opts["freeze-fs-on-backup"] == "1" ? "Yes" : "No"
|
|
512
|
+
}
|
|
513
|
+
result["Type"] = opts["type"] if opts["type"]
|
|
514
|
+
result
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Formats OS type for display.
|
|
518
|
+
#
|
|
519
|
+
# @param ostype [String, nil] OS type from config
|
|
520
|
+
# @return [String] formatted OS type
|
|
521
|
+
def format_ostype(ostype)
|
|
522
|
+
case ostype
|
|
523
|
+
when "l26" then "l26 (Linux 2.6+)"
|
|
524
|
+
when "l24" then "l24 (Linux 2.4)"
|
|
525
|
+
when "win11" then "win11 (Windows 11)"
|
|
526
|
+
when "win10" then "win10 (Windows 10)"
|
|
527
|
+
when "win8" then "win8 (Windows 8)"
|
|
528
|
+
when "win7" then "win7 (Windows 7)"
|
|
529
|
+
when "wxp" then "wxp (Windows XP)"
|
|
530
|
+
when "other" then "other"
|
|
531
|
+
else ostype || "-"
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Parses disk configuration strings from VM config.
|
|
536
|
+
#
|
|
537
|
+
# Disk keys: scsi0-30, ide0-3, virtio0-15, sata0-5
|
|
538
|
+
# Format: "storage:volume,size=X,format=Y,..."
|
|
539
|
+
# Example: "local-lvm:vm-100-disk-0,size=50G,format=raw"
|
|
540
|
+
#
|
|
541
|
+
# @param config [Hash] VM config
|
|
542
|
+
# @return [Array<Hash>, String] parsed disks or "-"
|
|
543
|
+
def parse_disks(config)
|
|
544
|
+
consume_matching(config, /^(scsi|ide|virtio|sata)\d+$/)
|
|
545
|
+
disk_keys = config.keys.select { |k| k.to_s.match?(/^(scsi|ide|virtio|sata)\d+$/) }
|
|
546
|
+
return "-" if disk_keys.empty?
|
|
547
|
+
|
|
548
|
+
disks = disk_keys.sort.map do |key|
|
|
549
|
+
parse_disk_string(key.to_s, config[key])
|
|
550
|
+
end.compact
|
|
551
|
+
|
|
552
|
+
disks.empty? ? "-" : disks
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Parses a single disk config string.
|
|
556
|
+
#
|
|
557
|
+
# @param name [String] disk name (e.g., "scsi0")
|
|
558
|
+
# @param value [String] disk config string
|
|
559
|
+
# @return [Hash, nil] parsed disk info
|
|
560
|
+
def parse_disk_string(name, value)
|
|
561
|
+
return nil if value.nil? || value.to_s.empty?
|
|
562
|
+
return nil if value.to_s == "none"
|
|
563
|
+
|
|
564
|
+
# Format: "storage:volume,key=value,..."
|
|
565
|
+
parts = value.to_s.split(",")
|
|
566
|
+
storage_part = parts.first
|
|
567
|
+
|
|
568
|
+
# Extract storage name (before colon)
|
|
569
|
+
storage = storage_part.include?(":") ? storage_part.split(":").first : storage_part
|
|
570
|
+
|
|
571
|
+
# Extract size and format from key=value pairs
|
|
572
|
+
size = nil
|
|
573
|
+
format = nil
|
|
574
|
+
parts[1..].each do |part|
|
|
575
|
+
key, val = part.split("=", 2)
|
|
576
|
+
case key
|
|
577
|
+
when "size" then size = val
|
|
578
|
+
when "format" then format = val
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
{
|
|
583
|
+
"NAME" => name,
|
|
584
|
+
"SIZE" => size || "-",
|
|
585
|
+
"STORAGE" => storage,
|
|
586
|
+
"FORMAT" => format || "-"
|
|
587
|
+
}
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
# Formats network section with IP addresses from agent.
|
|
591
|
+
#
|
|
592
|
+
# Network keys: net0-31
|
|
593
|
+
# Format: "model=X,bridge=Y,macaddr=Z,..."
|
|
594
|
+
# Example: "virtio=BC:24:11:AA:BB:CC,bridge=vmbr0"
|
|
595
|
+
#
|
|
596
|
+
# @param config [Hash] VM config
|
|
597
|
+
# @param agent_ips [Array<Hash>, nil] agent network interfaces
|
|
598
|
+
# @return [Array<Hash>, String] parsed networks or "-"
|
|
599
|
+
def format_network(config, agent_ips)
|
|
600
|
+
consume_matching(config, /^net\d+$/)
|
|
601
|
+
net_keys = config.keys.select { |k| k.to_s.match?(/^net\d+$/) }
|
|
602
|
+
return "-" if net_keys.empty?
|
|
603
|
+
|
|
604
|
+
# Build MAC -> IP mapping from agent data
|
|
605
|
+
mac_to_ip = build_mac_ip_map(agent_ips)
|
|
606
|
+
|
|
607
|
+
networks = net_keys.sort.map do |key|
|
|
608
|
+
parse_network_string(key.to_s, config[key], mac_to_ip)
|
|
609
|
+
end.compact
|
|
610
|
+
|
|
611
|
+
networks.empty? ? "-" : networks
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# Builds MAC address to IP mapping from agent interfaces.
|
|
615
|
+
#
|
|
616
|
+
# @param agent_ips [Array<Hash>, nil] agent interfaces
|
|
617
|
+
# @return [Hash] MAC -> IP mapping
|
|
618
|
+
def build_mac_ip_map(agent_ips)
|
|
619
|
+
return {} if agent_ips.nil?
|
|
620
|
+
|
|
621
|
+
map = {}
|
|
622
|
+
agent_ips.each do |iface|
|
|
623
|
+
mac = iface[:"hardware-address"]&.downcase
|
|
624
|
+
next if mac.nil?
|
|
625
|
+
|
|
626
|
+
# Find first non-loopback IPv4 address
|
|
627
|
+
ip_addrs = iface[:"ip-addresses"] || []
|
|
628
|
+
ipv4 = ip_addrs.find { |a| a[:"ip-address-type"] == "ipv4" && a[:"ip-address"] != "127.0.0.1" }
|
|
629
|
+
map[mac] = ipv4[:"ip-address"] if ipv4
|
|
630
|
+
end
|
|
631
|
+
map
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# Parses a single network config string.
|
|
635
|
+
#
|
|
636
|
+
# @param name [String] net name (e.g., "net0")
|
|
637
|
+
# @param value [String] network config string
|
|
638
|
+
# @param mac_to_ip [Hash] MAC -> IP mapping
|
|
639
|
+
# @return [Hash, nil] parsed network info
|
|
640
|
+
def parse_network_string(name, value, mac_to_ip)
|
|
641
|
+
return nil if value.nil? || value.to_s.empty?
|
|
642
|
+
|
|
643
|
+
parts = value.to_s.split(",")
|
|
644
|
+
model = nil
|
|
645
|
+
mac = nil
|
|
646
|
+
bridge = nil
|
|
647
|
+
firewall = "no"
|
|
648
|
+
|
|
649
|
+
parts.each do |part|
|
|
650
|
+
key, val = part.split("=", 2)
|
|
651
|
+
case key
|
|
652
|
+
when "bridge" then bridge = val
|
|
653
|
+
when "firewall" then firewall = val == "1" ? "yes" : "no"
|
|
654
|
+
when "virtio", "e1000", "rtl8139", "vmxnet3"
|
|
655
|
+
model = key
|
|
656
|
+
mac = val&.upcase
|
|
657
|
+
when "model" then model = val
|
|
658
|
+
when "macaddr" then mac = val&.upcase
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
ip = mac ? mac_to_ip[mac.downcase] : nil
|
|
663
|
+
|
|
664
|
+
{
|
|
665
|
+
"NAME" => name,
|
|
666
|
+
"MODEL" => model || "-",
|
|
667
|
+
"BRIDGE" => bridge || "-",
|
|
668
|
+
"FIREWALL" => firewall,
|
|
669
|
+
"MAC" => mac || "-",
|
|
670
|
+
"IP" => ip || "-"
|
|
671
|
+
}
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# Formats Cloud-Init section.
|
|
675
|
+
#
|
|
676
|
+
# Detects cloud-init presence by checking for CI config keys (ciuser,
|
|
677
|
+
# sshkeys, etc.) OR a cloud-init drive (any disk with "cloudinit" in
|
|
678
|
+
# the volume name). This matches PVE behavior where the Cloud-Init
|
|
679
|
+
# tab appears when the drive exists, even without configuration.
|
|
680
|
+
#
|
|
681
|
+
# @param config [Hash] VM config
|
|
682
|
+
# @return [Hash, String] cloud-init info or "-"
|
|
683
|
+
def format_cloud_init(config)
|
|
684
|
+
ci_keys = %i[citype ciuser cipassword cicustom ciupgrade searchdomain nameserver sshkeys]
|
|
685
|
+
ipconfig_keys = config.keys.select { |k| k.to_s.match?(/^ipconfig\d+$/) }
|
|
686
|
+
consume(*ci_keys)
|
|
687
|
+
consume_matching(config, /^ipconfig\d+$/)
|
|
688
|
+
|
|
689
|
+
ci_drive = config.keys.any? do |k|
|
|
690
|
+
k.to_s.match?(/^(scsi|ide|virtio|sata)\d+$/) && config[k].to_s.include?("cloudinit")
|
|
691
|
+
end
|
|
692
|
+
has_ci = ci_keys.any? { |k| config[k] } || ipconfig_keys.any? || ci_drive
|
|
693
|
+
return "-" unless has_ci
|
|
694
|
+
|
|
695
|
+
result = {
|
|
696
|
+
"User" => config[:ciuser] || "-",
|
|
697
|
+
"Password" => config[:cipassword] ? "set" : "-",
|
|
698
|
+
"DNS Server" => config[:nameserver] || "-",
|
|
699
|
+
"Search Domain" => config[:searchdomain] || "-",
|
|
700
|
+
"SSH Keys" => config[:sshkeys] ? "configured" : "-",
|
|
701
|
+
"Upgrade Packages" => config[:ciupgrade] == 0 ? "No" : "Yes",
|
|
702
|
+
"CI Type" => config[:citype] || "nocloud",
|
|
703
|
+
"CI Custom" => config[:cicustom] || "-"
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if ipconfig_keys.any?
|
|
707
|
+
result["IP Config"] = ipconfig_keys.sort.map do |key|
|
|
708
|
+
{ "INTERFACE" => key.to_s.sub("ipconfig", "net"), "CONFIG" => config[key].to_s }
|
|
709
|
+
end
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
result
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
# Formats USB devices section.
|
|
716
|
+
#
|
|
717
|
+
# @param config [Hash] VM config
|
|
718
|
+
# @return [Array<Hash>, String] USB devices table or "-"
|
|
719
|
+
def format_usb_devices(config)
|
|
720
|
+
usb_keys = config.keys.select { |k| k.to_s.match?(/^usb\d+$/) }
|
|
721
|
+
consume_matching(config, /^usb\d+$/)
|
|
722
|
+
return "-" if usb_keys.empty?
|
|
723
|
+
|
|
724
|
+
usb_keys.sort.map do |key|
|
|
725
|
+
{ "NAME" => key.to_s, "CONFIG" => config[key].to_s }
|
|
726
|
+
end
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
# Formats PCI passthrough section.
|
|
730
|
+
#
|
|
731
|
+
# @param config [Hash] VM config
|
|
732
|
+
# @return [Array<Hash>, String] PCI devices table or "-"
|
|
733
|
+
def format_pci_passthrough(config)
|
|
734
|
+
pci_keys = config.keys.select { |k| k.to_s.match?(/^hostpci\d+$/) }
|
|
735
|
+
consume_matching(config, /^hostpci\d+$/)
|
|
736
|
+
return "-" if pci_keys.empty?
|
|
737
|
+
|
|
738
|
+
pci_keys.sort.map do |key|
|
|
739
|
+
{ "NAME" => key.to_s, "CONFIG" => config[key].to_s }
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
# Formats serial ports section.
|
|
744
|
+
#
|
|
745
|
+
# @param config [Hash] VM config
|
|
746
|
+
# @return [Array<Hash>, String] serial ports table or "-"
|
|
747
|
+
def format_serial_ports(config)
|
|
748
|
+
serial_keys = config.keys.select { |k| k.to_s.match?(/^serial\d+$/) }
|
|
749
|
+
consume_matching(config, /^serial\d+$/)
|
|
750
|
+
return "-" if serial_keys.empty?
|
|
751
|
+
|
|
752
|
+
serial_keys.sort.map do |key|
|
|
753
|
+
{ "NAME" => key.to_s, "TYPE" => config[key].to_s }
|
|
754
|
+
end
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Formats audio device section.
|
|
758
|
+
#
|
|
759
|
+
# @param config [Hash] VM config
|
|
760
|
+
# @return [String] audio config or "-"
|
|
761
|
+
def format_audio(config)
|
|
762
|
+
consume(:audio0)
|
|
763
|
+
config[:audio0]&.to_s || "-"
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Formats EFI disk section.
|
|
767
|
+
#
|
|
768
|
+
# @param config [Hash] VM config
|
|
769
|
+
# @return [String] EFI disk info or "-"
|
|
770
|
+
def format_efi_disk(config)
|
|
771
|
+
consume(:efidisk0)
|
|
772
|
+
config[:efidisk0]&.to_s || "-"
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
# Formats TPM section.
|
|
776
|
+
#
|
|
777
|
+
# @param config [Hash] VM config
|
|
778
|
+
# @return [String] TPM info or "-"
|
|
779
|
+
def format_tpm(config)
|
|
780
|
+
consume(:tpmstate0)
|
|
781
|
+
config[:tpmstate0]&.to_s || "-"
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
# Formats snapshots for table display.
|
|
785
|
+
#
|
|
786
|
+
# @param snapshots [Array<Hash>, nil] snapshots
|
|
787
|
+
# @return [Array<Hash>, String] formatted snapshots or message
|
|
788
|
+
def format_snapshots(snapshots)
|
|
789
|
+
return "No snapshots" if snapshots.nil? || snapshots.empty?
|
|
790
|
+
|
|
791
|
+
snapshots.map do |snap|
|
|
792
|
+
snaptime = snap[:snaptime]
|
|
793
|
+
date = snaptime ? Time.at(snaptime).strftime("%Y-%m-%d %H:%M:%S") : "-"
|
|
794
|
+
|
|
795
|
+
{
|
|
796
|
+
"NAME" => snap[:name],
|
|
797
|
+
"DATE" => date,
|
|
798
|
+
"VMSTATE" => snap[:vmstate] ? "yes" : "no",
|
|
799
|
+
"DESCRIPTION" => snap[:description] || "-"
|
|
800
|
+
}
|
|
801
|
+
end
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
# Formats pending configuration changes.
|
|
805
|
+
#
|
|
806
|
+
# @param pending [Array<Hash>, nil] pending changes from API
|
|
807
|
+
# @return [Array<Hash>, String] pending changes table or "-"
|
|
808
|
+
def format_pending_changes(pending)
|
|
809
|
+
return "No pending changes" if pending.nil? || pending.empty?
|
|
810
|
+
|
|
811
|
+
# Only show entries with actual pending changes (new value or deletion)
|
|
812
|
+
changes = pending.select { |c| c.key?(:pending) || c[:delete] }
|
|
813
|
+
return "No pending changes" if changes.empty?
|
|
814
|
+
|
|
815
|
+
changes.map do |change|
|
|
816
|
+
row = { "KEY" => change[:key].to_s, "CURRENT" => change[:value].to_s }
|
|
817
|
+
row["PENDING"] = change[:pending].to_s if change.key?(:pending)
|
|
818
|
+
row["DELETE"] = "yes" if change[:delete]
|
|
819
|
+
row
|
|
820
|
+
end
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
# Registers config keys as consumed by a format method.
|
|
824
|
+
#
|
|
825
|
+
# @param keys [Array<Symbol>] config keys to mark as consumed
|
|
826
|
+
# @return [void]
|
|
827
|
+
def consume(*keys)
|
|
828
|
+
@consumed_keys.merge(keys.map(&:to_sym))
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
# Consumes all config keys matching a pattern.
|
|
832
|
+
#
|
|
833
|
+
# @param config [Hash] config hash
|
|
834
|
+
# @param pattern [Regexp] pattern to match key names
|
|
835
|
+
# @return [void]
|
|
836
|
+
def consume_matching(config, pattern)
|
|
837
|
+
config.keys.select { |k| k.to_s.match?(pattern) }.each { |k| consume(k) }
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
# Formats remaining unconsumed config keys as catch-all table.
|
|
841
|
+
#
|
|
842
|
+
# @param config [Hash] full config hash
|
|
843
|
+
# @return [Array<Hash>, String] remaining keys table or "-"
|
|
844
|
+
def format_remaining(config)
|
|
845
|
+
excluded = %i[digest]
|
|
846
|
+
remaining = config.keys.map(&:to_sym) - @consumed_keys.to_a - excluded
|
|
847
|
+
return "-" if remaining.empty?
|
|
848
|
+
|
|
849
|
+
remaining.sort.map { |k| { "KEY" => k.to_s, "VALUE" => config[k].to_s } }
|
|
850
|
+
end
|
|
851
|
+
end
|
|
852
|
+
end
|
|
853
|
+
end
|