@sonde/packs 0.1.0 → 0.1.2
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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +53 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +19 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/integrations/citrix.d.ts +13 -0
- package/dist/integrations/citrix.d.ts.map +1 -0
- package/dist/integrations/citrix.js +415 -0
- package/dist/integrations/citrix.js.map +1 -0
- package/dist/integrations/citrix.test.d.ts +2 -0
- package/dist/integrations/citrix.test.d.ts.map +1 -0
- package/dist/integrations/citrix.test.js +464 -0
- package/dist/integrations/citrix.test.js.map +1 -0
- package/dist/integrations/graph.d.ts +9 -0
- package/dist/integrations/graph.d.ts.map +1 -0
- package/dist/integrations/graph.js +285 -0
- package/dist/integrations/graph.js.map +1 -0
- package/dist/integrations/graph.test.d.ts +2 -0
- package/dist/integrations/graph.test.d.ts.map +1 -0
- package/dist/integrations/graph.test.js +356 -0
- package/dist/integrations/graph.test.js.map +1 -0
- package/dist/integrations/httpbin.d.ts +3 -0
- package/dist/integrations/httpbin.d.ts.map +1 -0
- package/dist/integrations/httpbin.js +65 -0
- package/dist/integrations/httpbin.js.map +1 -0
- package/dist/integrations/nutanix.d.ts +18 -0
- package/dist/integrations/nutanix.d.ts.map +1 -0
- package/dist/integrations/nutanix.js +1116 -0
- package/dist/integrations/nutanix.js.map +1 -0
- package/dist/integrations/nutanix.test.d.ts +2 -0
- package/dist/integrations/nutanix.test.d.ts.map +1 -0
- package/dist/integrations/nutanix.test.js +978 -0
- package/dist/integrations/nutanix.test.js.map +1 -0
- package/dist/integrations/proxmox.d.ts +12 -0
- package/dist/integrations/proxmox.d.ts.map +1 -0
- package/dist/integrations/proxmox.js +728 -0
- package/dist/integrations/proxmox.js.map +1 -0
- package/dist/integrations/proxmox.test.d.ts +2 -0
- package/dist/integrations/proxmox.test.d.ts.map +1 -0
- package/dist/integrations/proxmox.test.js +697 -0
- package/dist/integrations/proxmox.test.js.map +1 -0
- package/dist/integrations/servicenow.d.ts +3 -0
- package/dist/integrations/servicenow.d.ts.map +1 -0
- package/dist/integrations/servicenow.js +251 -0
- package/dist/integrations/servicenow.js.map +1 -0
- package/dist/integrations/servicenow.test.d.ts +2 -0
- package/dist/integrations/servicenow.test.d.ts.map +1 -0
- package/dist/integrations/servicenow.test.js +217 -0
- package/dist/integrations/servicenow.test.js.map +1 -0
- package/dist/integrations/splunk.d.ts +9 -0
- package/dist/integrations/splunk.d.ts.map +1 -0
- package/dist/integrations/splunk.js +237 -0
- package/dist/integrations/splunk.js.map +1 -0
- package/dist/integrations/splunk.test.d.ts +2 -0
- package/dist/integrations/splunk.test.d.ts.map +1 -0
- package/dist/integrations/splunk.test.js +323 -0
- package/dist/integrations/splunk.test.js.map +1 -0
- package/dist/mysql/index.d.ts +3 -0
- package/dist/mysql/index.d.ts.map +1 -0
- package/dist/mysql/index.js +13 -0
- package/dist/mysql/index.js.map +1 -0
- package/dist/mysql/manifest.d.ts +3 -0
- package/dist/mysql/manifest.d.ts.map +1 -0
- package/dist/mysql/manifest.js +69 -0
- package/dist/mysql/manifest.js.map +1 -0
- package/dist/mysql/probes/databases-list.d.ts +13 -0
- package/dist/mysql/probes/databases-list.d.ts.map +1 -0
- package/dist/mysql/probes/databases-list.js +31 -0
- package/dist/mysql/probes/databases-list.js.map +1 -0
- package/dist/mysql/probes/databases-list.test.d.ts +2 -0
- package/dist/mysql/probes/databases-list.test.d.ts.map +1 -0
- package/dist/mysql/probes/databases-list.test.js +54 -0
- package/dist/mysql/probes/databases-list.test.js.map +1 -0
- package/dist/mysql/probes/processlist.d.ts +18 -0
- package/dist/mysql/probes/processlist.d.ts.map +1 -0
- package/dist/mysql/probes/processlist.js +36 -0
- package/dist/mysql/probes/processlist.js.map +1 -0
- package/dist/mysql/probes/processlist.test.d.ts +2 -0
- package/dist/mysql/probes/processlist.test.d.ts.map +1 -0
- package/dist/mysql/probes/processlist.test.js +41 -0
- package/dist/mysql/probes/processlist.test.js.map +1 -0
- package/dist/mysql/probes/status.d.ts +14 -0
- package/dist/mysql/probes/status.d.ts.map +1 -0
- package/dist/mysql/probes/status.js +40 -0
- package/dist/mysql/probes/status.js.map +1 -0
- package/dist/mysql/probes/status.test.d.ts +2 -0
- package/dist/mysql/probes/status.test.d.ts.map +1 -0
- package/dist/mysql/probes/status.test.js +43 -0
- package/dist/mysql/probes/status.test.js.map +1 -0
- package/dist/nginx/index.d.ts +3 -0
- package/dist/nginx/index.d.ts.map +1 -0
- package/dist/nginx/index.js +13 -0
- package/dist/nginx/index.js.map +1 -0
- package/dist/nginx/manifest.d.ts +3 -0
- package/dist/nginx/manifest.d.ts.map +1 -0
- package/dist/nginx/manifest.js +68 -0
- package/dist/nginx/manifest.js.map +1 -0
- package/dist/nginx/probes/access-log-tail.d.ts +9 -0
- package/dist/nginx/probes/access-log-tail.d.ts.map +1 -0
- package/dist/nginx/probes/access-log-tail.js +14 -0
- package/dist/nginx/probes/access-log-tail.js.map +1 -0
- package/dist/nginx/probes/access-log-tail.test.d.ts +2 -0
- package/dist/nginx/probes/access-log-tail.test.d.ts.map +1 -0
- package/dist/nginx/probes/access-log-tail.test.js +40 -0
- package/dist/nginx/probes/access-log-tail.test.js.map +1 -0
- package/dist/nginx/probes/config-test.d.ts +8 -0
- package/dist/nginx/probes/config-test.d.ts.map +1 -0
- package/dist/nginx/probes/config-test.js +18 -0
- package/dist/nginx/probes/config-test.js.map +1 -0
- package/dist/nginx/probes/config-test.test.d.ts +2 -0
- package/dist/nginx/probes/config-test.test.d.ts.map +1 -0
- package/dist/nginx/probes/config-test.test.js +35 -0
- package/dist/nginx/probes/config-test.test.js.map +1 -0
- package/dist/nginx/probes/error-log-tail.d.ts +9 -0
- package/dist/nginx/probes/error-log-tail.d.ts.map +1 -0
- package/dist/nginx/probes/error-log-tail.js +14 -0
- package/dist/nginx/probes/error-log-tail.js.map +1 -0
- package/dist/nginx/probes/error-log-tail.test.d.ts +2 -0
- package/dist/nginx/probes/error-log-tail.test.d.ts.map +1 -0
- package/dist/nginx/probes/error-log-tail.test.js +34 -0
- package/dist/nginx/probes/error-log-tail.test.js.map +1 -0
- package/dist/postgres/index.d.ts +3 -0
- package/dist/postgres/index.d.ts.map +1 -0
- package/dist/postgres/index.js +13 -0
- package/dist/postgres/index.js.map +1 -0
- package/dist/postgres/manifest.d.ts +3 -0
- package/dist/postgres/manifest.d.ts.map +1 -0
- package/dist/postgres/manifest.js +90 -0
- package/dist/postgres/manifest.js.map +1 -0
- package/dist/postgres/probes/connections-active.d.ts +17 -0
- package/dist/postgres/probes/connections-active.d.ts.map +1 -0
- package/dist/postgres/probes/connections-active.js +37 -0
- package/dist/postgres/probes/connections-active.js.map +1 -0
- package/dist/postgres/probes/connections-active.test.d.ts +2 -0
- package/dist/postgres/probes/connections-active.test.d.ts.map +1 -0
- package/dist/postgres/probes/connections-active.test.js +36 -0
- package/dist/postgres/probes/connections-active.test.js.map +1 -0
- package/dist/postgres/probes/databases-list.d.ts +14 -0
- package/dist/postgres/probes/databases-list.d.ts.map +1 -0
- package/dist/postgres/probes/databases-list.js +34 -0
- package/dist/postgres/probes/databases-list.js.map +1 -0
- package/dist/postgres/probes/databases-list.test.d.ts +2 -0
- package/dist/postgres/probes/databases-list.test.d.ts.map +1 -0
- package/dist/postgres/probes/databases-list.test.js +49 -0
- package/dist/postgres/probes/databases-list.test.js.map +1 -0
- package/dist/postgres/probes/query-slow.d.ts +17 -0
- package/dist/postgres/probes/query-slow.d.ts.map +1 -0
- package/dist/postgres/probes/query-slow.js +37 -0
- package/dist/postgres/probes/query-slow.js.map +1 -0
- package/dist/postgres/probes/query-slow.test.d.ts +2 -0
- package/dist/postgres/probes/query-slow.test.d.ts.map +1 -0
- package/dist/postgres/probes/query-slow.test.js +30 -0
- package/dist/postgres/probes/query-slow.test.js.map +1 -0
- package/dist/proxmox/index.d.ts +3 -0
- package/dist/proxmox/index.d.ts.map +1 -0
- package/dist/proxmox/index.js +23 -0
- package/dist/proxmox/index.js.map +1 -0
- package/dist/proxmox/manifest.d.ts +3 -0
- package/dist/proxmox/manifest.d.ts.map +1 -0
- package/dist/proxmox/manifest.js +75 -0
- package/dist/proxmox/manifest.js.map +1 -0
- package/dist/proxmox/probes/ceph-status.d.ts +36 -0
- package/dist/proxmox/probes/ceph-status.d.ts.map +1 -0
- package/dist/proxmox/probes/ceph-status.js +71 -0
- package/dist/proxmox/probes/ceph-status.js.map +1 -0
- package/dist/proxmox/probes/ceph-status.test.d.ts +2 -0
- package/dist/proxmox/probes/ceph-status.test.d.ts.map +1 -0
- package/dist/proxmox/probes/ceph-status.test.js +115 -0
- package/dist/proxmox/probes/ceph-status.test.js.map +1 -0
- package/dist/proxmox/probes/cluster-config.d.ts +31 -0
- package/dist/proxmox/probes/cluster-config.d.ts.map +1 -0
- package/dist/proxmox/probes/cluster-config.js +72 -0
- package/dist/proxmox/probes/cluster-config.js.map +1 -0
- package/dist/proxmox/probes/cluster-config.test.d.ts +2 -0
- package/dist/proxmox/probes/cluster-config.test.d.ts.map +1 -0
- package/dist/proxmox/probes/cluster-config.test.js +107 -0
- package/dist/proxmox/probes/cluster-config.test.js.map +1 -0
- package/dist/proxmox/probes/ha-status.d.ts +18 -0
- package/dist/proxmox/probes/ha-status.d.ts.map +1 -0
- package/dist/proxmox/probes/ha-status.js +38 -0
- package/dist/proxmox/probes/ha-status.js.map +1 -0
- package/dist/proxmox/probes/ha-status.test.d.ts +2 -0
- package/dist/proxmox/probes/ha-status.test.d.ts.map +1 -0
- package/dist/proxmox/probes/ha-status.test.js +66 -0
- package/dist/proxmox/probes/ha-status.test.js.map +1 -0
- package/dist/proxmox/probes/lvm.d.ts +35 -0
- package/dist/proxmox/probes/lvm.d.ts.map +1 -0
- package/dist/proxmox/probes/lvm.js +75 -0
- package/dist/proxmox/probes/lvm.js.map +1 -0
- package/dist/proxmox/probes/lvm.test.d.ts +2 -0
- package/dist/proxmox/probes/lvm.test.d.ts.map +1 -0
- package/dist/proxmox/probes/lvm.test.js +128 -0
- package/dist/proxmox/probes/lvm.test.js.map +1 -0
- package/dist/proxmox/probes/lxc-config.d.ts +29 -0
- package/dist/proxmox/probes/lxc-config.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-config.js +67 -0
- package/dist/proxmox/probes/lxc-config.js.map +1 -0
- package/dist/proxmox/probes/lxc-config.test.d.ts +2 -0
- package/dist/proxmox/probes/lxc-config.test.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-config.test.js +77 -0
- package/dist/proxmox/probes/lxc-config.test.js.map +1 -0
- package/dist/proxmox/probes/lxc-list.d.ts +20 -0
- package/dist/proxmox/probes/lxc-list.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-list.js +49 -0
- package/dist/proxmox/probes/lxc-list.js.map +1 -0
- package/dist/proxmox/probes/lxc-list.test.d.ts +2 -0
- package/dist/proxmox/probes/lxc-list.test.d.ts.map +1 -0
- package/dist/proxmox/probes/lxc-list.test.js +51 -0
- package/dist/proxmox/probes/lxc-list.test.js.map +1 -0
- package/dist/proxmox/probes/vm-config.d.ts +21 -0
- package/dist/proxmox/probes/vm-config.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-config.js +58 -0
- package/dist/proxmox/probes/vm-config.js.map +1 -0
- package/dist/proxmox/probes/vm-config.test.d.ts +2 -0
- package/dist/proxmox/probes/vm-config.test.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-config.test.js +80 -0
- package/dist/proxmox/probes/vm-config.test.js.map +1 -0
- package/dist/proxmox/probes/vm-locks.d.ts +16 -0
- package/dist/proxmox/probes/vm-locks.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-locks.js +35 -0
- package/dist/proxmox/probes/vm-locks.js.map +1 -0
- package/dist/proxmox/probes/vm-locks.test.d.ts +2 -0
- package/dist/proxmox/probes/vm-locks.test.d.ts.map +1 -0
- package/dist/proxmox/probes/vm-locks.test.js +54 -0
- package/dist/proxmox/probes/vm-locks.test.js.map +1 -0
- package/dist/redis/index.d.ts +3 -0
- package/dist/redis/index.d.ts.map +1 -0
- package/dist/redis/index.js +13 -0
- package/dist/redis/index.js.map +1 -0
- package/dist/redis/manifest.d.ts +3 -0
- package/dist/redis/manifest.d.ts.map +1 -0
- package/dist/redis/manifest.js +51 -0
- package/dist/redis/manifest.js.map +1 -0
- package/dist/redis/probes/info.d.ts +15 -0
- package/dist/redis/probes/info.d.ts.map +1 -0
- package/dist/redis/probes/info.js +32 -0
- package/dist/redis/probes/info.js.map +1 -0
- package/dist/redis/probes/info.test.d.ts +2 -0
- package/dist/redis/probes/info.test.d.ts.map +1 -0
- package/dist/redis/probes/info.test.js +64 -0
- package/dist/redis/probes/info.test.js.map +1 -0
- package/dist/redis/probes/keys-count.d.ts +13 -0
- package/dist/redis/probes/keys-count.d.ts.map +1 -0
- package/dist/redis/probes/keys-count.js +24 -0
- package/dist/redis/probes/keys-count.js.map +1 -0
- package/dist/redis/probes/keys-count.test.d.ts +2 -0
- package/dist/redis/probes/keys-count.test.d.ts.map +1 -0
- package/dist/redis/probes/keys-count.test.js +37 -0
- package/dist/redis/probes/keys-count.test.js.map +1 -0
- package/dist/redis/probes/memory-usage.d.ts +16 -0
- package/dist/redis/probes/memory-usage.d.ts.map +1 -0
- package/dist/redis/probes/memory-usage.js +31 -0
- package/dist/redis/probes/memory-usage.js.map +1 -0
- package/dist/redis/probes/memory-usage.test.d.ts +2 -0
- package/dist/redis/probes/memory-usage.test.d.ts.map +1 -0
- package/dist/redis/probes/memory-usage.test.js +48 -0
- package/dist/redis/probes/memory-usage.test.js.map +1 -0
- package/dist/runbooks/nutanix.d.ts +3 -0
- package/dist/runbooks/nutanix.d.ts.map +1 -0
- package/dist/runbooks/nutanix.js +619 -0
- package/dist/runbooks/nutanix.js.map +1 -0
- package/dist/runbooks/nutanix.test.d.ts +2 -0
- package/dist/runbooks/nutanix.test.d.ts.map +1 -0
- package/dist/runbooks/nutanix.test.js +971 -0
- package/dist/runbooks/nutanix.test.js.map +1 -0
- package/dist/runbooks/proxmox.d.ts +3 -0
- package/dist/runbooks/proxmox.d.ts.map +1 -0
- package/dist/runbooks/proxmox.js +451 -0
- package/dist/runbooks/proxmox.js.map +1 -0
- package/dist/runbooks/proxmox.test.d.ts +2 -0
- package/dist/runbooks/proxmox.test.d.ts.map +1 -0
- package/dist/runbooks/proxmox.test.js +700 -0
- package/dist/runbooks/proxmox.test.js.map +1 -0
- package/dist/signatures.d.ts +2 -0
- package/dist/signatures.d.ts.map +1 -0
- package/dist/signatures.js +2 -0
- package/dist/signatures.js.map +1 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +2 -0
- package/dist/system/index.js.map +1 -1
- package/dist/system/manifest.d.ts.map +1 -1
- package/dist/system/manifest.js +19 -1
- package/dist/system/manifest.js.map +1 -1
- package/dist/system/probes/ping.d.ts +20 -0
- package/dist/system/probes/ping.d.ts.map +1 -0
- package/dist/system/probes/ping.js +54 -0
- package/dist/system/probes/ping.js.map +1 -0
- package/dist/system/probes/ping.test.d.ts +2 -0
- package/dist/system/probes/ping.test.d.ts.map +1 -0
- package/dist/system/probes/ping.test.js +127 -0
- package/dist/system/probes/ping.test.js.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +6 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +10 -1
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +60 -6
- package/src/integrations/citrix.test.ts +592 -0
- package/src/integrations/citrix.ts +553 -0
- package/src/integrations/graph.test.ts +478 -0
- package/src/integrations/graph.ts +409 -0
- package/src/integrations/httpbin.ts +68 -0
- package/src/integrations/nutanix.test.ts +1508 -0
- package/src/integrations/nutanix.ts +1456 -0
- package/src/integrations/proxmox.test.ts +1020 -0
- package/src/integrations/proxmox.ts +985 -0
- package/src/integrations/servicenow.test.ts +314 -0
- package/src/integrations/servicenow.ts +280 -0
- package/src/integrations/splunk.test.ts +440 -0
- package/src/integrations/splunk.ts +352 -0
- package/src/mysql/index.ts +14 -0
- package/src/mysql/manifest.ts +70 -0
- package/src/mysql/probes/databases-list.test.ts +62 -0
- package/src/mysql/probes/databases-list.ts +45 -0
- package/src/mysql/probes/processlist.test.ts +47 -0
- package/src/mysql/probes/processlist.ts +55 -0
- package/src/mysql/probes/status.test.ts +50 -0
- package/src/mysql/probes/status.ts +56 -0
- package/src/nginx/index.ts +14 -0
- package/src/nginx/manifest.ts +69 -0
- package/src/nginx/probes/access-log-tail.test.ts +51 -0
- package/src/nginx/probes/access-log-tail.ts +23 -0
- package/src/nginx/probes/config-test.test.ts +47 -0
- package/src/nginx/probes/config-test.ts +24 -0
- package/src/nginx/probes/error-log-tail.test.ts +44 -0
- package/src/nginx/probes/error-log-tail.ts +23 -0
- package/src/postgres/index.ts +14 -0
- package/src/postgres/manifest.ts +91 -0
- package/src/postgres/probes/connections-active.test.ts +42 -0
- package/src/postgres/probes/connections-active.ts +55 -0
- package/src/postgres/probes/databases-list.test.ts +57 -0
- package/src/postgres/probes/databases-list.ts +49 -0
- package/src/postgres/probes/query-slow.test.ts +37 -0
- package/src/postgres/probes/query-slow.ts +55 -0
- package/src/proxmox/index.ts +24 -0
- package/src/proxmox/manifest.ts +76 -0
- package/src/proxmox/probes/ceph-status.test.ts +126 -0
- package/src/proxmox/probes/ceph-status.ts +116 -0
- package/src/proxmox/probes/cluster-config.test.ts +118 -0
- package/src/proxmox/probes/cluster-config.ts +97 -0
- package/src/proxmox/probes/ha-status.test.ts +76 -0
- package/src/proxmox/probes/ha-status.ts +56 -0
- package/src/proxmox/probes/lvm.test.ts +140 -0
- package/src/proxmox/probes/lvm.ts +121 -0
- package/src/proxmox/probes/lxc-config.test.ts +89 -0
- package/src/proxmox/probes/lxc-config.ts +90 -0
- package/src/proxmox/probes/lxc-list.test.ts +60 -0
- package/src/proxmox/probes/lxc-list.ts +67 -0
- package/src/proxmox/probes/vm-config.test.ts +93 -0
- package/src/proxmox/probes/vm-config.ts +77 -0
- package/src/proxmox/probes/vm-locks.test.ts +63 -0
- package/src/proxmox/probes/vm-locks.ts +49 -0
- package/src/redis/index.ts +14 -0
- package/src/redis/manifest.ts +52 -0
- package/src/redis/probes/info.test.ts +73 -0
- package/src/redis/probes/info.ts +46 -0
- package/src/redis/probes/keys-count.test.ts +44 -0
- package/src/redis/probes/keys-count.ts +38 -0
- package/src/redis/probes/memory-usage.test.ts +54 -0
- package/src/redis/probes/memory-usage.ts +46 -0
- package/src/runbooks/nutanix.test.ts +1138 -0
- package/src/runbooks/nutanix.ts +941 -0
- package/src/runbooks/proxmox.test.ts +838 -0
- package/src/runbooks/proxmox.ts +626 -0
- package/src/signatures.ts +1 -0
- package/src/system/index.ts +2 -0
- package/src/system/manifest.ts +21 -1
- package/src/system/probes/ping.test.ts +163 -0
- package/src/system/probes/ping.ts +89 -0
- package/src/types.ts +62 -0
- package/src/validation.ts +21 -1
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,1116 @@
|
|
|
1
|
+
// --- Auth helpers ---
|
|
2
|
+
/** Build auth headers: Basic (username:password) or X-Ntnx-Api-Key */
|
|
3
|
+
export function buildAuthHeaders(credentials) {
|
|
4
|
+
if (credentials.authMethod === 'bearer_token') {
|
|
5
|
+
const key = credentials.credentials.nutanixApiKey ?? '';
|
|
6
|
+
return { 'X-Ntnx-Api-Key': key };
|
|
7
|
+
}
|
|
8
|
+
// basic auth (api_key method)
|
|
9
|
+
const { username, password } = credentials.credentials;
|
|
10
|
+
if (username && password) {
|
|
11
|
+
return { Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}` };
|
|
12
|
+
}
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
// --- Nutanix REST helpers ---
|
|
16
|
+
/** Build a full Nutanix Prism Central v4 URL */
|
|
17
|
+
export function nutanixUrl(endpoint, namespace, path, params) {
|
|
18
|
+
const base = `${endpoint.replace(/\/$/, '')}/api/${namespace}/v4.0/${path}`;
|
|
19
|
+
const url = new URL(base);
|
|
20
|
+
if (params) {
|
|
21
|
+
for (const [key, value] of Object.entries(params)) {
|
|
22
|
+
url.searchParams.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return url.toString();
|
|
26
|
+
}
|
|
27
|
+
/** GET a Nutanix v4 endpoint, unwrap response envelope, return { data, totalCount } */
|
|
28
|
+
export async function nutanixGet(namespace, path, config, credentials, fetchFn, params) {
|
|
29
|
+
const url = nutanixUrl(config.endpoint, namespace, path, params);
|
|
30
|
+
const headers = {
|
|
31
|
+
Accept: 'application/json',
|
|
32
|
+
...buildAuthHeaders(credentials),
|
|
33
|
+
...config.headers,
|
|
34
|
+
};
|
|
35
|
+
const res = await fetchFn(url, { headers });
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
throw new Error(`Nutanix API returned ${res.status}: ${res.statusText}`);
|
|
38
|
+
const body = (await res.json());
|
|
39
|
+
return {
|
|
40
|
+
data: body.data,
|
|
41
|
+
totalCount: body.metadata?.totalAvailableResults,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** POST to a Nutanix endpoint (used for v3 category query fallback) */
|
|
45
|
+
export async function nutanixPost(url, body, config, credentials, fetchFn) {
|
|
46
|
+
const headers = {
|
|
47
|
+
Accept: 'application/json',
|
|
48
|
+
'Content-Type': 'application/json',
|
|
49
|
+
...buildAuthHeaders(credentials),
|
|
50
|
+
...config.headers,
|
|
51
|
+
};
|
|
52
|
+
const res = await fetchFn(url, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers,
|
|
55
|
+
body: JSON.stringify(body),
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok)
|
|
58
|
+
throw new Error(`Nutanix API returned ${res.status}: ${res.statusText}`);
|
|
59
|
+
return res.json();
|
|
60
|
+
}
|
|
61
|
+
// --- Unit conversions ---
|
|
62
|
+
/** Convert parts-per-million to percentage (2 decimal places) */
|
|
63
|
+
export function ppmToPercent(ppm) {
|
|
64
|
+
return Math.round((ppm / 10000) * 100) / 100;
|
|
65
|
+
}
|
|
66
|
+
/** Convert microseconds to milliseconds (2 decimal places) */
|
|
67
|
+
export function usecsToMs(usecs) {
|
|
68
|
+
return Math.round((usecs / 1000) * 100) / 100;
|
|
69
|
+
}
|
|
70
|
+
// --- Probe handlers ---
|
|
71
|
+
const clustersList = async (params, config, credentials, fetchFn) => {
|
|
72
|
+
const name = params?.name;
|
|
73
|
+
const queryParams = {};
|
|
74
|
+
if (name) {
|
|
75
|
+
queryParams.$filter = `name eq '${name}'`;
|
|
76
|
+
}
|
|
77
|
+
const { data, totalCount } = await nutanixGet('clustermgmt', 'config/clusters', config, credentials, fetchFn, queryParams);
|
|
78
|
+
const items = data ?? [];
|
|
79
|
+
return {
|
|
80
|
+
clusters: items.map((c) => ({
|
|
81
|
+
name: c.name ?? null,
|
|
82
|
+
extId: c.extId ?? null,
|
|
83
|
+
hypervisorType: c.config?.hypervisorType ??
|
|
84
|
+
c.hypervisorType ??
|
|
85
|
+
null,
|
|
86
|
+
aosVersion: c.config?.buildInfo?.version ??
|
|
87
|
+
c.aosVersion ??
|
|
88
|
+
null,
|
|
89
|
+
numNodes: c.nodes?.numberOfNodes ??
|
|
90
|
+
c.numNodes ??
|
|
91
|
+
null,
|
|
92
|
+
redundancyFactor: c.config?.redundancyFactor ??
|
|
93
|
+
c.redundancyFactor ??
|
|
94
|
+
null,
|
|
95
|
+
operationMode: c.operationMode ?? null,
|
|
96
|
+
isDegraded: c.operationMode !== undefined && c.operationMode !== 'NORMAL',
|
|
97
|
+
})),
|
|
98
|
+
totalCount: totalCount ?? items.length,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const hostsList = async (params, config, credentials, fetchFn) => {
|
|
102
|
+
const clusterId = params?.cluster_id;
|
|
103
|
+
const queryParams = {};
|
|
104
|
+
if (clusterId) {
|
|
105
|
+
queryParams.$filter = `clusterExtId eq '${clusterId}'`;
|
|
106
|
+
}
|
|
107
|
+
const { data, totalCount } = await nutanixGet('clustermgmt', 'config/hosts', config, credentials, fetchFn, queryParams);
|
|
108
|
+
const items = data ?? [];
|
|
109
|
+
return {
|
|
110
|
+
hosts: items.map((h) => ({
|
|
111
|
+
name: h.hostName ?? h.name ?? null,
|
|
112
|
+
extId: h.extId ?? null,
|
|
113
|
+
serialNumber: h.serialNumber ?? null,
|
|
114
|
+
blockModel: h.blockModel ?? null,
|
|
115
|
+
hypervisorVersion: h.hypervisor?.fullName ??
|
|
116
|
+
h.hypervisorVersion ??
|
|
117
|
+
null,
|
|
118
|
+
cpuModel: h.cpuModel ?? null,
|
|
119
|
+
numCpuSockets: h.numCpuSockets ?? null,
|
|
120
|
+
numCpuCores: h.numCpuCores ?? null,
|
|
121
|
+
memoryCapacityBytes: h.memoryCapacityBytes ?? null,
|
|
122
|
+
controllerVmIp: h.controllerVm?.ip ??
|
|
123
|
+
h.controllerVmIp ??
|
|
124
|
+
null,
|
|
125
|
+
hypervisorIp: h.hypervisor?.ip ??
|
|
126
|
+
h.hypervisorIp ??
|
|
127
|
+
null,
|
|
128
|
+
ipmiIp: h.ipmi?.ip ?? h.ipmiIp ?? null,
|
|
129
|
+
maintenanceMode: h.maintenanceMode ?? false,
|
|
130
|
+
})),
|
|
131
|
+
totalCount: totalCount ?? items.length,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
const vmsList = async (params, config, credentials, fetchFn) => {
|
|
135
|
+
const name = params?.name;
|
|
136
|
+
const powerState = params?.power_state;
|
|
137
|
+
const clusterId = params?.cluster_id;
|
|
138
|
+
const limit = params?.limit || 50;
|
|
139
|
+
const filters = [];
|
|
140
|
+
if (name)
|
|
141
|
+
filters.push(`name eq '${name}'`);
|
|
142
|
+
if (powerState)
|
|
143
|
+
filters.push(`powerState eq '${powerState}'`);
|
|
144
|
+
if (clusterId)
|
|
145
|
+
filters.push(`clusterExtId eq '${clusterId}'`);
|
|
146
|
+
const queryParams = { $limit: String(limit) };
|
|
147
|
+
if (filters.length > 0) {
|
|
148
|
+
queryParams.$filter = filters.join(' and ');
|
|
149
|
+
}
|
|
150
|
+
const { data, totalCount } = await nutanixGet('vmm', 'ahv/config/vms', config, credentials, fetchFn, queryParams);
|
|
151
|
+
const items = data ?? [];
|
|
152
|
+
return {
|
|
153
|
+
vms: items.map((v) => ({
|
|
154
|
+
name: v.name ?? null,
|
|
155
|
+
extId: v.extId ?? null,
|
|
156
|
+
powerState: v.powerState ?? null,
|
|
157
|
+
numSockets: v.numSockets ?? null,
|
|
158
|
+
numCoresPerSocket: v.numCoresPerSocket ?? null,
|
|
159
|
+
memorySizeMb: v.memorySizeBytes != null
|
|
160
|
+
? Math.round(v.memorySizeBytes / 1048576)
|
|
161
|
+
: (v.memorySizeMb ?? null),
|
|
162
|
+
clusterExtId: v.cluster?.extId ?? v.clusterExtId ?? null,
|
|
163
|
+
hostExtId: v.host?.extId ?? v.hostExtId ?? null,
|
|
164
|
+
description: v.description ?? null,
|
|
165
|
+
createTime: v.createTime ?? null,
|
|
166
|
+
})),
|
|
167
|
+
totalCount: totalCount ?? items.length,
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
const vmDetail = async (params, config, credentials, fetchFn) => {
|
|
171
|
+
const vmId = params?.vm_id;
|
|
172
|
+
if (!vmId)
|
|
173
|
+
throw new Error('vm_id parameter is required');
|
|
174
|
+
const { data } = await nutanixGet('vmm', `ahv/config/vms/${vmId}`, config, credentials, fetchFn);
|
|
175
|
+
const vm = data;
|
|
176
|
+
// Parse disks
|
|
177
|
+
const disks = (vm.disks ?? []).map((d) => {
|
|
178
|
+
const backing = d.backingInfo ?? {};
|
|
179
|
+
return {
|
|
180
|
+
diskAddress: d.diskAddress ?? null,
|
|
181
|
+
deviceType: backing.deviceType ?? d.deviceType ?? null,
|
|
182
|
+
storageContainerId: backing.storageContainerId ?? null,
|
|
183
|
+
sizeBytes: backing.diskSizeBytes ?? backing.vmDiskSize ?? null,
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
// Parse NICs
|
|
187
|
+
const nics = (vm.nics ?? []).map((n) => {
|
|
188
|
+
const network = n.networkInfo ?? {};
|
|
189
|
+
const subnetRef = network.subnet ?? {};
|
|
190
|
+
return {
|
|
191
|
+
macAddress: n.macAddress ?? network.macAddress ?? null,
|
|
192
|
+
subnetExtId: subnetRef.extId ?? network.subnetExtId ?? null,
|
|
193
|
+
nicType: network.nicType ?? n.nicType ?? null,
|
|
194
|
+
isConnected: network.isConnected ?? n.isConnected ?? null,
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
// Total allocated storage
|
|
198
|
+
const totalStorageBytes = disks.reduce((sum, d) => sum + (d.sizeBytes ?? 0), 0);
|
|
199
|
+
return {
|
|
200
|
+
name: vm.name ?? null,
|
|
201
|
+
extId: vm.extId ?? null,
|
|
202
|
+
powerState: vm.powerState ?? null,
|
|
203
|
+
numSockets: vm.numSockets ?? null,
|
|
204
|
+
numCoresPerSocket: vm.numCoresPerSocket ?? null,
|
|
205
|
+
memorySizeMb: vm.memorySizeBytes != null
|
|
206
|
+
? Math.round(vm.memorySizeBytes / 1048576)
|
|
207
|
+
: (vm.memorySizeMb ?? null),
|
|
208
|
+
description: vm.description ?? null,
|
|
209
|
+
clusterExtId: vm.cluster?.extId ?? vm.clusterExtId ?? null,
|
|
210
|
+
hostExtId: vm.host?.extId ?? vm.hostExtId ?? null,
|
|
211
|
+
disks,
|
|
212
|
+
nics,
|
|
213
|
+
totalStorageBytes,
|
|
214
|
+
bootConfig: vm.bootConfig ?? null,
|
|
215
|
+
categories: vm.categories ?? null,
|
|
216
|
+
guestTools: vm.guestTools ?? null,
|
|
217
|
+
createTime: vm.createTime ?? null,
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
const vmStats = async (params, config, credentials, fetchFn) => {
|
|
221
|
+
const vmId = params?.vm_id;
|
|
222
|
+
if (!vmId)
|
|
223
|
+
throw new Error('vm_id parameter is required');
|
|
224
|
+
const { data } = await nutanixGet('vmm', `ahv/stats/vms/${vmId}`, config, credentials, fetchFn);
|
|
225
|
+
const stats = data ?? [];
|
|
226
|
+
const findStat = (metricType) => {
|
|
227
|
+
const entry = stats.find((s) => s.metricType === metricType || s.extId?.includes(metricType.toLowerCase()));
|
|
228
|
+
return entry?.value != null ? entry.value : null;
|
|
229
|
+
};
|
|
230
|
+
const cpuPpm = findStat('CPU_USAGE_PPM') ?? findStat('hypervisor_cpu_usage_ppm');
|
|
231
|
+
const memPpm = findStat('MEMORY_USAGE_PPM') ?? findStat('memory_usage_ppm');
|
|
232
|
+
const iops = findStat('IOPS') ?? findStat('controller_num_iops');
|
|
233
|
+
const bwKbps = findStat('IO_BANDWIDTH_KBPS') ?? findStat('controller_io_bandwidth_kBps');
|
|
234
|
+
const latencyUsecs = findStat('AVG_IO_LATENCY_USECS') ?? findStat('controller_avg_io_latency_usecs');
|
|
235
|
+
const rxBytes = findStat('NETWORK_RX_BYTES') ?? findStat('hypervisor_num_received_bytes');
|
|
236
|
+
const txBytes = findStat('NETWORK_TX_BYTES') ?? findStat('hypervisor_num_transmitted_bytes');
|
|
237
|
+
return {
|
|
238
|
+
cpuUsagePct: cpuPpm != null ? ppmToPercent(cpuPpm) : null,
|
|
239
|
+
memoryUsagePct: memPpm != null ? ppmToPercent(memPpm) : null,
|
|
240
|
+
iops: iops ?? null,
|
|
241
|
+
ioBandwidthKbps: bwKbps ?? null,
|
|
242
|
+
avgIoLatencyMs: latencyUsecs != null ? usecsToMs(latencyUsecs) : null,
|
|
243
|
+
networkRxBytes: rxBytes ?? null,
|
|
244
|
+
networkTxBytes: txBytes ?? null,
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
const alertsList = async (params, config, credentials, fetchFn) => {
|
|
248
|
+
const severity = params?.severity;
|
|
249
|
+
const resolved = params?.resolved;
|
|
250
|
+
const hours = params?.hours;
|
|
251
|
+
const entityType = params?.entity_type;
|
|
252
|
+
const limit = params?.limit || 50;
|
|
253
|
+
const filters = [];
|
|
254
|
+
if (severity)
|
|
255
|
+
filters.push(`severity eq '${severity}'`);
|
|
256
|
+
if (resolved === true)
|
|
257
|
+
filters.push("resolvedStatus eq 'RESOLVED'");
|
|
258
|
+
if (resolved === false)
|
|
259
|
+
filters.push("resolvedStatus eq 'UNRESOLVED'");
|
|
260
|
+
if (hours) {
|
|
261
|
+
const since = new Date(Date.now() - hours * 3600000).toISOString();
|
|
262
|
+
filters.push(`creationTime ge '${since}'`);
|
|
263
|
+
}
|
|
264
|
+
if (entityType)
|
|
265
|
+
filters.push(`sourceEntity/type eq '${entityType}'`);
|
|
266
|
+
const queryParams = { $limit: String(limit) };
|
|
267
|
+
if (filters.length > 0) {
|
|
268
|
+
queryParams.$filter = filters.join(' and ');
|
|
269
|
+
}
|
|
270
|
+
const { data, totalCount } = await nutanixGet('monitoring', 'alerts', config, credentials, fetchFn, queryParams);
|
|
271
|
+
const items = data ?? [];
|
|
272
|
+
return {
|
|
273
|
+
alerts: items.map((a) => {
|
|
274
|
+
const source = a.sourceEntity ?? {};
|
|
275
|
+
return {
|
|
276
|
+
title: a.title ?? null,
|
|
277
|
+
severity: a.severity ?? null,
|
|
278
|
+
sourceEntity: {
|
|
279
|
+
type: source.type ?? null,
|
|
280
|
+
name: source.name ?? null,
|
|
281
|
+
extId: source.extId ?? null,
|
|
282
|
+
},
|
|
283
|
+
creationTime: a.creationTime ?? null,
|
|
284
|
+
description: a.description ?? null,
|
|
285
|
+
resolvedStatus: a.resolvedStatus ?? null,
|
|
286
|
+
impactType: a.impactType ?? null,
|
|
287
|
+
possibleCauses: a.possibleCauses ?? null,
|
|
288
|
+
resolutionSteps: a.resolutionSteps ?? null,
|
|
289
|
+
};
|
|
290
|
+
}),
|
|
291
|
+
totalCount: totalCount ?? items.length,
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
const alertsSummary = async (_params, config, credentials, fetchFn) => {
|
|
295
|
+
const queryParams = { $limit: '500' };
|
|
296
|
+
const { data } = await nutanixGet('monitoring', 'alerts', config, credentials, fetchFn, queryParams);
|
|
297
|
+
const items = data ?? [];
|
|
298
|
+
const bySeverity = { CRITICAL: 0, WARNING: 0, INFO: 0 };
|
|
299
|
+
const byEntityType = {};
|
|
300
|
+
const unresolvedCritical = [];
|
|
301
|
+
for (const a of items) {
|
|
302
|
+
const sev = a.severity ?? 'INFO';
|
|
303
|
+
bySeverity[sev] = (bySeverity[sev] ?? 0) + 1;
|
|
304
|
+
const source = a.sourceEntity ?? {};
|
|
305
|
+
const entityType = source.type ?? 'Unknown';
|
|
306
|
+
byEntityType[entityType] = (byEntityType[entityType] ?? 0) + 1;
|
|
307
|
+
if (sev === 'CRITICAL' && a.resolvedStatus !== 'RESOLVED') {
|
|
308
|
+
unresolvedCritical.push({
|
|
309
|
+
title: a.title,
|
|
310
|
+
sourceEntity: { type: source.type, name: source.name, extId: source.extId },
|
|
311
|
+
creationTime: a.creationTime,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
bySeverity,
|
|
317
|
+
byEntityType,
|
|
318
|
+
unresolvedCritical,
|
|
319
|
+
totalCount: items.length,
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
const storageContainers = async (params, config, credentials, fetchFn) => {
|
|
323
|
+
const clusterId = params?.cluster_id;
|
|
324
|
+
const queryParams = {};
|
|
325
|
+
if (clusterId) {
|
|
326
|
+
queryParams.$filter = `clusterExtId eq '${clusterId}'`;
|
|
327
|
+
}
|
|
328
|
+
const { data, totalCount } = await nutanixGet('clustermgmt', 'config/storage-containers', config, credentials, fetchFn, queryParams);
|
|
329
|
+
const items = data ?? [];
|
|
330
|
+
return {
|
|
331
|
+
containers: items.map((c) => {
|
|
332
|
+
const max = c.maxCapacity ?? c.maxCapacityBytes ?? 0;
|
|
333
|
+
const used = c.usedCapacity ?? c.usedBytes ?? 0;
|
|
334
|
+
const reserved = c.reservedCapacity ?? c.reservedCapacityBytes ?? 0;
|
|
335
|
+
const usedPct = max > 0 ? Math.round((used / max) * 10000) / 100 : 0;
|
|
336
|
+
const available = max - used;
|
|
337
|
+
return {
|
|
338
|
+
name: c.name ?? null,
|
|
339
|
+
extId: c.extId ?? null,
|
|
340
|
+
maxCapacityBytes: max,
|
|
341
|
+
usedBytes: used,
|
|
342
|
+
reservedCapacityBytes: reserved,
|
|
343
|
+
replicationFactor: c.replicationFactor ?? null,
|
|
344
|
+
compressionEnabled: c.compressionEnabled ?? false,
|
|
345
|
+
deduplicationEnabled: c.onDiskDedup === 'POST_PROCESS' ||
|
|
346
|
+
c.onDiskDedup === 'INLINE' ||
|
|
347
|
+
(c.deduplicationEnabled ?? false),
|
|
348
|
+
erasureCodingEnabled: c.erasureCodingEnabled ?? false,
|
|
349
|
+
usedPct,
|
|
350
|
+
availableBytes: available,
|
|
351
|
+
highUsage: usedPct > 85,
|
|
352
|
+
};
|
|
353
|
+
}),
|
|
354
|
+
totalCount: totalCount ?? items.length,
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
const categoriesList = async (params, config, credentials, fetchFn) => {
|
|
358
|
+
const key = params?.key;
|
|
359
|
+
const queryParams = {};
|
|
360
|
+
if (key) {
|
|
361
|
+
queryParams.$filter = `key eq '${key}'`;
|
|
362
|
+
}
|
|
363
|
+
const { data, totalCount } = await nutanixGet('prism', 'config/categories', config, credentials, fetchFn, queryParams);
|
|
364
|
+
const items = data ?? [];
|
|
365
|
+
return {
|
|
366
|
+
categories: items.map((c) => ({
|
|
367
|
+
key: c.key ?? null,
|
|
368
|
+
value: c.value ?? null,
|
|
369
|
+
description: c.description ?? null,
|
|
370
|
+
type: c.type ?? null,
|
|
371
|
+
})),
|
|
372
|
+
totalCount: totalCount ?? items.length,
|
|
373
|
+
};
|
|
374
|
+
};
|
|
375
|
+
const categoriesEntities = async (params, config, credentials, fetchFn) => {
|
|
376
|
+
const key = params?.key;
|
|
377
|
+
const value = params?.value;
|
|
378
|
+
if (!key)
|
|
379
|
+
throw new Error('key parameter is required');
|
|
380
|
+
if (!value)
|
|
381
|
+
throw new Error('value parameter is required');
|
|
382
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/api/nutanix/v3/category/query`;
|
|
383
|
+
const body = {
|
|
384
|
+
usage_type: 'APPLIED_TO',
|
|
385
|
+
category_filter: {
|
|
386
|
+
type: 'CATEGORIES_MATCH_ANY',
|
|
387
|
+
params: { [key]: [value] },
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
const result = (await nutanixPost(url, body, config, credentials, fetchFn));
|
|
391
|
+
const entities = [];
|
|
392
|
+
for (const group of result.results ?? []) {
|
|
393
|
+
const entityType = group.kind ?? 'unknown';
|
|
394
|
+
for (const ref of group.kind_reference_list ?? []) {
|
|
395
|
+
entities.push({
|
|
396
|
+
entityType,
|
|
397
|
+
entityId: ref.uuid ?? '',
|
|
398
|
+
entityName: ref.name ?? null,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
entities,
|
|
404
|
+
totalCount: entities.length,
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
const networksList = async (params, config, credentials, fetchFn) => {
|
|
408
|
+
const clusterId = params?.cluster_id;
|
|
409
|
+
const queryParams = {};
|
|
410
|
+
if (clusterId) {
|
|
411
|
+
queryParams.$filter = `clusterExtId eq '${clusterId}'`;
|
|
412
|
+
}
|
|
413
|
+
const { data, totalCount } = await nutanixGet('networking', 'config/subnets', config, credentials, fetchFn, queryParams);
|
|
414
|
+
const items = data ?? [];
|
|
415
|
+
return {
|
|
416
|
+
subnets: items.map((s) => {
|
|
417
|
+
const ipConfig = s.ipConfig ?? {};
|
|
418
|
+
const pool = (ipConfig.ipv4Config ?? ipConfig);
|
|
419
|
+
return {
|
|
420
|
+
name: s.name ?? null,
|
|
421
|
+
type: s.subnetType ?? s.type ?? null,
|
|
422
|
+
vlanId: s.vlanId ?? null,
|
|
423
|
+
networkIp: pool.networkIp ?? pool.subnetIp ?? null,
|
|
424
|
+
prefixLength: pool.prefixLength ?? null,
|
|
425
|
+
dhcpEnabled: pool.dhcpServerAddress != null || (s.dhcpEnabled ?? false),
|
|
426
|
+
vpcRef: s.vpcReference?.extId ?? s.vpcRef ?? null,
|
|
427
|
+
clusterExtId: s.clusterReference ??
|
|
428
|
+
s.cluster?.extId ??
|
|
429
|
+
s.clusterExtId ??
|
|
430
|
+
null,
|
|
431
|
+
};
|
|
432
|
+
}),
|
|
433
|
+
totalCount: totalCount ?? items.length,
|
|
434
|
+
};
|
|
435
|
+
};
|
|
436
|
+
const tasksRecent = async (params, config, credentials, fetchFn) => {
|
|
437
|
+
const hours = params?.hours || 24;
|
|
438
|
+
const status = params?.status;
|
|
439
|
+
const limit = params?.limit || 50;
|
|
440
|
+
const since = new Date(Date.now() - hours * 3600000).toISOString();
|
|
441
|
+
const filters = [`startTime ge '${since}'`];
|
|
442
|
+
if (status)
|
|
443
|
+
filters.push(`status eq '${status}'`);
|
|
444
|
+
const queryParams = {
|
|
445
|
+
$limit: String(limit),
|
|
446
|
+
$filter: filters.join(' and '),
|
|
447
|
+
};
|
|
448
|
+
const { data, totalCount } = await nutanixGet('prism', 'config/tasks', config, credentials, fetchFn, queryParams);
|
|
449
|
+
const items = data ?? [];
|
|
450
|
+
const now = Date.now();
|
|
451
|
+
const oneHourMs = 3600000;
|
|
452
|
+
return {
|
|
453
|
+
tasks: items.map((t) => {
|
|
454
|
+
const startTime = t.startTime;
|
|
455
|
+
const endTime = t.completedTime ?? t.endTime ?? null;
|
|
456
|
+
const isFailed = t.status === 'FAILED';
|
|
457
|
+
const isLongRunning = !endTime && startTime && now - new Date(startTime).getTime() > oneHourMs;
|
|
458
|
+
return {
|
|
459
|
+
type: t.operationType ?? t.type ?? null,
|
|
460
|
+
status: t.status ?? null,
|
|
461
|
+
entityRef: t.entityReference ?? t.entityRef ?? null,
|
|
462
|
+
startTime: startTime ?? null,
|
|
463
|
+
endTime,
|
|
464
|
+
errorMessage: t.errorMessages?.[0]?.message ??
|
|
465
|
+
t.errorMessage ??
|
|
466
|
+
null,
|
|
467
|
+
progressPct: t.progressPercentage ?? t.progressPct ?? null,
|
|
468
|
+
isFailed,
|
|
469
|
+
isLongRunning: !!isLongRunning,
|
|
470
|
+
};
|
|
471
|
+
}),
|
|
472
|
+
totalCount: totalCount ?? items.length,
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
const clusterHealth = async (params, config, credentials, fetchFn) => {
|
|
476
|
+
const clusterId = params?.cluster_id;
|
|
477
|
+
// Fetch cluster info
|
|
478
|
+
let clusterInfo = null;
|
|
479
|
+
if (clusterId) {
|
|
480
|
+
const { data } = await nutanixGet('clustermgmt', `config/clusters/${clusterId}`, config, credentials, fetchFn);
|
|
481
|
+
clusterInfo = data;
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
const { data } = await nutanixGet('clustermgmt', 'config/clusters', config, credentials, fetchFn, { $limit: '1' });
|
|
485
|
+
const clusters = data ?? [];
|
|
486
|
+
clusterInfo = clusters[0] ?? null;
|
|
487
|
+
}
|
|
488
|
+
const cExtId = clusterInfo?.extId ?? clusterId;
|
|
489
|
+
// Parallel fetches: hosts, critical alerts, storage
|
|
490
|
+
const hostsFilter = {};
|
|
491
|
+
if (cExtId)
|
|
492
|
+
hostsFilter.$filter = `clusterExtId eq '${cExtId}'`;
|
|
493
|
+
const storageFilter = {};
|
|
494
|
+
if (cExtId)
|
|
495
|
+
storageFilter.$filter = `clusterExtId eq '${cExtId}'`;
|
|
496
|
+
const [hostsResult, alertsResult, storageResult] = await Promise.all([
|
|
497
|
+
nutanixGet('clustermgmt', 'config/hosts', config, credentials, fetchFn, hostsFilter),
|
|
498
|
+
nutanixGet('monitoring', 'alerts', config, credentials, fetchFn, {
|
|
499
|
+
$filter: "severity eq 'CRITICAL' and resolvedStatus eq 'UNRESOLVED'",
|
|
500
|
+
$limit: '100',
|
|
501
|
+
}),
|
|
502
|
+
nutanixGet('clustermgmt', 'config/storage-containers', config, credentials, fetchFn, storageFilter),
|
|
503
|
+
]);
|
|
504
|
+
const hosts = hostsResult.data ?? [];
|
|
505
|
+
const alerts = alertsResult.data ?? [];
|
|
506
|
+
const containers = storageResult.data ?? [];
|
|
507
|
+
const degradedNodes = hosts.filter((h) => h.maintenanceMode === true || h.status === 'DEGRADED');
|
|
508
|
+
const storageContainersInfo = containers.map((c) => {
|
|
509
|
+
const max = c.maxCapacity ?? c.maxCapacityBytes ?? 0;
|
|
510
|
+
const used = c.usedCapacity ?? c.usedBytes ?? 0;
|
|
511
|
+
return {
|
|
512
|
+
name: c.name,
|
|
513
|
+
usedPct: max > 0 ? Math.round((used / max) * 10000) / 100 : 0,
|
|
514
|
+
};
|
|
515
|
+
});
|
|
516
|
+
const issues = [];
|
|
517
|
+
if (degradedNodes.length > 0) {
|
|
518
|
+
issues.push(`${degradedNodes.length} node(s) degraded or in maintenance`);
|
|
519
|
+
}
|
|
520
|
+
if (alerts.length > 0) {
|
|
521
|
+
issues.push(`${alerts.length} unresolved critical alert(s)`);
|
|
522
|
+
}
|
|
523
|
+
for (const sc of storageContainersInfo) {
|
|
524
|
+
if (sc.usedPct > 85) {
|
|
525
|
+
issues.push(`Storage container ${sc.name} at ${sc.usedPct}% capacity`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
let healthAssessment;
|
|
529
|
+
if (alerts.length > 0 || degradedNodes.length > 0) {
|
|
530
|
+
healthAssessment = 'CRITICAL';
|
|
531
|
+
}
|
|
532
|
+
else if (storageContainersInfo.some((sc) => sc.usedPct > 85)) {
|
|
533
|
+
healthAssessment = 'WARNING';
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
healthAssessment = 'HEALTHY';
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
cluster: clusterInfo
|
|
540
|
+
? {
|
|
541
|
+
name: clusterInfo.name,
|
|
542
|
+
extId: clusterInfo.extId,
|
|
543
|
+
operationMode: clusterInfo.operationMode,
|
|
544
|
+
}
|
|
545
|
+
: null,
|
|
546
|
+
nodeCount: hosts.length,
|
|
547
|
+
degradedNodes: degradedNodes.map((h) => ({
|
|
548
|
+
name: h.hostName ?? h.name,
|
|
549
|
+
extId: h.extId,
|
|
550
|
+
maintenanceMode: h.maintenanceMode,
|
|
551
|
+
})),
|
|
552
|
+
criticalAlerts: alerts.map((a) => ({
|
|
553
|
+
title: a.title,
|
|
554
|
+
sourceEntity: a.sourceEntity,
|
|
555
|
+
creationTime: a.creationTime,
|
|
556
|
+
})),
|
|
557
|
+
storageContainers: storageContainersInfo,
|
|
558
|
+
healthAssessment,
|
|
559
|
+
issues,
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
const vmSnapshots = async (params, config, credentials, fetchFn) => {
|
|
563
|
+
const vmId = params?.vm_id;
|
|
564
|
+
if (!vmId)
|
|
565
|
+
throw new Error('vm_id parameter is required');
|
|
566
|
+
// Try v4 dataprotection API first
|
|
567
|
+
let items = [];
|
|
568
|
+
let usedV3 = false;
|
|
569
|
+
try {
|
|
570
|
+
const { data } = await nutanixGet('dataprotection', 'config/recovery-points', config, credentials, fetchFn, { $filter: `vmExtId eq '${vmId}'` });
|
|
571
|
+
items = data ?? [];
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
// Fall back to v3
|
|
575
|
+
const url = `${config.endpoint.replace(/\/$/, '')}/api/nutanix/v3/vm_recovery_points/list`;
|
|
576
|
+
const result = (await nutanixPost(url, { filter: `vm_uuid==${vmId}`, length: 100 }, config, credentials, fetchFn));
|
|
577
|
+
items = result.entities ?? [];
|
|
578
|
+
usedV3 = true;
|
|
579
|
+
}
|
|
580
|
+
const now = Date.now();
|
|
581
|
+
const sevenDaysMs = 7 * 24 * 3600000;
|
|
582
|
+
const snapshots = items.map((rp) => {
|
|
583
|
+
const name = rp.name ??
|
|
584
|
+
rp.status?.name ??
|
|
585
|
+
rp.extId ??
|
|
586
|
+
null;
|
|
587
|
+
const creationTime = rp.creationTime ?? rp.status?.creation_time ?? null;
|
|
588
|
+
const expirationTime = rp.expirationTime ??
|
|
589
|
+
rp.status?.expiration_time ??
|
|
590
|
+
null;
|
|
591
|
+
const consistencyType = rp.recoveryPointType ??
|
|
592
|
+
rp.status?.recovery_point_type ??
|
|
593
|
+
null;
|
|
594
|
+
const sizeBytes = rp.sizeBytes ?? rp.diskSizeBytes ?? null;
|
|
595
|
+
const createdMs = creationTime ? new Date(creationTime).getTime() : null;
|
|
596
|
+
const ageMs = createdMs ? now - createdMs : null;
|
|
597
|
+
const isOld = ageMs != null && ageMs > sevenDaysMs;
|
|
598
|
+
const expirationMs = expirationTime ? new Date(expirationTime).getTime() : null;
|
|
599
|
+
const isExpired = expirationMs != null && expirationMs < now;
|
|
600
|
+
return {
|
|
601
|
+
name,
|
|
602
|
+
extId: rp.extId ?? null,
|
|
603
|
+
creationTime,
|
|
604
|
+
expirationTime,
|
|
605
|
+
consistencyType,
|
|
606
|
+
sizeBytes,
|
|
607
|
+
ageDays: ageMs != null ? Math.round((ageMs / 86400000) * 10) / 10 : null,
|
|
608
|
+
isOld,
|
|
609
|
+
isExpired,
|
|
610
|
+
};
|
|
611
|
+
});
|
|
612
|
+
const warnings = [];
|
|
613
|
+
const oldSnapshots = snapshots.filter((s) => s.isOld);
|
|
614
|
+
const expiredSnapshots = snapshots.filter((s) => s.isExpired);
|
|
615
|
+
if (oldSnapshots.length > 0) {
|
|
616
|
+
warnings.push(`${oldSnapshots.length} snapshot(s) older than 7 days`);
|
|
617
|
+
}
|
|
618
|
+
if (expiredSnapshots.length > 0) {
|
|
619
|
+
warnings.push(`${expiredSnapshots.length} expired snapshot(s) not cleaned up`);
|
|
620
|
+
}
|
|
621
|
+
return { snapshots, totalCount: snapshots.length, usedV3, warnings };
|
|
622
|
+
};
|
|
623
|
+
const protectionPolicies = async (params, config, credentials, fetchFn) => {
|
|
624
|
+
const vmId = params?.vm_id;
|
|
625
|
+
const { data, totalCount } = await nutanixGet('dataprotection', 'config/protection-policies', config, credentials, fetchFn);
|
|
626
|
+
const items = data ?? [];
|
|
627
|
+
const policies = items.map((p) => {
|
|
628
|
+
const schedules = p.schedules ?? [];
|
|
629
|
+
const primarySchedule = schedules[0];
|
|
630
|
+
const rpo = primarySchedule
|
|
631
|
+
? {
|
|
632
|
+
value: primarySchedule.recoveryPointObjective ?? primarySchedule.rpoInMinutes ?? null,
|
|
633
|
+
unit: primarySchedule.rpoUnit ?? 'MINUTES',
|
|
634
|
+
}
|
|
635
|
+
: null;
|
|
636
|
+
const retention = primarySchedule
|
|
637
|
+
? {
|
|
638
|
+
local: primarySchedule.localRetentionCount ?? null,
|
|
639
|
+
remote: primarySchedule.remoteRetentionCount ?? null,
|
|
640
|
+
}
|
|
641
|
+
: null;
|
|
642
|
+
const protectedEntities = p.protectedEntities ??
|
|
643
|
+
p.entityReferences ??
|
|
644
|
+
[];
|
|
645
|
+
return {
|
|
646
|
+
name: p.name ?? null,
|
|
647
|
+
extId: p.extId ?? null,
|
|
648
|
+
description: p.description ?? null,
|
|
649
|
+
rpo,
|
|
650
|
+
retention,
|
|
651
|
+
remoteSite: p.remoteSiteReference?.name ?? p.remoteSite ?? null,
|
|
652
|
+
protectedEntityCount: protectedEntities.length,
|
|
653
|
+
protectedEntityIds: protectedEntities.map((e) => e.extId ?? e.entityId ?? ''),
|
|
654
|
+
lastSuccessfulReplication: p.lastSuccessfulReplicationTime ?? null,
|
|
655
|
+
};
|
|
656
|
+
});
|
|
657
|
+
if (vmId) {
|
|
658
|
+
const covering = policies.filter((p) => p.protectedEntityIds.includes(vmId));
|
|
659
|
+
return {
|
|
660
|
+
policies: covering,
|
|
661
|
+
totalCount: covering.length,
|
|
662
|
+
vmCovered: covering.length > 0,
|
|
663
|
+
allPoliciesCount: policies.length,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
return { policies, totalCount: totalCount ?? policies.length };
|
|
667
|
+
};
|
|
668
|
+
const lifecycleStatus = async (_params, config, credentials, fetchFn) => {
|
|
669
|
+
const { data, totalCount } = await nutanixGet('lifecycle', 'resources/entities', config, credentials, fetchFn);
|
|
670
|
+
const items = data ?? [];
|
|
671
|
+
const entities = items.map((e) => {
|
|
672
|
+
const availableVersion = e.availableVersion?.version ??
|
|
673
|
+
e.availableVersion ??
|
|
674
|
+
null;
|
|
675
|
+
const currentVersion = e.installedVersion?.version ??
|
|
676
|
+
e.currentVersion ??
|
|
677
|
+
null;
|
|
678
|
+
return {
|
|
679
|
+
entityType: e.entityType ?? e.entityModel ?? null,
|
|
680
|
+
name: e.name ?? null,
|
|
681
|
+
extId: e.extId ?? null,
|
|
682
|
+
currentVersion,
|
|
683
|
+
availableVersion,
|
|
684
|
+
updateStatus: e.updateStatus ?? null,
|
|
685
|
+
hasUpdate: availableVersion != null && availableVersion !== currentVersion,
|
|
686
|
+
};
|
|
687
|
+
});
|
|
688
|
+
const warnings = [];
|
|
689
|
+
const updatable = entities.filter((e) => e.hasUpdate);
|
|
690
|
+
if (updatable.length > 0) {
|
|
691
|
+
warnings.push(`${updatable.length} component(s) have available updates`);
|
|
692
|
+
for (const e of updatable) {
|
|
693
|
+
warnings.push(`${e.entityType ?? e.name}: ${e.currentVersion} → ${e.availableVersion}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return {
|
|
697
|
+
entities,
|
|
698
|
+
updatableCount: updatable.length,
|
|
699
|
+
totalCount: totalCount ?? entities.length,
|
|
700
|
+
warnings,
|
|
701
|
+
};
|
|
702
|
+
};
|
|
703
|
+
const hostStats = async (params, config, credentials, fetchFn) => {
|
|
704
|
+
const hostId = params?.host_id;
|
|
705
|
+
if (!hostId)
|
|
706
|
+
throw new Error('host_id parameter is required');
|
|
707
|
+
const { data } = await nutanixGet('clustermgmt', `stats/hosts/${hostId}`, config, credentials, fetchFn);
|
|
708
|
+
const stats = data ?? [];
|
|
709
|
+
const findStat = (metricType) => {
|
|
710
|
+
const entry = stats.find((s) => s.metricType === metricType || s.extId?.includes(metricType.toLowerCase()));
|
|
711
|
+
return entry?.value != null ? entry.value : null;
|
|
712
|
+
};
|
|
713
|
+
const cpuPpm = findStat('HYPERVISOR_CPU_USAGE_PPM') ?? findStat('hypervisor_cpu_usage_ppm');
|
|
714
|
+
const memPpm = findStat('HYPERVISOR_MEMORY_USAGE_PPM') ?? findStat('hypervisor_memory_usage_ppm');
|
|
715
|
+
const iops = findStat('IOPS') ?? findStat('controller_num_iops');
|
|
716
|
+
const bwKbps = findStat('IO_BANDWIDTH_KBPS') ?? findStat('controller_io_bandwidth_kBps');
|
|
717
|
+
const networkRx = findStat('NETWORK_RX_BYTES') ?? findStat('hypervisor_num_received_bytes');
|
|
718
|
+
const networkTx = findStat('NETWORK_TX_BYTES') ?? findStat('hypervisor_num_transmitted_bytes');
|
|
719
|
+
const uptimeUsecs = findStat('HYPERVISOR_UPTIME_USECS') ?? findStat('hypervisor_uptime_usecs');
|
|
720
|
+
const cpuPct = cpuPpm != null ? ppmToPercent(cpuPpm) : null;
|
|
721
|
+
const memPct = memPpm != null ? ppmToPercent(memPpm) : null;
|
|
722
|
+
const warnings = [];
|
|
723
|
+
if (cpuPct != null && cpuPct > 85) {
|
|
724
|
+
warnings.push(`Host CPU at ${cpuPct}%`);
|
|
725
|
+
}
|
|
726
|
+
if (memPct != null && memPct > 90) {
|
|
727
|
+
warnings.push(`Host memory at ${memPct}%`);
|
|
728
|
+
}
|
|
729
|
+
return {
|
|
730
|
+
cpuUsagePct: cpuPct,
|
|
731
|
+
memoryUsagePct: memPct,
|
|
732
|
+
iops: iops ?? null,
|
|
733
|
+
ioBandwidthKbps: bwKbps ?? null,
|
|
734
|
+
networkRxBytes: networkRx ?? null,
|
|
735
|
+
networkTxBytes: networkTx ?? null,
|
|
736
|
+
hypervisorUptimeUsecs: uptimeUsecs ?? null,
|
|
737
|
+
warnings,
|
|
738
|
+
};
|
|
739
|
+
};
|
|
740
|
+
const clusterStats = async (params, config, credentials, fetchFn) => {
|
|
741
|
+
const clusterId = params?.cluster_id;
|
|
742
|
+
if (!clusterId)
|
|
743
|
+
throw new Error('cluster_id parameter is required');
|
|
744
|
+
const { data } = await nutanixGet('clustermgmt', `stats/clusters/${clusterId}`, config, credentials, fetchFn);
|
|
745
|
+
const stats = data ?? [];
|
|
746
|
+
const findStat = (metricType) => {
|
|
747
|
+
const entry = stats.find((s) => s.metricType === metricType || s.extId?.includes(metricType.toLowerCase()));
|
|
748
|
+
return entry?.value != null ? entry.value : null;
|
|
749
|
+
};
|
|
750
|
+
const cpuCapacityHz = findStat('CPU_CAPACITY_HZ') ?? findStat('cpu_capacity_hz');
|
|
751
|
+
const cpuUsedHz = findStat('CPU_USAGE_HZ') ?? findStat('hypervisor_cpu_usage_hz');
|
|
752
|
+
const memCapacity = findStat('MEMORY_CAPACITY_BYTES') ?? findStat('memory_capacity_bytes');
|
|
753
|
+
const memUsed = findStat('MEMORY_USAGE_BYTES') ?? findStat('hypervisor_memory_usage_bytes');
|
|
754
|
+
const storageCapacity = findStat('STORAGE_CAPACITY_BYTES') ?? findStat('storage_capacity_bytes');
|
|
755
|
+
const storageUsed = findStat('STORAGE_USAGE_BYTES') ?? findStat('storage_usage_bytes');
|
|
756
|
+
const iops = findStat('IOPS') ?? findStat('controller_num_iops');
|
|
757
|
+
const latencyUsecs = findStat('AVG_IO_LATENCY_USECS') ?? findStat('controller_avg_io_latency_usecs');
|
|
758
|
+
const cpuPct = cpuCapacityHz && cpuUsedHz && cpuCapacityHz > 0
|
|
759
|
+
? Math.round((cpuUsedHz / cpuCapacityHz) * 10000) / 100
|
|
760
|
+
: null;
|
|
761
|
+
const memPct = memCapacity && memUsed && memCapacity > 0
|
|
762
|
+
? Math.round((memUsed / memCapacity) * 10000) / 100
|
|
763
|
+
: null;
|
|
764
|
+
const storagePct = storageCapacity && storageUsed && storageCapacity > 0
|
|
765
|
+
? Math.round((storageUsed / storageCapacity) * 10000) / 100
|
|
766
|
+
: null;
|
|
767
|
+
return {
|
|
768
|
+
cpuCapacityHz,
|
|
769
|
+
cpuUsedHz,
|
|
770
|
+
cpuUsagePct: cpuPct,
|
|
771
|
+
memoryCapacityBytes: memCapacity,
|
|
772
|
+
memoryUsedBytes: memUsed,
|
|
773
|
+
memoryUsagePct: memPct,
|
|
774
|
+
storageCapacityBytes: storageCapacity,
|
|
775
|
+
storageUsedBytes: storageUsed,
|
|
776
|
+
storageUsagePct: storagePct,
|
|
777
|
+
iops: iops ?? null,
|
|
778
|
+
avgIoLatencyMs: latencyUsecs != null ? usecsToMs(latencyUsecs) : null,
|
|
779
|
+
};
|
|
780
|
+
};
|
|
781
|
+
const imagesList = async (params, config, credentials, fetchFn) => {
|
|
782
|
+
const name = params?.name;
|
|
783
|
+
const queryParams = {};
|
|
784
|
+
if (name) {
|
|
785
|
+
queryParams.$filter = `name eq '${name}'`;
|
|
786
|
+
}
|
|
787
|
+
const { data, totalCount } = await nutanixGet('vmm', 'content/images', config, credentials, fetchFn, queryParams);
|
|
788
|
+
const items = data ?? [];
|
|
789
|
+
return {
|
|
790
|
+
images: items.map((img) => ({
|
|
791
|
+
name: img.name ?? null,
|
|
792
|
+
extId: img.extId ?? null,
|
|
793
|
+
type: img.type ?? img.imageType ?? null,
|
|
794
|
+
sizeBytes: img.sizeBytes ?? img.size ?? null,
|
|
795
|
+
sourceClusterId: img.source?.clusterExtId ?? img.sourceClusterId ?? null,
|
|
796
|
+
description: img.description ?? null,
|
|
797
|
+
createTime: img.createTime ?? null,
|
|
798
|
+
})),
|
|
799
|
+
totalCount: totalCount ?? items.length,
|
|
800
|
+
};
|
|
801
|
+
};
|
|
802
|
+
const vmsByHost = async (params, config, credentials, fetchFn) => {
|
|
803
|
+
const hostId = params?.host_id;
|
|
804
|
+
if (!hostId)
|
|
805
|
+
throw new Error('host_id parameter is required');
|
|
806
|
+
const { data, totalCount } = await nutanixGet('vmm', 'ahv/config/vms', config, credentials, fetchFn, { $filter: `hostExtId eq '${hostId}'` });
|
|
807
|
+
const items = data ?? [];
|
|
808
|
+
return {
|
|
809
|
+
vms: items.map((v) => ({
|
|
810
|
+
name: v.name ?? null,
|
|
811
|
+
extId: v.extId ?? null,
|
|
812
|
+
powerState: v.powerState ?? null,
|
|
813
|
+
numSockets: v.numSockets ?? null,
|
|
814
|
+
numCoresPerSocket: v.numCoresPerSocket ?? null,
|
|
815
|
+
memorySizeMb: v.memorySizeBytes != null
|
|
816
|
+
? Math.round(v.memorySizeBytes / 1048576)
|
|
817
|
+
: (v.memorySizeMb ?? null),
|
|
818
|
+
description: v.description ?? null,
|
|
819
|
+
})),
|
|
820
|
+
totalCount: totalCount ?? items.length,
|
|
821
|
+
hostId,
|
|
822
|
+
};
|
|
823
|
+
};
|
|
824
|
+
// --- Pack definition ---
|
|
825
|
+
export const nutanixPack = {
|
|
826
|
+
manifest: {
|
|
827
|
+
name: 'nutanix',
|
|
828
|
+
type: 'integration',
|
|
829
|
+
version: '0.1.0',
|
|
830
|
+
description: 'Nutanix Prism Central — clusters, VMs, hosts, alerts, storage, categories, networks, and tasks via v4 REST API',
|
|
831
|
+
requires: { groups: [], files: [], commands: [] },
|
|
832
|
+
probes: [
|
|
833
|
+
{
|
|
834
|
+
name: 'clusters.list',
|
|
835
|
+
description: 'List Nutanix clusters with health and configuration',
|
|
836
|
+
capability: 'observe',
|
|
837
|
+
params: {
|
|
838
|
+
name: { type: 'string', description: 'Filter by cluster name', required: false },
|
|
839
|
+
},
|
|
840
|
+
timeout: 15000,
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: 'hosts.list',
|
|
844
|
+
description: 'List hypervisor hosts with hardware and network details',
|
|
845
|
+
capability: 'observe',
|
|
846
|
+
params: {
|
|
847
|
+
cluster_id: {
|
|
848
|
+
type: 'string',
|
|
849
|
+
description: 'Filter by cluster extId',
|
|
850
|
+
required: false,
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
timeout: 15000,
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
name: 'vms.list',
|
|
857
|
+
description: 'List AHV virtual machines with filtering',
|
|
858
|
+
capability: 'observe',
|
|
859
|
+
params: {
|
|
860
|
+
name: { type: 'string', description: 'Filter by VM name', required: false },
|
|
861
|
+
power_state: {
|
|
862
|
+
type: 'string',
|
|
863
|
+
description: 'Filter by power state (ON/OFF)',
|
|
864
|
+
required: false,
|
|
865
|
+
},
|
|
866
|
+
cluster_id: {
|
|
867
|
+
type: 'string',
|
|
868
|
+
description: 'Filter by cluster extId',
|
|
869
|
+
required: false,
|
|
870
|
+
},
|
|
871
|
+
limit: { type: 'number', description: 'Max results (default: 50)', required: false },
|
|
872
|
+
},
|
|
873
|
+
timeout: 15000,
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
name: 'vm.detail',
|
|
877
|
+
description: 'Full VM configuration with disks, NICs, boot config, and categories',
|
|
878
|
+
capability: 'observe',
|
|
879
|
+
params: {
|
|
880
|
+
vm_id: { type: 'string', description: 'VM extId', required: true },
|
|
881
|
+
},
|
|
882
|
+
timeout: 15000,
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
name: 'vm.stats',
|
|
886
|
+
description: 'VM performance stats — CPU, memory, IOPS, latency, network I/O',
|
|
887
|
+
capability: 'observe',
|
|
888
|
+
params: {
|
|
889
|
+
vm_id: { type: 'string', description: 'VM extId', required: true },
|
|
890
|
+
},
|
|
891
|
+
timeout: 30000,
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
name: 'alerts.list',
|
|
895
|
+
description: 'List alerts with severity, time range, and entity type filters',
|
|
896
|
+
capability: 'observe',
|
|
897
|
+
params: {
|
|
898
|
+
severity: {
|
|
899
|
+
type: 'string',
|
|
900
|
+
description: 'Filter by severity (CRITICAL/WARNING/INFO)',
|
|
901
|
+
required: false,
|
|
902
|
+
},
|
|
903
|
+
resolved: {
|
|
904
|
+
type: 'boolean',
|
|
905
|
+
description: 'Filter by resolution status',
|
|
906
|
+
required: false,
|
|
907
|
+
},
|
|
908
|
+
hours: {
|
|
909
|
+
type: 'number',
|
|
910
|
+
description: 'Only alerts from the last N hours',
|
|
911
|
+
required: false,
|
|
912
|
+
},
|
|
913
|
+
entity_type: {
|
|
914
|
+
type: 'string',
|
|
915
|
+
description: 'Filter by source entity type',
|
|
916
|
+
required: false,
|
|
917
|
+
},
|
|
918
|
+
limit: { type: 'number', description: 'Max results (default: 50)', required: false },
|
|
919
|
+
},
|
|
920
|
+
timeout: 15000,
|
|
921
|
+
},
|
|
922
|
+
{
|
|
923
|
+
name: 'alerts.summary',
|
|
924
|
+
description: 'Alert summary grouped by severity and entity type',
|
|
925
|
+
capability: 'observe',
|
|
926
|
+
params: {},
|
|
927
|
+
timeout: 15000,
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
name: 'storage.containers',
|
|
931
|
+
description: 'Storage containers with capacity, usage, and data services status',
|
|
932
|
+
capability: 'observe',
|
|
933
|
+
params: {
|
|
934
|
+
cluster_id: {
|
|
935
|
+
type: 'string',
|
|
936
|
+
description: 'Filter by cluster extId',
|
|
937
|
+
required: false,
|
|
938
|
+
},
|
|
939
|
+
},
|
|
940
|
+
timeout: 15000,
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
name: 'categories.list',
|
|
944
|
+
description: 'List Prism categories',
|
|
945
|
+
capability: 'observe',
|
|
946
|
+
params: {
|
|
947
|
+
key: { type: 'string', description: 'Filter by category key', required: false },
|
|
948
|
+
},
|
|
949
|
+
timeout: 15000,
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
name: 'categories.entities',
|
|
953
|
+
description: 'Find entities tagged with a specific category key:value pair (v3 API)',
|
|
954
|
+
capability: 'observe',
|
|
955
|
+
params: {
|
|
956
|
+
key: { type: 'string', description: 'Category key', required: true },
|
|
957
|
+
value: { type: 'string', description: 'Category value', required: true },
|
|
958
|
+
},
|
|
959
|
+
timeout: 15000,
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
name: 'networks.list',
|
|
963
|
+
description: 'List subnets/networks with VLAN, IP config, and cluster assignment',
|
|
964
|
+
capability: 'observe',
|
|
965
|
+
params: {
|
|
966
|
+
cluster_id: {
|
|
967
|
+
type: 'string',
|
|
968
|
+
description: 'Filter by cluster extId',
|
|
969
|
+
required: false,
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
timeout: 15000,
|
|
973
|
+
},
|
|
974
|
+
{
|
|
975
|
+
name: 'tasks.recent',
|
|
976
|
+
description: 'Recent Prism tasks with failure and long-running detection',
|
|
977
|
+
capability: 'observe',
|
|
978
|
+
params: {
|
|
979
|
+
hours: {
|
|
980
|
+
type: 'number',
|
|
981
|
+
description: 'Lookback window in hours (default: 24)',
|
|
982
|
+
required: false,
|
|
983
|
+
},
|
|
984
|
+
status: {
|
|
985
|
+
type: 'string',
|
|
986
|
+
description: 'Filter by status (e.g. FAILED)',
|
|
987
|
+
required: false,
|
|
988
|
+
},
|
|
989
|
+
limit: { type: 'number', description: 'Max results (default: 50)', required: false },
|
|
990
|
+
},
|
|
991
|
+
timeout: 15000,
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
name: 'cluster.health',
|
|
995
|
+
description: 'Composite health check — cluster info, hosts, critical alerts, and storage capacity',
|
|
996
|
+
capability: 'observe',
|
|
997
|
+
params: {
|
|
998
|
+
cluster_id: {
|
|
999
|
+
type: 'string',
|
|
1000
|
+
description: 'Cluster extId (uses first cluster if omitted)',
|
|
1001
|
+
required: false,
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
timeout: 30000,
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
name: 'vm.snapshots',
|
|
1008
|
+
description: 'VM recovery points / snapshots with age and expiration warnings',
|
|
1009
|
+
capability: 'observe',
|
|
1010
|
+
params: {
|
|
1011
|
+
vm_id: { type: 'string', description: 'VM extId', required: true },
|
|
1012
|
+
},
|
|
1013
|
+
timeout: 15000,
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
name: 'protection.policies',
|
|
1017
|
+
description: 'Data protection policies with RPO, retention, and coverage',
|
|
1018
|
+
capability: 'observe',
|
|
1019
|
+
params: {
|
|
1020
|
+
vm_id: {
|
|
1021
|
+
type: 'string',
|
|
1022
|
+
description: 'VM extId — show only policies covering this VM',
|
|
1023
|
+
required: false,
|
|
1024
|
+
},
|
|
1025
|
+
},
|
|
1026
|
+
timeout: 15000,
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
name: 'lifecycle.status',
|
|
1030
|
+
description: 'LCM entity versions and available updates (AOS, hypervisor, firmware, NCC)',
|
|
1031
|
+
capability: 'observe',
|
|
1032
|
+
params: {},
|
|
1033
|
+
timeout: 15000,
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
name: 'host.stats',
|
|
1037
|
+
description: 'Host performance stats with CPU/memory threshold warnings',
|
|
1038
|
+
capability: 'observe',
|
|
1039
|
+
params: {
|
|
1040
|
+
host_id: { type: 'string', description: 'Host extId', required: true },
|
|
1041
|
+
},
|
|
1042
|
+
timeout: 15000,
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
name: 'cluster.stats',
|
|
1046
|
+
description: 'Aggregate cluster metrics — CPU, memory, storage utilization, IOPS, latency',
|
|
1047
|
+
capability: 'observe',
|
|
1048
|
+
params: {
|
|
1049
|
+
cluster_id: { type: 'string', description: 'Cluster extId', required: true },
|
|
1050
|
+
},
|
|
1051
|
+
timeout: 15000,
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
name: 'images.list',
|
|
1055
|
+
description: 'List disk images and ISOs in the image library',
|
|
1056
|
+
capability: 'observe',
|
|
1057
|
+
params: {
|
|
1058
|
+
name: { type: 'string', description: 'Filter by image name', required: false },
|
|
1059
|
+
},
|
|
1060
|
+
timeout: 15000,
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
name: 'vms.by_host',
|
|
1064
|
+
description: 'List all VMs on a specific host — useful for maintenance planning',
|
|
1065
|
+
capability: 'observe',
|
|
1066
|
+
params: {
|
|
1067
|
+
host_id: { type: 'string', description: 'Host extId', required: true },
|
|
1068
|
+
},
|
|
1069
|
+
timeout: 15000,
|
|
1070
|
+
},
|
|
1071
|
+
],
|
|
1072
|
+
runbook: {
|
|
1073
|
+
category: 'hyperconverged',
|
|
1074
|
+
probes: ['clusters.list', 'alerts.summary', 'storage.containers'],
|
|
1075
|
+
parallel: true,
|
|
1076
|
+
},
|
|
1077
|
+
},
|
|
1078
|
+
handlers: {
|
|
1079
|
+
'clusters.list': clustersList,
|
|
1080
|
+
'hosts.list': hostsList,
|
|
1081
|
+
'vms.list': vmsList,
|
|
1082
|
+
'vm.detail': vmDetail,
|
|
1083
|
+
'vm.stats': vmStats,
|
|
1084
|
+
'alerts.list': alertsList,
|
|
1085
|
+
'alerts.summary': alertsSummary,
|
|
1086
|
+
'storage.containers': storageContainers,
|
|
1087
|
+
'categories.list': categoriesList,
|
|
1088
|
+
'categories.entities': categoriesEntities,
|
|
1089
|
+
'networks.list': networksList,
|
|
1090
|
+
'tasks.recent': tasksRecent,
|
|
1091
|
+
'cluster.health': clusterHealth,
|
|
1092
|
+
'vm.snapshots': vmSnapshots,
|
|
1093
|
+
'protection.policies': protectionPolicies,
|
|
1094
|
+
'lifecycle.status': lifecycleStatus,
|
|
1095
|
+
'host.stats': hostStats,
|
|
1096
|
+
'cluster.stats': clusterStats,
|
|
1097
|
+
'images.list': imagesList,
|
|
1098
|
+
'vms.by_host': vmsByHost,
|
|
1099
|
+
},
|
|
1100
|
+
testConnection: async (config, credentials, fetchFn) => {
|
|
1101
|
+
const url = nutanixUrl(config.endpoint, 'clustermgmt', 'config/clusters');
|
|
1102
|
+
const fullUrl = new URL(url);
|
|
1103
|
+
fullUrl.searchParams.set('$limit', '1');
|
|
1104
|
+
const headers = {
|
|
1105
|
+
Accept: 'application/json',
|
|
1106
|
+
...buildAuthHeaders(credentials),
|
|
1107
|
+
...config.headers,
|
|
1108
|
+
};
|
|
1109
|
+
const res = await fetchFn(fullUrl.toString(), { headers });
|
|
1110
|
+
if (!res.ok)
|
|
1111
|
+
return false;
|
|
1112
|
+
const body = (await res.json());
|
|
1113
|
+
return body.data != null;
|
|
1114
|
+
},
|
|
1115
|
+
};
|
|
1116
|
+
//# sourceMappingURL=nutanix.js.map
|