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.
Files changed (558) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/rules/branch-before-changes.md +52 -0
  3. data/.claude/rules/documentation-updates.md +104 -0
  4. data/.claude/rules/git-workflow.md +84 -0
  5. data/.claude/rules/proxmox-api-docs.md +58 -0
  6. data/.claude/rules/rbs-signatures.md +80 -0
  7. data/.claude/rules/refactoring-as-design-option.md +35 -0
  8. data/.claude/scheduled_tasks.lock +1 -0
  9. data/.claude/settings.json +51 -0
  10. data/.mcp.json +8 -0
  11. data/.ruby-gemset +1 -0
  12. data/.ruby-version +1 -0
  13. data/CHANGELOG.md +138 -0
  14. data/CLAUDE.md +211 -0
  15. data/CODE_OF_CONDUCT.md +132 -0
  16. data/LICENSE.txt +21 -0
  17. data/README.md +143 -0
  18. data/Rakefile +8 -0
  19. data/docs/proxmox-api-update.sh +96 -0
  20. data/exe/pvectl +5 -0
  21. data/lib/pvectl/argv_preprocessor.rb +334 -0
  22. data/lib/pvectl/cli.rb +102 -0
  23. data/lib/pvectl/commands/apt.rb +389 -0
  24. data/lib/pvectl/commands/clone_container.rb +230 -0
  25. data/lib/pvectl/commands/clone_vm.rb +331 -0
  26. data/lib/pvectl/commands/cloudinit/command.rb +122 -0
  27. data/lib/pvectl/commands/cloudinit/dump.rb +94 -0
  28. data/lib/pvectl/commands/cloudinit/pending.rb +137 -0
  29. data/lib/pvectl/commands/cloudinit/regenerate.rb +79 -0
  30. data/lib/pvectl/commands/config/command.rb +65 -0
  31. data/lib/pvectl/commands/config/get_contexts.rb +68 -0
  32. data/lib/pvectl/commands/config/set_cluster.rb +103 -0
  33. data/lib/pvectl/commands/config/set_context.rb +136 -0
  34. data/lib/pvectl/commands/config/set_credentials.rb +181 -0
  35. data/lib/pvectl/commands/config/use_context.rb +69 -0
  36. data/lib/pvectl/commands/config/view.rb +67 -0
  37. data/lib/pvectl/commands/console.rb +93 -0
  38. data/lib/pvectl/commands/console_ct.rb +187 -0
  39. data/lib/pvectl/commands/console_vm.rb +187 -0
  40. data/lib/pvectl/commands/container_lifecycle_command.rb +77 -0
  41. data/lib/pvectl/commands/create_backup.rb +173 -0
  42. data/lib/pvectl/commands/create_container.rb +141 -0
  43. data/lib/pvectl/commands/create_resource_command.rb +244 -0
  44. data/lib/pvectl/commands/create_snapshot.rb +242 -0
  45. data/lib/pvectl/commands/create_vm.rb +267 -0
  46. data/lib/pvectl/commands/delete_backup.rb +139 -0
  47. data/lib/pvectl/commands/delete_command.rb +119 -0
  48. data/lib/pvectl/commands/delete_container.rb +30 -0
  49. data/lib/pvectl/commands/delete_snapshot.rb +248 -0
  50. data/lib/pvectl/commands/delete_vm.rb +127 -0
  51. data/lib/pvectl/commands/describe/command.rb +251 -0
  52. data/lib/pvectl/commands/edit_container.rb +56 -0
  53. data/lib/pvectl/commands/edit_dns.rb +149 -0
  54. data/lib/pvectl/commands/edit_hosts.rb +135 -0
  55. data/lib/pvectl/commands/edit_node.rb +54 -0
  56. data/lib/pvectl/commands/edit_resource_command.rb +180 -0
  57. data/lib/pvectl/commands/edit_vm.rb +154 -0
  58. data/lib/pvectl/commands/edit_volume.rb +189 -0
  59. data/lib/pvectl/commands/feature_command.rb +230 -0
  60. data/lib/pvectl/commands/feature_container.rb +21 -0
  61. data/lib/pvectl/commands/feature_vm.rb +94 -0
  62. data/lib/pvectl/commands/get/command.rb +360 -0
  63. data/lib/pvectl/commands/get/handlers/backups.rb +76 -0
  64. data/lib/pvectl/commands/get/handlers/capabilities.rb +107 -0
  65. data/lib/pvectl/commands/get/handlers/containers.rb +148 -0
  66. data/lib/pvectl/commands/get/handlers/disks.rb +107 -0
  67. data/lib/pvectl/commands/get/handlers/dns.rb +94 -0
  68. data/lib/pvectl/commands/get/handlers/hosts.rb +94 -0
  69. data/lib/pvectl/commands/get/handlers/nodes.rb +162 -0
  70. data/lib/pvectl/commands/get/handlers/services.rb +81 -0
  71. data/lib/pvectl/commands/get/handlers/snapshots.rb +97 -0
  72. data/lib/pvectl/commands/get/handlers/storage.rb +118 -0
  73. data/lib/pvectl/commands/get/handlers/subscription.rb +69 -0
  74. data/lib/pvectl/commands/get/handlers/tasks.rb +89 -0
  75. data/lib/pvectl/commands/get/handlers/templates.rb +175 -0
  76. data/lib/pvectl/commands/get/handlers/time.rb +118 -0
  77. data/lib/pvectl/commands/get/handlers/vms.rb +145 -0
  78. data/lib/pvectl/commands/get/handlers/volume.rb +134 -0
  79. data/lib/pvectl/commands/get/resource_handler.rb +63 -0
  80. data/lib/pvectl/commands/get/resource_registry.rb +18 -0
  81. data/lib/pvectl/commands/get/watch_loop.rb +129 -0
  82. data/lib/pvectl/commands/irreversible_command.rb +265 -0
  83. data/lib/pvectl/commands/logs/command.rb +275 -0
  84. data/lib/pvectl/commands/logs/handlers/journal.rb +46 -0
  85. data/lib/pvectl/commands/logs/handlers/syslog.rb +53 -0
  86. data/lib/pvectl/commands/logs/handlers/task_detail.rb +52 -0
  87. data/lib/pvectl/commands/logs/handlers/task_logs.rb +115 -0
  88. data/lib/pvectl/commands/logs/resource_handler.rb +46 -0
  89. data/lib/pvectl/commands/logs/resource_registry.rb +22 -0
  90. data/lib/pvectl/commands/migrate_command.rb +282 -0
  91. data/lib/pvectl/commands/migrate_container.rb +23 -0
  92. data/lib/pvectl/commands/migrate_vm.rb +122 -0
  93. data/lib/pvectl/commands/move_disk_command.rb +239 -0
  94. data/lib/pvectl/commands/move_disk_container.rb +21 -0
  95. data/lib/pvectl/commands/move_disk_vm.rb +127 -0
  96. data/lib/pvectl/commands/ping.rb +249 -0
  97. data/lib/pvectl/commands/pull.rb +342 -0
  98. data/lib/pvectl/commands/push.rb +352 -0
  99. data/lib/pvectl/commands/reset.rb +64 -0
  100. data/lib/pvectl/commands/resource_lifecycle_command.rb +277 -0
  101. data/lib/pvectl/commands/resource_registry.rb +73 -0
  102. data/lib/pvectl/commands/restart.rb +70 -0
  103. data/lib/pvectl/commands/restart_container.rb +18 -0
  104. data/lib/pvectl/commands/restore_backup.rb +236 -0
  105. data/lib/pvectl/commands/resume.rb +57 -0
  106. data/lib/pvectl/commands/rollback_snapshot.rb +228 -0
  107. data/lib/pvectl/commands/sendkey_vm.rb +205 -0
  108. data/lib/pvectl/commands/service.rb +293 -0
  109. data/lib/pvectl/commands/set_container.rb +50 -0
  110. data/lib/pvectl/commands/set_node.rb +52 -0
  111. data/lib/pvectl/commands/set_resource_command.rb +185 -0
  112. data/lib/pvectl/commands/set_vm.rb +136 -0
  113. data/lib/pvectl/commands/set_volume.rb +212 -0
  114. data/lib/pvectl/commands/shared_config_parsers.rb +126 -0
  115. data/lib/pvectl/commands/shared_flags.rb +155 -0
  116. data/lib/pvectl/commands/shutdown.rb +73 -0
  117. data/lib/pvectl/commands/shutdown_container.rb +18 -0
  118. data/lib/pvectl/commands/start.rb +79 -0
  119. data/lib/pvectl/commands/start_container.rb +18 -0
  120. data/lib/pvectl/commands/stop.rb +75 -0
  121. data/lib/pvectl/commands/stop_container.rb +18 -0
  122. data/lib/pvectl/commands/suspend.rb +64 -0
  123. data/lib/pvectl/commands/template_command.rb +205 -0
  124. data/lib/pvectl/commands/template_container.rb +27 -0
  125. data/lib/pvectl/commands/template_vm.rb +106 -0
  126. data/lib/pvectl/commands/top/command.rb +206 -0
  127. data/lib/pvectl/commands/top/handlers/containers.rb +61 -0
  128. data/lib/pvectl/commands/top/handlers/nodes.rb +61 -0
  129. data/lib/pvectl/commands/top/handlers/vms.rb +61 -0
  130. data/lib/pvectl/commands/top/resource_handler.rb +46 -0
  131. data/lib/pvectl/commands/top/resource_registry.rb +22 -0
  132. data/lib/pvectl/commands/unlink_disk_vm.rb +232 -0
  133. data/lib/pvectl/commands/vm_lifecycle_command.rb +77 -0
  134. data/lib/pvectl/commands/wakeonlan_node.rb +153 -0
  135. data/lib/pvectl/config/errors.rb +62 -0
  136. data/lib/pvectl/config/models/cluster.rb +180 -0
  137. data/lib/pvectl/config/models/context.rb +100 -0
  138. data/lib/pvectl/config/models/resolved_config.rb +171 -0
  139. data/lib/pvectl/config/models/user.rb +133 -0
  140. data/lib/pvectl/config/provider.rb +297 -0
  141. data/lib/pvectl/config/service.rb +300 -0
  142. data/lib/pvectl/config/store.rb +161 -0
  143. data/lib/pvectl/config/wizard.rb +309 -0
  144. data/lib/pvectl/config_serializer.rb +1034 -0
  145. data/lib/pvectl/connection/retry_handler.rb +161 -0
  146. data/lib/pvectl/connection.rb +157 -0
  147. data/lib/pvectl/console/terminal_session.rb +449 -0
  148. data/lib/pvectl/editor_session.rb +157 -0
  149. data/lib/pvectl/exit_codes.rb +43 -0
  150. data/lib/pvectl/formatters/base.rb +55 -0
  151. data/lib/pvectl/formatters/color_support.rb +90 -0
  152. data/lib/pvectl/formatters/json.rb +45 -0
  153. data/lib/pvectl/formatters/output_helper.rb +77 -0
  154. data/lib/pvectl/formatters/registry.rb +72 -0
  155. data/lib/pvectl/formatters/table.rb +235 -0
  156. data/lib/pvectl/formatters/wide.rb +93 -0
  157. data/lib/pvectl/formatters/yaml.rb +49 -0
  158. data/lib/pvectl/manifest_serializer.rb +142 -0
  159. data/lib/pvectl/models/apt_package.rb +107 -0
  160. data/lib/pvectl/models/backup.rb +173 -0
  161. data/lib/pvectl/models/base.rb +49 -0
  162. data/lib/pvectl/models/capability.rb +62 -0
  163. data/lib/pvectl/models/container.rb +205 -0
  164. data/lib/pvectl/models/container_operation_result.rb +27 -0
  165. data/lib/pvectl/models/dns_config.rb +54 -0
  166. data/lib/pvectl/models/hosts_file.rb +47 -0
  167. data/lib/pvectl/models/journal_entry.rb +16 -0
  168. data/lib/pvectl/models/network_interface.rb +85 -0
  169. data/lib/pvectl/models/node.rb +195 -0
  170. data/lib/pvectl/models/node_operation_result.rb +45 -0
  171. data/lib/pvectl/models/operation_result.rb +110 -0
  172. data/lib/pvectl/models/physical_disk.rb +193 -0
  173. data/lib/pvectl/models/service.rb +80 -0
  174. data/lib/pvectl/models/snapshot.rb +101 -0
  175. data/lib/pvectl/models/snapshot_description.rb +39 -0
  176. data/lib/pvectl/models/storage.rb +180 -0
  177. data/lib/pvectl/models/subscription.rb +87 -0
  178. data/lib/pvectl/models/syslog_entry.rb +17 -0
  179. data/lib/pvectl/models/task.rb +95 -0
  180. data/lib/pvectl/models/task_entry.rb +52 -0
  181. data/lib/pvectl/models/task_log_line.rb +17 -0
  182. data/lib/pvectl/models/time_config.rb +47 -0
  183. data/lib/pvectl/models/vm.rb +137 -0
  184. data/lib/pvectl/models/vm_operation_result.rb +27 -0
  185. data/lib/pvectl/models/volume.rb +133 -0
  186. data/lib/pvectl/models/volume_operation_result.rb +26 -0
  187. data/lib/pvectl/parsers/cloud_init_config.rb +92 -0
  188. data/lib/pvectl/parsers/disk_config.rb +97 -0
  189. data/lib/pvectl/parsers/lxc_mount_config.rb +98 -0
  190. data/lib/pvectl/parsers/lxc_net_config.rb +97 -0
  191. data/lib/pvectl/parsers/net_config.rb +95 -0
  192. data/lib/pvectl/parsers/smart_text.rb +42 -0
  193. data/lib/pvectl/plugin_loader.rb +157 -0
  194. data/lib/pvectl/presenters/apt_package.rb +99 -0
  195. data/lib/pvectl/presenters/backup.rb +128 -0
  196. data/lib/pvectl/presenters/base.rb +283 -0
  197. data/lib/pvectl/presenters/capability.rb +104 -0
  198. data/lib/pvectl/presenters/config/context.rb +80 -0
  199. data/lib/pvectl/presenters/container.rb +574 -0
  200. data/lib/pvectl/presenters/container_operation_result.rb +109 -0
  201. data/lib/pvectl/presenters/disk.rb +184 -0
  202. data/lib/pvectl/presenters/dns_config.rb +68 -0
  203. data/lib/pvectl/presenters/hosts_file.rb +61 -0
  204. data/lib/pvectl/presenters/journal_entry.rb +20 -0
  205. data/lib/pvectl/presenters/node.rb +762 -0
  206. data/lib/pvectl/presenters/node_operation_result.rb +50 -0
  207. data/lib/pvectl/presenters/operation_result.rb +61 -0
  208. data/lib/pvectl/presenters/service.rb +76 -0
  209. data/lib/pvectl/presenters/snapshot.rb +239 -0
  210. data/lib/pvectl/presenters/snapshot_operation_result.rb +125 -0
  211. data/lib/pvectl/presenters/storage.rb +329 -0
  212. data/lib/pvectl/presenters/subscription.rb +189 -0
  213. data/lib/pvectl/presenters/syslog_entry.rb +20 -0
  214. data/lib/pvectl/presenters/task_entry.rb +69 -0
  215. data/lib/pvectl/presenters/task_log_line.rb +20 -0
  216. data/lib/pvectl/presenters/template.rb +76 -0
  217. data/lib/pvectl/presenters/time_config.rb +86 -0
  218. data/lib/pvectl/presenters/top_container.rb +112 -0
  219. data/lib/pvectl/presenters/top_node.rb +115 -0
  220. data/lib/pvectl/presenters/top_presenter.rb +59 -0
  221. data/lib/pvectl/presenters/top_vm.rb +105 -0
  222. data/lib/pvectl/presenters/vm.rb +853 -0
  223. data/lib/pvectl/presenters/vm_operation_result.rb +109 -0
  224. data/lib/pvectl/presenters/volume.rb +136 -0
  225. data/lib/pvectl/presenters/volume_operation_result.rb +58 -0
  226. data/lib/pvectl/repositories/apt.rb +93 -0
  227. data/lib/pvectl/repositories/backup.rb +186 -0
  228. data/lib/pvectl/repositories/base.rb +110 -0
  229. data/lib/pvectl/repositories/capabilities.rb +96 -0
  230. data/lib/pvectl/repositories/container.rb +503 -0
  231. data/lib/pvectl/repositories/disk.rb +87 -0
  232. data/lib/pvectl/repositories/dns.rb +54 -0
  233. data/lib/pvectl/repositories/hosts.rb +63 -0
  234. data/lib/pvectl/repositories/journal.rb +23 -0
  235. data/lib/pvectl/repositories/node.rb +537 -0
  236. data/lib/pvectl/repositories/service.rb +139 -0
  237. data/lib/pvectl/repositories/snapshot.rb +133 -0
  238. data/lib/pvectl/repositories/storage.rb +302 -0
  239. data/lib/pvectl/repositories/subscription.rb +77 -0
  240. data/lib/pvectl/repositories/syslog.rb +25 -0
  241. data/lib/pvectl/repositories/task.rb +82 -0
  242. data/lib/pvectl/repositories/task_list.rb +30 -0
  243. data/lib/pvectl/repositories/task_log.rb +31 -0
  244. data/lib/pvectl/repositories/time_config.rb +53 -0
  245. data/lib/pvectl/repositories/vm.rb +616 -0
  246. data/lib/pvectl/repositories/volume.rb +306 -0
  247. data/lib/pvectl/selectors/base.rb +201 -0
  248. data/lib/pvectl/selectors/container.rb +116 -0
  249. data/lib/pvectl/selectors/disk.rb +59 -0
  250. data/lib/pvectl/selectors/vm.rb +116 -0
  251. data/lib/pvectl/selectors/volume.rb +59 -0
  252. data/lib/pvectl/services/backup.rb +209 -0
  253. data/lib/pvectl/services/clone_container.rb +260 -0
  254. data/lib/pvectl/services/clone_vm.rb +265 -0
  255. data/lib/pvectl/services/cloudinit.rb +96 -0
  256. data/lib/pvectl/services/console.rb +152 -0
  257. data/lib/pvectl/services/container_lifecycle.rb +124 -0
  258. data/lib/pvectl/services/create_container.rb +179 -0
  259. data/lib/pvectl/services/create_vm.rb +191 -0
  260. data/lib/pvectl/services/edit_container.rb +125 -0
  261. data/lib/pvectl/services/edit_dns.rb +159 -0
  262. data/lib/pvectl/services/edit_hosts.rb +78 -0
  263. data/lib/pvectl/services/edit_node.rb +147 -0
  264. data/lib/pvectl/services/edit_vm.rb +125 -0
  265. data/lib/pvectl/services/edit_volume.rb +224 -0
  266. data/lib/pvectl/services/get/resource_service.rb +98 -0
  267. data/lib/pvectl/services/move_disk.rb +132 -0
  268. data/lib/pvectl/services/pull_config.rb +94 -0
  269. data/lib/pvectl/services/push_config.rb +524 -0
  270. data/lib/pvectl/services/resize_volume.rb +253 -0
  271. data/lib/pvectl/services/resource_delete.rb +169 -0
  272. data/lib/pvectl/services/resource_migration.rb +170 -0
  273. data/lib/pvectl/services/sendkey.rb +108 -0
  274. data/lib/pvectl/services/service_lifecycle.rb +89 -0
  275. data/lib/pvectl/services/set_container.rb +128 -0
  276. data/lib/pvectl/services/set_node.rb +236 -0
  277. data/lib/pvectl/services/set_vm.rb +128 -0
  278. data/lib/pvectl/services/set_volume.rb +126 -0
  279. data/lib/pvectl/services/snapshot.rb +261 -0
  280. data/lib/pvectl/services/task_listing.rb +75 -0
  281. data/lib/pvectl/services/unlink_disk.rb +86 -0
  282. data/lib/pvectl/services/vm_lifecycle.rb +124 -0
  283. data/lib/pvectl/services/wakeonlan.rb +79 -0
  284. data/lib/pvectl/utils/resource_resolver.rb +80 -0
  285. data/lib/pvectl/version.rb +13 -0
  286. data/lib/pvectl/wizards/create_container.rb +105 -0
  287. data/lib/pvectl/wizards/create_vm.rb +98 -0
  288. data/lib/pvectl.rb +439 -0
  289. data/sig/external/gli.rbs +16 -0
  290. data/sig/external/proxmox_api.rbs +10 -0
  291. data/sig/pvectl/argv_preprocessor.rbs +53 -0
  292. data/sig/pvectl/cli.rbs +26 -0
  293. data/sig/pvectl/commands/apt.rbs +47 -0
  294. data/sig/pvectl/commands/clone_container.rbs +31 -0
  295. data/sig/pvectl/commands/clone_vm.rbs +33 -0
  296. data/sig/pvectl/commands/cloudinit/command.rbs +13 -0
  297. data/sig/pvectl/commands/cloudinit/dump.rbs +13 -0
  298. data/sig/pvectl/commands/cloudinit/pending.rbs +17 -0
  299. data/sig/pvectl/commands/cloudinit/regenerate.rbs +11 -0
  300. data/sig/pvectl/commands/config/command.rbs +9 -0
  301. data/sig/pvectl/commands/config/get_contexts.rbs +11 -0
  302. data/sig/pvectl/commands/config/set_cluster.rbs +11 -0
  303. data/sig/pvectl/commands/config/set_context.rbs +15 -0
  304. data/sig/pvectl/commands/config/set_credentials.rbs +15 -0
  305. data/sig/pvectl/commands/config/use_context.rbs +11 -0
  306. data/sig/pvectl/commands/config/view.rbs +11 -0
  307. data/sig/pvectl/commands/console.rbs +9 -0
  308. data/sig/pvectl/commands/console_ct.rbs +27 -0
  309. data/sig/pvectl/commands/console_vm.rbs +27 -0
  310. data/sig/pvectl/commands/container_lifecycle_command.rbs +25 -0
  311. data/sig/pvectl/commands/create_backup.rbs +29 -0
  312. data/sig/pvectl/commands/create_container.rbs +30 -0
  313. data/sig/pvectl/commands/create_resource_command.rbs +53 -0
  314. data/sig/pvectl/commands/create_snapshot.rbs +35 -0
  315. data/sig/pvectl/commands/create_vm.rbs +30 -0
  316. data/sig/pvectl/commands/delete_backup.rbs +25 -0
  317. data/sig/pvectl/commands/delete_command.rbs +45 -0
  318. data/sig/pvectl/commands/delete_container.rbs +11 -0
  319. data/sig/pvectl/commands/delete_snapshot.rbs +35 -0
  320. data/sig/pvectl/commands/delete_vm.rbs +13 -0
  321. data/sig/pvectl/commands/describe/command.rbs +27 -0
  322. data/sig/pvectl/commands/edit_container.rbs +17 -0
  323. data/sig/pvectl/commands/edit_dns.rbs +25 -0
  324. data/sig/pvectl/commands/edit_hosts.rbs +23 -0
  325. data/sig/pvectl/commands/edit_node.rbs +17 -0
  326. data/sig/pvectl/commands/edit_resource_command.rbs +35 -0
  327. data/sig/pvectl/commands/edit_vm.rbs +19 -0
  328. data/sig/pvectl/commands/edit_volume.rbs +24 -0
  329. data/sig/pvectl/commands/feature_command.rbs +43 -0
  330. data/sig/pvectl/commands/feature_container.rbs +10 -0
  331. data/sig/pvectl/commands/feature_vm.rbs +12 -0
  332. data/sig/pvectl/commands/get/command.rbs +42 -0
  333. data/sig/pvectl/commands/get/handlers/backups.rbs +23 -0
  334. data/sig/pvectl/commands/get/handlers/capabilities.rbs +29 -0
  335. data/sig/pvectl/commands/get/handlers/containers.rbs +35 -0
  336. data/sig/pvectl/commands/get/handlers/disks.rbs +27 -0
  337. data/sig/pvectl/commands/get/handlers/dns.rbs +25 -0
  338. data/sig/pvectl/commands/get/handlers/hosts.rbs +25 -0
  339. data/sig/pvectl/commands/get/handlers/nodes.rbs +33 -0
  340. data/sig/pvectl/commands/get/handlers/services.rbs +23 -0
  341. data/sig/pvectl/commands/get/handlers/snapshots.rbs +27 -0
  342. data/sig/pvectl/commands/get/handlers/storage.rbs +25 -0
  343. data/sig/pvectl/commands/get/handlers/subscription.rbs +25 -0
  344. data/sig/pvectl/commands/get/handlers/tasks.rbs +28 -0
  345. data/sig/pvectl/commands/get/handlers/templates.rbs +35 -0
  346. data/sig/pvectl/commands/get/handlers/time.rbs +29 -0
  347. data/sig/pvectl/commands/get/handlers/vms.rbs +35 -0
  348. data/sig/pvectl/commands/get/handlers/volume.rbs +27 -0
  349. data/sig/pvectl/commands/get/resource_handler.rbs +13 -0
  350. data/sig/pvectl/commands/get/resource_registry.rbs +8 -0
  351. data/sig/pvectl/commands/get/watch_loop.rbs +33 -0
  352. data/sig/pvectl/commands/irreversible_command.rbs +32 -0
  353. data/sig/pvectl/commands/logs/command.rbs +35 -0
  354. data/sig/pvectl/commands/logs/handlers/journal.rbs +21 -0
  355. data/sig/pvectl/commands/logs/handlers/syslog.rbs +21 -0
  356. data/sig/pvectl/commands/logs/handlers/task_detail.rbs +21 -0
  357. data/sig/pvectl/commands/logs/handlers/task_logs.rbs +35 -0
  358. data/sig/pvectl/commands/logs/resource_handler.rbs +11 -0
  359. data/sig/pvectl/commands/logs/resource_registry.rbs +8 -0
  360. data/sig/pvectl/commands/migrate_command.rbs +45 -0
  361. data/sig/pvectl/commands/migrate_container.rbs +11 -0
  362. data/sig/pvectl/commands/migrate_vm.rbs +13 -0
  363. data/sig/pvectl/commands/move_disk_command.rbs +43 -0
  364. data/sig/pvectl/commands/move_disk_container.rbs +11 -0
  365. data/sig/pvectl/commands/move_disk_vm.rbs +13 -0
  366. data/sig/pvectl/commands/ping.rbs +39 -0
  367. data/sig/pvectl/commands/pull.rbs +33 -0
  368. data/sig/pvectl/commands/push.rbs +32 -0
  369. data/sig/pvectl/commands/reset.rbs +11 -0
  370. data/sig/pvectl/commands/resource_lifecycle_command.rbs +55 -0
  371. data/sig/pvectl/commands/resource_registry.rbs +19 -0
  372. data/sig/pvectl/commands/restart.rbs +11 -0
  373. data/sig/pvectl/commands/restart_container.rbs +9 -0
  374. data/sig/pvectl/commands/restore_backup.rbs +27 -0
  375. data/sig/pvectl/commands/resume.rbs +11 -0
  376. data/sig/pvectl/commands/rollback_snapshot.rbs +31 -0
  377. data/sig/pvectl/commands/sendkey_vm.rbs +25 -0
  378. data/sig/pvectl/commands/service.rbs +38 -0
  379. data/sig/pvectl/commands/set_container.rbs +13 -0
  380. data/sig/pvectl/commands/set_node.rbs +13 -0
  381. data/sig/pvectl/commands/set_resource_command.rbs +25 -0
  382. data/sig/pvectl/commands/set_vm.rbs +15 -0
  383. data/sig/pvectl/commands/set_volume.rbs +24 -0
  384. data/sig/pvectl/commands/shared_config_parsers.rbs +19 -0
  385. data/sig/pvectl/commands/shared_flags.rbs +10 -0
  386. data/sig/pvectl/commands/shutdown.rbs +11 -0
  387. data/sig/pvectl/commands/shutdown_container.rbs +9 -0
  388. data/sig/pvectl/commands/start.rbs +11 -0
  389. data/sig/pvectl/commands/start_container.rbs +9 -0
  390. data/sig/pvectl/commands/stop.rbs +11 -0
  391. data/sig/pvectl/commands/stop_container.rbs +9 -0
  392. data/sig/pvectl/commands/suspend.rbs +11 -0
  393. data/sig/pvectl/commands/template_command.rbs +21 -0
  394. data/sig/pvectl/commands/template_container.rbs +10 -0
  395. data/sig/pvectl/commands/template_vm.rbs +12 -0
  396. data/sig/pvectl/commands/top/command.rbs +31 -0
  397. data/sig/pvectl/commands/top/handlers/containers.rbs +21 -0
  398. data/sig/pvectl/commands/top/handlers/nodes.rbs +21 -0
  399. data/sig/pvectl/commands/top/handlers/vms.rbs +21 -0
  400. data/sig/pvectl/commands/top/resource_handler.rbs +11 -0
  401. data/sig/pvectl/commands/top/resource_registry.rbs +8 -0
  402. data/sig/pvectl/commands/unlink_disk_vm.rbs +27 -0
  403. data/sig/pvectl/commands/vm_lifecycle_command.rbs +25 -0
  404. data/sig/pvectl/commands/wakeonlan_node.rbs +21 -0
  405. data/sig/pvectl/config/errors.rbs +24 -0
  406. data/sig/pvectl/config/models/cluster.rbs +39 -0
  407. data/sig/pvectl/config/models/context.rbs +23 -0
  408. data/sig/pvectl/config/models/resolved_config.rbs +51 -0
  409. data/sig/pvectl/config/models/user.rbs +31 -0
  410. data/sig/pvectl/config/provider.rbs +40 -0
  411. data/sig/pvectl/config/service.rbs +65 -0
  412. data/sig/pvectl/config/store.rbs +14 -0
  413. data/sig/pvectl/config/wizard.rbs +48 -0
  414. data/sig/pvectl/config_serializer.rbs +121 -0
  415. data/sig/pvectl/connection/retry_handler.rbs +31 -0
  416. data/sig/pvectl/connection.rbs +35 -0
  417. data/sig/pvectl/console/terminal_session.rbs +63 -0
  418. data/sig/pvectl/editor_session.rbs +33 -0
  419. data/sig/pvectl/exit_codes.rbs +19 -0
  420. data/sig/pvectl/formatters/base.rbs +13 -0
  421. data/sig/pvectl/formatters/color_support.rbs +13 -0
  422. data/sig/pvectl/formatters/json.rbs +7 -0
  423. data/sig/pvectl/formatters/output_helper.rbs +9 -0
  424. data/sig/pvectl/formatters/registry.rbs +13 -0
  425. data/sig/pvectl/formatters/table.rbs +25 -0
  426. data/sig/pvectl/formatters/wide.rbs +15 -0
  427. data/sig/pvectl/formatters/yaml.rbs +7 -0
  428. data/sig/pvectl/manifest_serializer.rbs +18 -0
  429. data/sig/pvectl/models/apt_package.rbs +26 -0
  430. data/sig/pvectl/models/backup.rbs +31 -0
  431. data/sig/pvectl/models/base.rbs +11 -0
  432. data/sig/pvectl/models/capability.rbs +16 -0
  433. data/sig/pvectl/models/container.rbs +44 -0
  434. data/sig/pvectl/models/container_operation_result.rbs +9 -0
  435. data/sig/pvectl/models/dns_config.rbs +15 -0
  436. data/sig/pvectl/models/hosts_file.rbs +13 -0
  437. data/sig/pvectl/models/journal_entry.rbs +10 -0
  438. data/sig/pvectl/models/network_interface.rbs +20 -0
  439. data/sig/pvectl/models/node.rbs +47 -0
  440. data/sig/pvectl/models/node_operation_result.rbs +12 -0
  441. data/sig/pvectl/models/operation_result.rbs +21 -0
  442. data/sig/pvectl/models/physical_disk.rbs +35 -0
  443. data/sig/pvectl/models/service.rbs +18 -0
  444. data/sig/pvectl/models/snapshot.rbs +21 -0
  445. data/sig/pvectl/models/snapshot_description.rbs +18 -0
  446. data/sig/pvectl/models/storage.rbs +39 -0
  447. data/sig/pvectl/models/subscription.rbs +24 -0
  448. data/sig/pvectl/models/syslog_entry.rbs +10 -0
  449. data/sig/pvectl/models/task.rbs +22 -0
  450. data/sig/pvectl/models/task_entry.rbs +24 -0
  451. data/sig/pvectl/models/task_log_line.rbs +10 -0
  452. data/sig/pvectl/models/time_config.rbs +12 -0
  453. data/sig/pvectl/models/vm.rbs +32 -0
  454. data/sig/pvectl/models/vm_operation_result.rbs +9 -0
  455. data/sig/pvectl/models/volume.rbs +29 -0
  456. data/sig/pvectl/models/volume_operation_result.rbs +9 -0
  457. data/sig/pvectl/parsers/cloud_init_config.rbs +15 -0
  458. data/sig/pvectl/parsers/disk_config.rbs +19 -0
  459. data/sig/pvectl/parsers/lxc_mount_config.rbs +19 -0
  460. data/sig/pvectl/parsers/lxc_net_config.rbs +19 -0
  461. data/sig/pvectl/parsers/net_config.rbs +19 -0
  462. data/sig/pvectl/parsers/smart_text.rbs +7 -0
  463. data/sig/pvectl/plugin_loader.rbs +25 -0
  464. data/sig/pvectl/presenters/apt_package.rbs +19 -0
  465. data/sig/pvectl/presenters/backup.rbs +25 -0
  466. data/sig/pvectl/presenters/base.rbs +41 -0
  467. data/sig/pvectl/presenters/capability.rbs +19 -0
  468. data/sig/pvectl/presenters/config/context.rbs +17 -0
  469. data/sig/pvectl/presenters/container.rbs +78 -0
  470. data/sig/pvectl/presenters/container_operation_result.rbs +19 -0
  471. data/sig/pvectl/presenters/disk.rbs +31 -0
  472. data/sig/pvectl/presenters/dns_config.rbs +13 -0
  473. data/sig/pvectl/presenters/hosts_file.rbs +13 -0
  474. data/sig/pvectl/presenters/journal_entry.rbs +11 -0
  475. data/sig/pvectl/presenters/node.rbs +118 -0
  476. data/sig/pvectl/presenters/node_operation_result.rbs +11 -0
  477. data/sig/pvectl/presenters/operation_result.rbs +17 -0
  478. data/sig/pvectl/presenters/service.rbs +15 -0
  479. data/sig/pvectl/presenters/snapshot.rbs +35 -0
  480. data/sig/pvectl/presenters/snapshot_operation_result.rbs +27 -0
  481. data/sig/pvectl/presenters/storage.rbs +59 -0
  482. data/sig/pvectl/presenters/subscription.rbs +36 -0
  483. data/sig/pvectl/presenters/syslog_entry.rbs +11 -0
  484. data/sig/pvectl/presenters/task_entry.rbs +21 -0
  485. data/sig/pvectl/presenters/task_log_line.rbs +11 -0
  486. data/sig/pvectl/presenters/template.rbs +15 -0
  487. data/sig/pvectl/presenters/time_config.rbs +19 -0
  488. data/sig/pvectl/presenters/top_container.rbs +17 -0
  489. data/sig/pvectl/presenters/top_node.rbs +17 -0
  490. data/sig/pvectl/presenters/top_presenter.rbs +13 -0
  491. data/sig/pvectl/presenters/top_vm.rbs +17 -0
  492. data/sig/pvectl/presenters/vm.rbs +91 -0
  493. data/sig/pvectl/presenters/vm_operation_result.rbs +19 -0
  494. data/sig/pvectl/presenters/volume.rbs +23 -0
  495. data/sig/pvectl/presenters/volume_operation_result.rbs +11 -0
  496. data/sig/pvectl/repositories/apt.rbs +17 -0
  497. data/sig/pvectl/repositories/backup.rbs +27 -0
  498. data/sig/pvectl/repositories/base.rbs +23 -0
  499. data/sig/pvectl/repositories/capabilities.rbs +20 -0
  500. data/sig/pvectl/repositories/container.rbs +63 -0
  501. data/sig/pvectl/repositories/disk.rbs +17 -0
  502. data/sig/pvectl/repositories/dns.rbs +13 -0
  503. data/sig/pvectl/repositories/hosts.rbs +13 -0
  504. data/sig/pvectl/repositories/journal.rbs +7 -0
  505. data/sig/pvectl/repositories/node.rbs +68 -0
  506. data/sig/pvectl/repositories/service.rbs +27 -0
  507. data/sig/pvectl/repositories/snapshot.rbs +19 -0
  508. data/sig/pvectl/repositories/storage.rbs +37 -0
  509. data/sig/pvectl/repositories/subscription.rbs +17 -0
  510. data/sig/pvectl/repositories/syslog.rbs +7 -0
  511. data/sig/pvectl/repositories/task.rbs +19 -0
  512. data/sig/pvectl/repositories/task_list.rbs +7 -0
  513. data/sig/pvectl/repositories/task_log.rbs +11 -0
  514. data/sig/pvectl/repositories/time_config.rbs +13 -0
  515. data/sig/pvectl/repositories/vm.rbs +85 -0
  516. data/sig/pvectl/repositories/volume.rbs +43 -0
  517. data/sig/pvectl/selectors/base.rbs +37 -0
  518. data/sig/pvectl/selectors/container.rbs +19 -0
  519. data/sig/pvectl/selectors/disk.rbs +13 -0
  520. data/sig/pvectl/selectors/vm.rbs +19 -0
  521. data/sig/pvectl/selectors/volume.rbs +13 -0
  522. data/sig/pvectl/services/backup.rbs +27 -0
  523. data/sig/pvectl/services/clone_container.rbs +35 -0
  524. data/sig/pvectl/services/clone_vm.rbs +35 -0
  525. data/sig/pvectl/services/cloudinit.rbs +19 -0
  526. data/sig/pvectl/services/console.rbs +23 -0
  527. data/sig/pvectl/services/container_lifecycle.rbs +26 -0
  528. data/sig/pvectl/services/create_container.rbs +64 -0
  529. data/sig/pvectl/services/create_vm.rbs +72 -0
  530. data/sig/pvectl/services/edit_container.rbs +17 -0
  531. data/sig/pvectl/services/edit_dns.rbs +23 -0
  532. data/sig/pvectl/services/edit_hosts.rbs +13 -0
  533. data/sig/pvectl/services/edit_node.rbs +21 -0
  534. data/sig/pvectl/services/edit_vm.rbs +17 -0
  535. data/sig/pvectl/services/edit_volume.rbs +18 -0
  536. data/sig/pvectl/services/get/resource_service.rbs +23 -0
  537. data/sig/pvectl/services/move_disk.rbs +21 -0
  538. data/sig/pvectl/services/pull_config.rbs +18 -0
  539. data/sig/pvectl/services/push_config.rbs +37 -0
  540. data/sig/pvectl/services/resize_volume.rbs +47 -0
  541. data/sig/pvectl/services/resource_delete.rbs +27 -0
  542. data/sig/pvectl/services/resource_migration.rbs +29 -0
  543. data/sig/pvectl/services/sendkey.rbs +19 -0
  544. data/sig/pvectl/services/service_lifecycle.rbs +17 -0
  545. data/sig/pvectl/services/set_container.rbs +14 -0
  546. data/sig/pvectl/services/set_node.rbs +23 -0
  547. data/sig/pvectl/services/set_vm.rbs +14 -0
  548. data/sig/pvectl/services/set_volume.rbs +12 -0
  549. data/sig/pvectl/services/snapshot.rbs +35 -0
  550. data/sig/pvectl/services/task_listing.rbs +13 -0
  551. data/sig/pvectl/services/unlink_disk.rbs +17 -0
  552. data/sig/pvectl/services/vm_lifecycle.rbs +26 -0
  553. data/sig/pvectl/services/wakeonlan.rbs +17 -0
  554. data/sig/pvectl/utils/resource_resolver.rbs +17 -0
  555. data/sig/pvectl/wizards/create_container.rbs +21 -0
  556. data/sig/pvectl/wizards/create_vm.rbs +21 -0
  557. data/sig/pvectl.rbs +9 -0
  558. metadata +675 -0
@@ -0,0 +1,762 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pvectl
4
+ module Presenters
5
+ # Presenter for Proxmox cluster nodes.
6
+ #
7
+ # Defines column layout and formatting for table output.
8
+ # Standard columns show essential node info.
9
+ # Wide columns add detailed metrics (load, swap, storage, kernel).
10
+ #
11
+ # @example Using with formatter
12
+ # presenter = Node.new
13
+ # formatter = Formatters::Table.new
14
+ # output = formatter.format(nodes, presenter)
15
+ #
16
+ # @see Pvectl::Models::Node Node model
17
+ # @see Pvectl::Formatters::Table Table formatter
18
+ #
19
+ class Node < Base
20
+ # Returns column headers for standard table output.
21
+ #
22
+ # @return [Array<String>] column headers
23
+ def columns
24
+ %w[NAME STATUS VERSION CPU MEMORY GUESTS UPTIME]
25
+ end
26
+
27
+ # Returns additional column headers for wide output.
28
+ #
29
+ # @return [Array<String>] extra column headers
30
+ def extra_columns
31
+ %w[LOAD SWAP STORAGE VMS CTS KERNEL IP]
32
+ end
33
+
34
+ # Converts Node model to table row values.
35
+ #
36
+ # @param model [Models::Node] Node model
37
+ # @param context [Hash] optional context
38
+ # @return [Array<String>] row values matching columns order
39
+ def to_row(model, **_context)
40
+ @node = model
41
+ [
42
+ node.name,
43
+ node.status,
44
+ version_display,
45
+ cpu_percent,
46
+ memory_display,
47
+ node.guests_total.to_s,
48
+ uptime_human
49
+ ]
50
+ end
51
+
52
+ # Returns additional values for wide output.
53
+ #
54
+ # @param model [Models::Node] Node model
55
+ # @param context [Hash] optional context
56
+ # @return [Array<String>] extra values matching extra_columns order
57
+ def extra_values(model, **_context)
58
+ @node = model
59
+ [
60
+ load_display,
61
+ swap_display,
62
+ storage_display,
63
+ node.guests_vms.to_s,
64
+ node.guests_cts.to_s,
65
+ kernel_display,
66
+ ip_display
67
+ ]
68
+ end
69
+
70
+ # Converts Node model to hash for JSON/YAML output.
71
+ #
72
+ # Returns a structured hash with nested objects for complex data
73
+ # like CPU, memory, disk, uptime, swap, load, and guests.
74
+ #
75
+ # @param model [Models::Node] Node model
76
+ # @return [Hash] hash representation with string keys
77
+ def to_hash(model)
78
+ @node = model
79
+ {
80
+ "name" => node.name,
81
+ "status" => node.status,
82
+ "version" => node.version,
83
+ "kernel" => node.kernel,
84
+ "cpu" => {
85
+ "usage_percent" => node.cpu.nil? ? nil : (node.cpu * 100).round,
86
+ "cores" => node.maxcpu
87
+ },
88
+ "memory" => {
89
+ "used_bytes" => node.mem,
90
+ "total_bytes" => node.maxmem,
91
+ "used_gb" => memory_used_gb,
92
+ "total_gb" => memory_total_gb,
93
+ "usage_percent" => memory_percent(node)
94
+ },
95
+ "swap" => {
96
+ "used_bytes" => node.swap_used,
97
+ "total_bytes" => node.swap_total,
98
+ "usage_percent" => swap_percent(node)
99
+ },
100
+ "storage" => {
101
+ "used_bytes" => node.disk,
102
+ "total_bytes" => node.maxdisk,
103
+ "usage_percent" => storage_percent(node)
104
+ },
105
+ "load" => {
106
+ "avg1" => node.loadavg&.dig(0),
107
+ "avg5" => node.loadavg&.dig(1),
108
+ "avg15" => node.loadavg&.dig(2)
109
+ },
110
+ "guests" => {
111
+ "total" => node.guests_total,
112
+ "vms" => node.guests_vms,
113
+ "cts" => node.guests_cts
114
+ },
115
+ "uptime" => {
116
+ "seconds" => node.uptime,
117
+ "human" => uptime_human
118
+ },
119
+ "alerts" => alerts,
120
+ "network" => {
121
+ "ip" => node.ip
122
+ }
123
+ }
124
+ end
125
+
126
+ # Converts Node model to description format for describe command.
127
+ #
128
+ # Returns a structured Hash with sections for kubectl-style vertical output.
129
+ # Nested Hashes create indented subsections.
130
+ # Arrays of Hashes render as inline tables.
131
+ #
132
+ # @param model [Models::Node] Node model with describe details
133
+ # @return [Hash] structured hash for describe formatter
134
+ def to_description(model)
135
+ @node = model
136
+ return offline_description if node.offline?
137
+
138
+ {
139
+ "Name" => node.name,
140
+ "Status" => node.status,
141
+ "Subscription" => subscription_display,
142
+ "System" => {
143
+ "Version" => version_display,
144
+ "Kernel" => kernel_display,
145
+ "Boot Mode" => boot_mode,
146
+ "Uptime" => uptime_human
147
+ },
148
+ "CPU" => {
149
+ "Model" => cpu_model || "-",
150
+ "Cores" => cpu_cores || node.maxcpu || "-",
151
+ "Sockets" => cpu_sockets || "-",
152
+ "Usage" => cpu_percent
153
+ },
154
+ "Memory" => {
155
+ "Usage" => memory_percent_display(node),
156
+ "Used" => format_gib(node.mem),
157
+ "Total" => format_gib(node.maxmem)
158
+ },
159
+ "Swap" => {
160
+ "Usage" => swap_percent_display(node),
161
+ "Used" => format_gib(node.swap_used),
162
+ "Total" => format_gib(node.swap_total)
163
+ },
164
+ "Load Average" => {
165
+ "1 min" => node.loadavg&.dig(0)&.round(2) || "-",
166
+ "5 min" => node.loadavg&.dig(1)&.round(2) || "-",
167
+ "15 min" => node.loadavg&.dig(2)&.round(2) || "-"
168
+ },
169
+ "Root Filesystem" => rootfs_display,
170
+ "Network Interfaces" => format_network_interfaces(node.network_interfaces),
171
+ "DNS" => {
172
+ "Search" => dns_search,
173
+ "Nameservers" => dns_nameservers
174
+ },
175
+ "Time" => {
176
+ "Timezone" => timezone,
177
+ "Local Time" => local_time
178
+ },
179
+ "Services" => format_services(node.services),
180
+ "Storage Pools" => format_storage_pools(node.storage_pools),
181
+ "Physical Disks" => format_physical_disks(node.physical_disks),
182
+ "Capabilities" => {
183
+ "QEMU CPU Models" => format_cpu_models(node.qemu_cpu_models),
184
+ "QEMU Machines" => format_machines(node.qemu_machines)
185
+ },
186
+ "Guests" => {
187
+ "VMs" => node.guests_vms,
188
+ "Containers" => node.guests_cts,
189
+ "Total" => node.guests_total
190
+ },
191
+ "Updates" => {
192
+ "Available" => "#{node.updates_available} packages"
193
+ },
194
+ "Firewall" => format_firewall(node.firewall),
195
+ "Firewall Rules" => format_firewall_rules(node.firewall),
196
+ "Task History" => format_task_history(node.tasks),
197
+ "Alerts" => alerts_display
198
+ }
199
+ end
200
+
201
+ # -----------------------------------------------------------------
202
+ # Display Methods (moved from Models::Node)
203
+ # -----------------------------------------------------------------
204
+
205
+ # Returns CPU usage as percentage string.
206
+ #
207
+ # @return [String] CPU percentage (e.g., "23%") or "-" if offline/unavailable
208
+ def cpu_percent
209
+ return "-" if node.offline? || node.cpu.nil?
210
+
211
+ "#{(node.cpu * 100).round}%"
212
+ end
213
+
214
+ # Returns memory used in GB.
215
+ #
216
+ # @return [Float, nil] memory used in GB, or nil if unavailable
217
+ def memory_used_gb
218
+ return nil if node.mem.nil?
219
+
220
+ (node.mem.to_f / 1024 / 1024 / 1024).round(1)
221
+ end
222
+
223
+ # Returns total memory in GB.
224
+ #
225
+ # @return [Integer, nil] total memory in GB, or nil if unavailable
226
+ def memory_total_gb
227
+ return nil if node.maxmem.nil?
228
+
229
+ (node.maxmem.to_f / 1024 / 1024 / 1024).round(0)
230
+ end
231
+
232
+ # Returns memory formatted as "used/total GB".
233
+ #
234
+ # @return [String] formatted memory (e.g., "45.2/128 GB") or "-" if offline
235
+ def memory_display
236
+ return "-" if node.offline? || memory_total_gb.nil?
237
+ return "-/#{memory_total_gb} GB" if memory_used_gb.nil?
238
+
239
+ "#{memory_used_gb}/#{memory_total_gb} GB"
240
+ end
241
+
242
+ # Returns disk used in GB.
243
+ #
244
+ # @return [Float, nil] disk used in GB, or nil if unavailable
245
+ def disk_used_gb
246
+ return nil if node.disk.nil?
247
+
248
+ (node.disk.to_f / 1024 / 1024 / 1024).round(1)
249
+ end
250
+
251
+ # Returns total disk in GB.
252
+ #
253
+ # @return [Float, nil] total disk in GB, or nil if unavailable
254
+ def disk_total_gb
255
+ return nil if node.maxdisk.nil?
256
+
257
+ (node.maxdisk.to_f / 1024 / 1024 / 1024).round(1)
258
+ end
259
+
260
+ # Returns disk formatted with appropriate unit (GB or TB).
261
+ #
262
+ # Uses GB for disks under 1 TB, TB for larger disks.
263
+ # This provides better readability for smaller storage.
264
+ #
265
+ # @return [String] formatted disk (e.g., "85/100 GB" or "1.2/4.0 TB") or "-" if offline
266
+ def storage_display
267
+ return "-" if node.offline? || node.maxdisk.nil?
268
+
269
+ total_gb = disk_total_gb
270
+ used_gb = disk_used_gb || 0.0
271
+
272
+ if total_gb >= 1024
273
+ # Use TB for disks >= 1 TB
274
+ used_tb = (used_gb / 1024).round(1)
275
+ total_tb = (total_gb / 1024).round(1)
276
+ "#{used_tb}/#{total_tb} TB"
277
+ else
278
+ # Use GB for smaller disks
279
+ "#{used_gb.round(0).to_i}/#{total_gb.round(0).to_i} GB"
280
+ end
281
+ end
282
+
283
+ # Returns swap used in GB.
284
+ #
285
+ # @return [Float, nil] swap used in GB, or nil if unavailable
286
+ def swap_used_gb
287
+ return nil if node.swap_used.nil?
288
+
289
+ (node.swap_used.to_f / 1024 / 1024 / 1024).round(1)
290
+ end
291
+
292
+ # Returns total swap in GB.
293
+ #
294
+ # @return [Integer, nil] total swap in GB, or nil if unavailable
295
+ def swap_total_gb
296
+ return nil if node.swap_total.nil?
297
+
298
+ (node.swap_total.to_f / 1024 / 1024 / 1024).round(0)
299
+ end
300
+
301
+ # Returns swap formatted as "used/total GB".
302
+ #
303
+ # @return [String] formatted swap (e.g., "0.0/8 GB") or "-" if offline
304
+ def swap_display
305
+ return "-" if node.offline? || swap_total_gb.nil?
306
+
307
+ "#{swap_used_gb}/#{swap_total_gb} GB"
308
+ end
309
+
310
+ # Returns 1-minute load average.
311
+ #
312
+ # @return [Float, nil] 1-minute load average, or nil if unavailable
313
+ def load_1m
314
+ return nil if node.loadavg.nil? || node.loadavg.empty?
315
+
316
+ node.loadavg[0]
317
+ end
318
+
319
+ # Returns load average display with high-load indicator.
320
+ #
321
+ # @return [String] load average (e.g., "0.45" or "2.31\u2191") or "-" if offline
322
+ def load_display
323
+ return "-" if node.offline? || load_1m.nil?
324
+
325
+ load = load_1m.round(2)
326
+ load > 2.0 ? "#{load}\u2191" : load.to_s
327
+ end
328
+
329
+ # Returns uptime in human-readable format.
330
+ #
331
+ # @return [String] formatted uptime (e.g., "45d 3h") or "-" if offline
332
+ def uptime_human
333
+ return "-" if node.offline? || node.uptime.nil? || node.uptime.zero?
334
+
335
+ days = node.uptime / 86_400
336
+ hours = (node.uptime % 86_400) / 3600
337
+ minutes = (node.uptime % 3600) / 60
338
+
339
+ if days.positive?
340
+ "#{days}d #{hours}h"
341
+ elsif hours.positive?
342
+ "#{hours}h #{minutes}m"
343
+ else
344
+ "#{minutes}m"
345
+ end
346
+ end
347
+
348
+ # Returns version display.
349
+ #
350
+ # @return [String] version (e.g., "8.3.2") or "-" if unavailable
351
+ def version_display
352
+ node.version || "-"
353
+ end
354
+
355
+ # Returns kernel display.
356
+ #
357
+ # @return [String] kernel version or "-" if unavailable
358
+ def kernel_display
359
+ node.kernel || "-"
360
+ end
361
+
362
+ # Returns IP address for display.
363
+ #
364
+ # @return [String] IP address or "-" if unavailable
365
+ def ip_display
366
+ node.ip || "-"
367
+ end
368
+
369
+ # Returns array of alert messages for this node.
370
+ #
371
+ # Alerts are generated based on thresholds:
372
+ # - CPU >= 90%: critical
373
+ # - CPU >= 80%: warning
374
+ # - Memory >= 90%: critical
375
+ # - Memory >= 80%: warning
376
+ # - Status offline: always alert
377
+ #
378
+ # @return [Array<String>] list of alert messages
379
+ def alerts
380
+ result = []
381
+ result << "Node offline" if node.offline?
382
+
383
+ if node.online?
384
+ cpu_pct = node.cpu.nil? ? 0 : (node.cpu * 100).round
385
+ mem_pct = (node.maxmem.nil? || node.maxmem.zero?) ? 0 : ((node.mem.to_f / node.maxmem) * 100).round
386
+
387
+ result << "CPU critical (#{cpu_pct}%)" if cpu_pct >= 90
388
+ result << "CPU warning (#{cpu_pct}%)" if cpu_pct >= 80 && cpu_pct < 90
389
+ result << "Memory critical (#{mem_pct}%)" if mem_pct >= 90
390
+ result << "Memory warning (#{mem_pct}%)" if mem_pct >= 80 && mem_pct < 90
391
+ end
392
+
393
+ result
394
+ end
395
+
396
+ # Returns alerts as comma-separated string for display.
397
+ #
398
+ # @return [String] alerts (e.g., "CPU critical (92%), Memory warning") or "-" if none
399
+ def alerts_display
400
+ alerts.empty? ? "-" : alerts.join(", ")
401
+ end
402
+
403
+ # Checks if node has any alerts.
404
+ #
405
+ # @return [Boolean] true if alerts exist
406
+ def has_alerts?
407
+ !alerts.empty?
408
+ end
409
+
410
+ # Returns CPU model name.
411
+ #
412
+ # @return [String, nil] CPU model
413
+ def cpu_model
414
+ node.cpuinfo&.dig(:model)
415
+ end
416
+
417
+ # Returns CPU socket count.
418
+ #
419
+ # @return [Integer, nil] socket count
420
+ def cpu_sockets
421
+ node.cpuinfo&.dig(:sockets)
422
+ end
423
+
424
+ # Returns CPU core count (total).
425
+ #
426
+ # @return [Integer, nil] core count
427
+ def cpu_cores
428
+ node.cpuinfo&.dig(:cores)
429
+ end
430
+
431
+ # Returns boot mode.
432
+ #
433
+ # @return [String] "UEFI" or "BIOS" or "-"
434
+ def boot_mode
435
+ mode = node.boot_info&.dig(:mode)
436
+ case mode
437
+ when "efi" then "UEFI"
438
+ when "bios" then "BIOS"
439
+ else "-"
440
+ end
441
+ end
442
+
443
+ # Returns subscription status display.
444
+ #
445
+ # @return [String] e.g., "Active (Community)" or "Inactive"
446
+ def subscription_display
447
+ return "-" if node.subscription.nil?
448
+
449
+ status = node.subscription[:status]
450
+ level = node.subscription[:level]
451
+
452
+ level_name = case level
453
+ when "c" then "Community"
454
+ when "b" then "Basic"
455
+ when "s" then "Standard"
456
+ when "p" then "Premium"
457
+ else level
458
+ end
459
+
460
+ status == "Active" ? "Active (#{level_name})" : "Inactive"
461
+ end
462
+
463
+ # Returns timezone.
464
+ #
465
+ # @return [String] timezone or "-"
466
+ def timezone
467
+ node.time_info&.dig(:timezone) || "-"
468
+ end
469
+
470
+ # Returns local time formatted.
471
+ #
472
+ # @return [String] local time or "-"
473
+ def local_time
474
+ localtime = node.time_info&.dig(:localtime)
475
+ return "-" if localtime.nil?
476
+
477
+ Time.at(localtime).strftime("%Y-%m-%d %H:%M:%S")
478
+ end
479
+
480
+ # Returns DNS search domain.
481
+ #
482
+ # @return [String] search domain or "-"
483
+ def dns_search
484
+ node.dns&.dig(:search) || "-"
485
+ end
486
+
487
+ # Returns DNS nameservers.
488
+ #
489
+ # @return [String] comma-separated nameservers or "-"
490
+ def dns_nameservers
491
+ servers = [node.dns&.dig(:dns1), node.dns&.dig(:dns2), node.dns&.dig(:dns3)].compact
492
+ servers.empty? ? "-" : servers.join(", ")
493
+ end
494
+
495
+ # Returns rootfs usage percentage.
496
+ #
497
+ # @return [Integer, nil] percentage
498
+ def rootfs_usage_percent
499
+ return nil if node.rootfs.nil? || node.rootfs[:total].nil? || node.rootfs[:total].zero?
500
+
501
+ ((node.rootfs[:used].to_f / node.rootfs[:total]) * 100).round
502
+ end
503
+
504
+ # Returns rootfs display.
505
+ #
506
+ # @return [String] e.g., "30% (1.2/4.0 TiB)"
507
+ def rootfs_display
508
+ return "-" if node.rootfs.nil?
509
+
510
+ used_gb = (node.rootfs[:used].to_f / 1024 / 1024 / 1024).round(1)
511
+ total_gb = (node.rootfs[:total].to_f / 1024 / 1024 / 1024).round(1)
512
+ pct = rootfs_usage_percent || 0
513
+
514
+ if total_gb >= 1024
515
+ "#{pct}% (#{(used_gb / 1024).round(1)}/#{(total_gb / 1024).round(1)} TiB)"
516
+ else
517
+ "#{pct}% (#{used_gb}/#{total_gb} GiB)"
518
+ end
519
+ end
520
+
521
+ private
522
+
523
+ # @return [Models::Node] the current node being presented
524
+ attr_reader :node
525
+ alias resource node
526
+
527
+ # Returns description for offline nodes.
528
+ #
529
+ # @return [Hash] minimal description
530
+ def offline_description
531
+ {
532
+ "Name" => node.name,
533
+ "Status" => node.status,
534
+ "Note" => node.offline_note || "Node is offline. Detailed metrics unavailable."
535
+ }
536
+ end
537
+
538
+ # Returns memory percentage for display.
539
+ #
540
+ # @param model [Models::Node] Node model
541
+ # @return [String] percentage string
542
+ def memory_percent_display(model)
543
+ pct = memory_percent(model)
544
+ pct ? "#{pct.round}%" : "-"
545
+ end
546
+
547
+ # Returns swap percentage for display.
548
+ #
549
+ # @param model [Models::Node] Node model
550
+ # @return [String] percentage string
551
+ def swap_percent_display(model)
552
+ pct = swap_percent(model)
553
+ pct ? "#{pct.round}%" : "-"
554
+ end
555
+
556
+ # Formats bytes to GiB string.
557
+ #
558
+ # @param bytes [Integer, nil] bytes value
559
+ # @return [String] formatted GiB string
560
+ def format_gib(bytes)
561
+ return "-" if bytes.nil?
562
+
563
+ "#{(bytes.to_f / 1024 / 1024 / 1024).round(1)} GiB"
564
+ end
565
+
566
+ # Formats network interfaces for table display.
567
+ #
568
+ # @param interfaces [Array<Hash>] network interfaces
569
+ # @return [Array<Hash>, String] formatted interfaces or "-"
570
+ def format_network_interfaces(interfaces)
571
+ return "-" if interfaces.nil? || interfaces.empty?
572
+
573
+ interfaces.map do |iface|
574
+ {
575
+ "Name" => iface[:iface],
576
+ "Type" => iface[:type],
577
+ "Address" => iface[:address] || iface[:cidr] || "-",
578
+ "Gateway" => iface[:gateway] || "-"
579
+ }
580
+ end
581
+ end
582
+
583
+ # Formats services for table display.
584
+ #
585
+ # @param services [Array<Hash>] services
586
+ # @return [Array<Hash>, String] formatted services or "-"
587
+ def format_services(services)
588
+ return "-" if services.nil? || services.empty?
589
+
590
+ services.map do |svc|
591
+ {
592
+ "Name" => svc[:service] || svc[:name],
593
+ "State" => svc[:state],
594
+ "Description" => svc[:desc] || "-"
595
+ }
596
+ end
597
+ end
598
+
599
+ # Formats storage pools for table display.
600
+ #
601
+ # Supports both Models::Storage instances (new format) and Hash (legacy).
602
+ #
603
+ # @param pools [Array<Models::Storage>, Array<Hash>] storage pools
604
+ # @return [Array<Hash>, String] formatted pools or "-"
605
+ def format_storage_pools(pools)
606
+ return "-" if pools.nil? || pools.empty?
607
+
608
+ # Handle both Models::Storage and Hash formats
609
+ pools.select { |p| storage_enabled?(p) }.map do |pool|
610
+ format_storage_pool(pool)
611
+ end
612
+ end
613
+
614
+ # Formats physical disks for table display.
615
+ #
616
+ # @param disks [Array<Hash>] physical disks
617
+ # @return [Array<Hash>, String] formatted disks or "-"
618
+ def format_physical_disks(disks)
619
+ return "-" if disks.nil? || disks.empty?
620
+
621
+ disks.map do |disk|
622
+ size_gb = disk[:size] ? (disk[:size].to_f / 1024 / 1024 / 1024).round(1) : 0
623
+ {
624
+ "Device" => disk[:devpath],
625
+ "Model" => disk[:model] || "-",
626
+ "Size" => format_storage_size(size_gb),
627
+ "Type" => disk[:type] || "-",
628
+ "Health" => disk[:health] || "-"
629
+ }
630
+ end
631
+ end
632
+
633
+ # Formats CPU models list as a table.
634
+ #
635
+ # @param models [Array<Hash>] CPU models from API
636
+ # @return [Array<Hash>, String] table rows or "-"
637
+ def format_cpu_models(models)
638
+ return "-" if models.nil? || models.empty?
639
+
640
+ models.map do |m|
641
+ { "Name" => m[:name] || "-", "Vendor" => m[:vendor] || "-" }
642
+ end
643
+ end
644
+
645
+ # Formats machine types list as a table.
646
+ #
647
+ # @param machines [Array<Hash>] machine types from API
648
+ # @return [Array<Hash>, String] table rows or "-"
649
+ def format_machines(machines)
650
+ return "-" if machines.nil? || machines.empty?
651
+
652
+ machines.map do |m|
653
+ { "ID" => m[:id] || "-", "Type" => m[:type] || "-" }
654
+ end
655
+ end
656
+
657
+ # Calculates memory usage percentage.
658
+ #
659
+ # @param model [Models::Node] Node model
660
+ # @return [Float, nil] memory usage percentage or nil if unavailable
661
+ def memory_percent(model)
662
+ return nil if model.maxmem.nil? || model.maxmem.zero? || model.mem.nil?
663
+
664
+ ((model.mem.to_f / model.maxmem) * 100).round(1)
665
+ end
666
+
667
+ # Calculates swap usage percentage.
668
+ #
669
+ # @param model [Models::Node] Node model
670
+ # @return [Float, nil] swap usage percentage or nil if unavailable
671
+ def swap_percent(model)
672
+ return nil if model.swap_total.nil? || model.swap_total.zero? || model.swap_used.nil?
673
+
674
+ ((model.swap_used.to_f / model.swap_total) * 100).round(1)
675
+ end
676
+
677
+ # Calculates storage usage percentage.
678
+ #
679
+ # @param model [Models::Node] Node model
680
+ # @return [Float, nil] storage usage percentage or nil if unavailable
681
+ def storage_percent(model)
682
+ return nil if model.maxdisk.nil? || model.maxdisk.zero? || model.disk.nil?
683
+
684
+ ((model.disk.to_f / model.maxdisk) * 100).round(1)
685
+ end
686
+
687
+ # Checks if storage pool is enabled.
688
+ # Supports both Models::Storage and Hash formats.
689
+ #
690
+ # @param pool [Models::Storage, Hash] storage pool
691
+ # @return [Boolean] true if enabled
692
+ def storage_enabled?(pool)
693
+ if pool.respond_to?(:enabled?)
694
+ pool.enabled?
695
+ else
696
+ pool[:enabled] != 0
697
+ end
698
+ end
699
+
700
+ # Formats a single storage pool for display.
701
+ # Supports both Models::Storage and Hash formats.
702
+ #
703
+ # @param pool [Models::Storage, Hash] storage pool
704
+ # @return [Hash] formatted pool data
705
+ def format_storage_pool(pool)
706
+ if pool.respond_to?(:name)
707
+ # Models::Storage instance - use Storage presenter for display
708
+ format_storage_pool_model(pool)
709
+ else
710
+ # Hash format (legacy)
711
+ format_storage_pool_hash(pool)
712
+ end
713
+ end
714
+
715
+ # Formats a storage pool from Models::Storage instance.
716
+ # Uses Storage presenter for display formatting.
717
+ #
718
+ # @param pool [Models::Storage] storage pool model
719
+ # @return [Hash] formatted pool data
720
+ def format_storage_pool_model(pool)
721
+ storage_presenter = Storage.new
722
+ storage_presenter.to_row(pool) # Set up @storage in presenter
723
+ {
724
+ "Name" => pool.name,
725
+ "Type" => storage_presenter.type_display,
726
+ "Total" => storage_presenter.total_display,
727
+ "Used" => storage_presenter.used_display,
728
+ "Available" => storage_presenter.avail_display,
729
+ "Usage" => storage_presenter.usage_display
730
+ }
731
+ end
732
+
733
+ # Formats a storage pool from Hash data (legacy format).
734
+ #
735
+ # @param pool [Hash] storage pool hash
736
+ # @return [Hash] formatted pool data
737
+ def format_storage_pool_hash(pool)
738
+ total_gb = pool[:total] ? (pool[:total].to_f / 1024 / 1024 / 1024).round(1) : 0
739
+ used_gb = pool[:used] ? (pool[:used].to_f / 1024 / 1024 / 1024).round(1) : 0
740
+ avail_gb = pool[:avail] ? (pool[:avail].to_f / 1024 / 1024 / 1024).round(1) : 0
741
+ usage_pct = pool[:total] && pool[:total] > 0 ? ((pool[:used].to_f / pool[:total]) * 100).round : 0
742
+
743
+ {
744
+ "Name" => pool[:storage],
745
+ "Type" => pool[:type],
746
+ "Total" => format_storage_size(total_gb),
747
+ "Used" => format_storage_size(used_gb),
748
+ "Available" => format_storage_size(avail_gb),
749
+ "Usage" => "#{usage_pct}%"
750
+ }
751
+ end
752
+
753
+ # Formats storage size with appropriate unit.
754
+ #
755
+ # @param gb [Float] size in GB
756
+ # @return [String] formatted size
757
+ def format_storage_size(gb)
758
+ gb >= 1024 ? "#{(gb / 1024).round(1)} TiB" : "#{gb} GiB"
759
+ end
760
+ end
761
+ end
762
+ end