diff --git a/src/PSProxmoxVE.Core/Models/Cluster/PveClusterNode.cs b/src/PSProxmoxVE.Core/Models/Cluster/PveClusterNode.cs new file mode 100644 index 0000000..ea551d8 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Cluster/PveClusterNode.cs @@ -0,0 +1,69 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Cluster; + +/// +/// Represents a node entry within the cluster status response, +/// as returned by the /cluster/status endpoint when the item type is "node". +/// +public class PveClusterNode +{ + /// + /// The node name (e.g., "pve", "pve2"). + /// + [JsonPropertyName("name")] + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Indicates whether this node is currently online (1) or offline (0). + /// + [JsonPropertyName("online")] + [JsonProperty("online")] + public int? Online { get; set; } + + /// + /// Indicates whether this is the local node making the request (1) or a remote node (0). + /// + [JsonPropertyName("local")] + [JsonProperty("local")] + public int? Local { get; set; } + + /// + /// The numeric node ID within the cluster Corosync configuration. + /// + [JsonPropertyName("nodeid")] + [JsonProperty("nodeid")] + public int? NodeId { get; set; } + + /// + /// The IP address used for cluster communication on this node. + /// + [JsonPropertyName("ip")] + [JsonProperty("ip")] + public string? Ip { get; set; } + + /// + /// The cluster membership level of this node (e.g., "c" for configured member). + /// + [JsonPropertyName("level")] + [JsonProperty("level")] + public string? Level { get; set; } + + /// + /// The item type as returned by the cluster status endpoint (always "node" for this model). + /// + [JsonPropertyName("type")] + [JsonProperty("type")] + public string? Type { get; set; } + + /// + public override string ToString() + { + var onlineStr = Online is 1 ? "online" : "offline"; + var localStr = Local is 1 ? " [local]" : string.Empty; + return $"Node: {Name}{localStr} | ID: {NodeId?.ToString() ?? "N/A"} | " + + $"IP: {Ip ?? "N/A"} | {onlineStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Cluster/PveClusterStatus.cs b/src/PSProxmoxVE.Core/Models/Cluster/PveClusterStatus.cs new file mode 100644 index 0000000..1c8bc35 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Cluster/PveClusterStatus.cs @@ -0,0 +1,95 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Cluster; + +/// +/// Represents an entry in the mixed array returned by the /cluster/status endpoint. +/// Each entry has a Type of either "cluster" (cluster-wide info) or "node" (per-node info). +/// Filter by to distinguish cluster vs. node entries. +/// +public class PveClusterStatus +{ + /// + /// The entry type: "cluster" for the cluster-wide record, or "node" for a node record. + /// + [JsonPropertyName("type")] + [JsonProperty("type")] + public string? Type { get; set; } + + /// + /// The name of the cluster (present when Type == "cluster") or the node name (when Type == "node"). + /// + [JsonPropertyName("name")] + [JsonProperty("name")] + public string? Name { get; set; } + + /// + /// The total number of nodes in the cluster. Only present when Type == "cluster". + /// + [JsonPropertyName("nodes")] + [JsonProperty("nodes")] + public int? Nodes { get; set; } + + /// + /// Indicates whether the cluster currently has quorum (1) or not (0). + /// Without quorum, most write operations are blocked to maintain data consistency. + /// Only present when Type == "cluster". + /// + [JsonPropertyName("quorate")] + [JsonProperty("quorate")] + public int? Quorate { get; set; } + + /// + /// The cluster configuration version number. Only present when Type == "cluster". + /// + [JsonPropertyName("version")] + [JsonProperty("version")] + public int? Version { get; set; } + + /// + /// The IP address used for cluster communication on this node. + /// Only present when Type == "node". + /// + [JsonPropertyName("ip")] + [JsonProperty("ip")] + public string? Ip { get; set; } + + /// + /// Indicates whether this node is currently online (1) or offline (0). + /// Only present when Type == "node". + /// + [JsonPropertyName("online")] + [JsonProperty("online")] + public int? Online { get; set; } + + /// + /// Indicates whether this is the local node making the request (1) or a remote node (0). + /// Only present when Type == "node". + /// + [JsonPropertyName("local")] + [JsonProperty("local")] + public int? Local { get; set; } + + /// + /// The numeric node ID within the cluster Corosync configuration. + /// Only present when Type == "node". + /// + [JsonPropertyName("nodeid")] + [JsonProperty("nodeid")] + public int? NodeId { get; set; } + + /// + public override string ToString() + { + if (Type == "node") + { + var onlineStr = Online is 1 ? "online" : "offline"; + var localStr = Local is 1 ? " [local]" : string.Empty; + return $"Node: {Name ?? "N/A"}{localStr} | IP: {Ip ?? "N/A"} | {onlineStr}"; + } + var quorateStr = Quorate is 1 ? "quorate" : "NO QUORUM"; + return $"Cluster: {Name ?? "N/A"} | Nodes: {Nodes?.ToString() ?? "N/A"} | " + + $"{quorateStr} | Config Version: {Version?.ToString() ?? "N/A"}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Containers/PveContainer.cs b/src/PSProxmoxVE.Core/Models/Containers/PveContainer.cs new file mode 100644 index 0000000..ab84e37 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Containers/PveContainer.cs @@ -0,0 +1,114 @@ +using System; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Containers; + +/// +/// Represents a Linux Container (LXC) as returned by the cluster or node container list endpoints. +/// +public class PveContainer +{ + /// + /// The unique container identifier. + /// + [JsonPropertyName("vmid")] + [JsonProperty("vmid")] + public int VmId { get; set; } + + /// + /// The hostname / display name of the container. + /// + [JsonPropertyName("name")] + [JsonProperty("name")] + public string? Name { get; set; } + + /// + /// The current runtime status of the container (e.g., "running", "stopped"). + /// + [JsonPropertyName("status")] + [JsonProperty("status")] + public string? Status { get; set; } + + /// + /// The node on which the container resides. + /// + [JsonPropertyName("node")] + [JsonProperty("node")] + public string? Node { get; set; } + + /// + /// The number of CPU cores assigned to the container. + /// + [JsonPropertyName("cpus")] + [JsonProperty("cpus")] + public int? CpuCount { get; set; } + + /// + /// Maximum memory allocated to the container, in bytes. + /// + [JsonPropertyName("maxmem")] + [JsonProperty("maxmem")] + public long? MaxMem { get; set; } + + /// + /// Maximum swap space allocated to the container, in bytes. + /// + [JsonPropertyName("maxswap")] + [JsonProperty("maxswap")] + public long? MaxSwap { get; set; } + + /// + /// Root filesystem disk size allocated to the container, in bytes. + /// + [JsonPropertyName("maxdisk")] + [JsonProperty("maxdisk")] + public long? RootFsSize { get; set; } + + /// + /// The OS template type used for this container (e.g., "debian", "ubuntu"). + /// + [JsonPropertyName("ostype")] + [JsonProperty("ostype")] + public string? OsType { get; set; } + + /// + /// Indicates whether the container runs in unprivileged mode (1) or privileged mode (0). + /// + [JsonPropertyName("unprivileged")] + [JsonProperty("unprivileged")] + public int? Unprivileged { get; set; } + + /// + /// The current lock type applied to the container, if any (e.g., "migrate", "backup"). + /// + [JsonPropertyName("lock")] + [JsonProperty("lock")] + public string? Lock { get; set; } + + /// + /// Container uptime in seconds. + /// + [JsonPropertyName("uptime")] + [JsonProperty("uptime")] + public long? Uptime { get; set; } + + /// + /// Semicolon-separated list of tags assigned to the container. + /// + [JsonPropertyName("tags")] + [JsonProperty("tags")] + public string? Tags { get; set; } + + /// + public override string ToString() + { + var maxMemMb = MaxMem.HasValue ? $"{MaxMem.Value / 1024 / 1024} MB" : "N/A"; + var rootFsGb = RootFsSize.HasValue ? $"{RootFsSize.Value / 1024 / 1024 / 1024} GB" : "N/A"; + var uptimeStr = Uptime.HasValue ? TimeSpan.FromSeconds(Uptime.Value).ToString(@"d\.hh\:mm\:ss") : "N/A"; + var privStr = Unprivileged is 1 ? "unprivileged" : "privileged"; + return $"CT {VmId}: {Name ?? "(unnamed)"} | Node: {Node ?? "N/A"} | " + + $"Status: {Status ?? "N/A"} | CPUs: {CpuCount?.ToString() ?? "N/A"} | " + + $"Mem: {maxMemMb} | Disk: {rootFsGb} | {privStr} | Uptime: {uptimeStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Containers/PveContainerConfig.cs b/src/PSProxmoxVE.Core/Models/Containers/PveContainerConfig.cs new file mode 100644 index 0000000..7461668 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Containers/PveContainerConfig.cs @@ -0,0 +1,236 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Containers; + +/// +/// Represents the configuration of a Linux Container (LXC), +/// as returned by the /nodes/{node}/lxc/{vmid}/config endpoint. +/// +public class PveContainerConfig +{ + // ------------------------------------------------------------------------- + // Identity / OS + // ------------------------------------------------------------------------- + + /// + /// The container hostname. + /// + [JsonPropertyName("hostname")] + [JsonProperty("hostname")] + public string? Hostname { get; set; } + + /// + /// The OS type used for the container (e.g., "debian", "ubuntu", "alpine"). + /// + [JsonPropertyName("ostype")] + [JsonProperty("ostype")] + public string? OsType { get; set; } + + /// + /// Container architecture (e.g., "amd64", "arm64"). + /// + [JsonPropertyName("arch")] + [JsonProperty("arch")] + public string? Arch { get; set; } + + /// + /// Indicates whether the container runs unprivileged (1) or privileged (0). + /// + [JsonPropertyName("unprivileged")] + [JsonProperty("unprivileged")] + public int? Unprivileged { get; set; } + + // ------------------------------------------------------------------------- + // Resources + // ------------------------------------------------------------------------- + + /// + /// Memory limit in megabytes. + /// + [JsonPropertyName("memory")] + [JsonProperty("memory")] + public int? Memory { get; set; } + + /// + /// Swap space limit in megabytes. 0 disables swap. + /// + [JsonPropertyName("swap")] + [JsonProperty("swap")] + public int? Swap { get; set; } + + /// + /// Number of CPU cores allocated to the container. + /// + [JsonPropertyName("cores")] + [JsonProperty("cores")] + public int? Cores { get; set; } + + /// + /// Root filesystem configuration string (e.g., "local-lvm:8"). + /// + [JsonPropertyName("rootfs")] + [JsonProperty("rootfs")] + public string? RootFs { get; set; } + + // ------------------------------------------------------------------------- + // Metadata + // ------------------------------------------------------------------------- + + /// + /// Human-readable description or notes for the container. + /// + [JsonPropertyName("description")] + [JsonProperty("description")] + public string? Description { get; set; } + + /// + /// Semicolon-separated list of tags assigned to the container. + /// + [JsonPropertyName("tags")] + [JsonProperty("tags")] + public string? Tags { get; set; } + + /// + /// When set to 1, prevents the container from being deleted or modified accidentally. + /// + [JsonPropertyName("protection")] + [JsonProperty("protection")] + public int? Protection { get; set; } + + /// + /// Startup / shutdown order and delay configuration string. + /// + [JsonPropertyName("startup")] + [JsonProperty("startup")] + public string? Startup { get; set; } + + /// + /// Feature flags for the container (e.g., "nesting=1,keyctl=1"). + /// + [JsonPropertyName("features")] + [JsonProperty("features")] + public string? Features { get; set; } + + // ------------------------------------------------------------------------- + // Network interfaces (0-7) + // ------------------------------------------------------------------------- + + /// Network interface 0 configuration string. + [JsonPropertyName("net0")] + [JsonProperty("net0")] + public string? Net0 { get; set; } + + /// Network interface 1 configuration string. + [JsonPropertyName("net1")] + [JsonProperty("net1")] + public string? Net1 { get; set; } + + /// Network interface 2 configuration string. + [JsonPropertyName("net2")] + [JsonProperty("net2")] + public string? Net2 { get; set; } + + /// Network interface 3 configuration string. + [JsonPropertyName("net3")] + [JsonProperty("net3")] + public string? Net3 { get; set; } + + /// Network interface 4 configuration string. + [JsonPropertyName("net4")] + [JsonProperty("net4")] + public string? Net4 { get; set; } + + /// Network interface 5 configuration string. + [JsonPropertyName("net5")] + [JsonProperty("net5")] + public string? Net5 { get; set; } + + /// Network interface 6 configuration string. + [JsonPropertyName("net6")] + [JsonProperty("net6")] + public string? Net6 { get; set; } + + /// Network interface 7 configuration string. + [JsonPropertyName("net7")] + [JsonProperty("net7")] + public string? Net7 { get; set; } + + // ------------------------------------------------------------------------- + // Mount points (0-7) + // ------------------------------------------------------------------------- + + /// Mount point 0 configuration string (e.g., "local-lvm:10,mp=/data"). + [JsonPropertyName("mp0")] + [JsonProperty("mp0")] + public string? Mp0 { get; set; } + + /// Mount point 1 configuration string. + [JsonPropertyName("mp1")] + [JsonProperty("mp1")] + public string? Mp1 { get; set; } + + /// Mount point 2 configuration string. + [JsonPropertyName("mp2")] + [JsonProperty("mp2")] + public string? Mp2 { get; set; } + + /// Mount point 3 configuration string. + [JsonPropertyName("mp3")] + [JsonProperty("mp3")] + public string? Mp3 { get; set; } + + /// Mount point 4 configuration string. + [JsonPropertyName("mp4")] + [JsonProperty("mp4")] + public string? Mp4 { get; set; } + + /// Mount point 5 configuration string. + [JsonPropertyName("mp5")] + [JsonProperty("mp5")] + public string? Mp5 { get; set; } + + /// Mount point 6 configuration string. + [JsonPropertyName("mp6")] + [JsonProperty("mp6")] + public string? Mp6 { get; set; } + + /// Mount point 7 configuration string. + [JsonPropertyName("mp7")] + [JsonProperty("mp7")] + public string? Mp7 { get; set; } + + // ------------------------------------------------------------------------- + // DNS / SSH + // ------------------------------------------------------------------------- + + /// + /// DNS nameserver(s) for the container. + /// + [JsonPropertyName("nameserver")] + [JsonProperty("nameserver")] + public string? Nameserver { get; set; } + + /// + /// DNS search domain for the container. + /// + [JsonPropertyName("searchdomain")] + [JsonProperty("searchdomain")] + public string? Searchdomain { get; set; } + + /// + /// SSH public keys injected into the container's root account. + /// + [JsonPropertyName("ssh-public-keys")] + [JsonProperty("ssh-public-keys")] + public string? SshPublicKeys { get; set; } + + /// + public override string ToString() + { + return $"Config | Hostname: {Hostname ?? "N/A"} | OS: {OsType ?? "N/A"} | " + + $"Cores: {Cores?.ToString() ?? "N/A"} | Memory: {Memory?.ToString() ?? "N/A"} MB | " + + $"Swap: {Swap?.ToString() ?? "N/A"} MB | Arch: {Arch ?? "N/A"} | " + + $"Unprivileged: {(Unprivileged is 1 ? "yes" : "no")}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Network/PveNetwork.cs b/src/PSProxmoxVE.Core/Models/Network/PveNetwork.cs new file mode 100644 index 0000000..397d41a --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Network/PveNetwork.cs @@ -0,0 +1,130 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Network; + +/// +/// Represents a network interface configuration on a Proxmox VE node, +/// as returned by the /nodes/{node}/network endpoint. +/// +public class PveNetwork +{ + /// + /// The interface name (e.g., "vmbr0", "eth0", "bond0"). + /// + [JsonPropertyName("iface")] + [JsonProperty("iface")] + public string Iface { get; set; } = string.Empty; + + /// + /// The interface type (e.g., "bridge", "bond", "eth", "vlan", "OVSBridge"). + /// + [JsonPropertyName("type")] + [JsonProperty("type")] + public string? Type { get; set; } + + /// + /// The IPv4 address assigned to this interface. + /// + [JsonPropertyName("address")] + [JsonProperty("address")] + public string? Address { get; set; } + + /// + /// The IPv4 netmask for this interface. + /// + [JsonPropertyName("netmask")] + [JsonProperty("netmask")] + public string? Netmask { get; set; } + + /// + /// The IPv4 default gateway associated with this interface. + /// + [JsonPropertyName("gateway")] + [JsonProperty("gateway")] + public string? Gateway { get; set; } + + /// + /// Space-separated list of physical ports attached to this bridge. + /// + [JsonPropertyName("bridge_ports")] + [JsonProperty("bridge_ports")] + public string? BridgePorts { get; set; } + + /// + /// Space-separated list of slave interfaces for bond/team interfaces. + /// + [JsonPropertyName("slaves")] + [JsonProperty("slaves")] + public string? BondSlaves { get; set; } + + /// + /// VLAN ID for VLAN sub-interfaces. + /// + [JsonPropertyName("vlan-id")] + [JsonProperty("vlan-id")] + public int? VlanId { get; set; } + + /// + /// The Maximum Transmission Unit in bytes. + /// + [JsonPropertyName("mtu")] + [JsonProperty("mtu")] + public int? Mtu { get; set; } + + /// + /// Indicates whether the interface is brought up automatically at boot (1) or not (0). + /// + [JsonPropertyName("autostart")] + [JsonProperty("autostart")] + public int? Autostart { get; set; } + + /// + /// Indicates whether the interface is currently active/up (1) or down (0). + /// + [JsonPropertyName("active")] + [JsonProperty("active")] + public int? Active { get; set; } + + /// + /// Optional comment or description for this interface. + /// + [JsonPropertyName("comments")] + [JsonProperty("comments")] + public string? Comments { get; set; } + + /// + /// IPv4 address in CIDR notation (e.g., "192.168.1.1/24"). + /// + [JsonPropertyName("cidr")] + [JsonProperty("cidr")] + public string? Cidr { get; set; } + + /// + /// Indicates whether VLAN-aware bridging is enabled on this bridge (1) or not (0). + /// + [JsonPropertyName("bridge_vlan_aware")] + [JsonProperty("bridge_vlan_aware")] + public int? BridgeVlanAware { get; set; } + + /// + /// The bonding mode for bond interfaces (e.g., "active-backup", "802.3ad", "balance-rr"). + /// + [JsonPropertyName("bond_mode")] + [JsonProperty("bond_mode")] + public string? BondMode { get; set; } + + /// + /// The node this network interface belongs to. Populated by cmdlets after retrieval. + /// + public string? Node { get; set; } + + /// + public override string ToString() + { + var addr = Cidr ?? Address ?? "N/A"; + var activeStr = Active is 1 ? "up" : "down"; + return $"Network: {Iface} | Type: {Type ?? "N/A"} | Address: {addr} | " + + $"GW: {Gateway ?? "N/A"} | MTU: {Mtu?.ToString() ?? "N/A"} | {activeStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Network/PveSdnVnet.cs b/src/PSProxmoxVE.Core/Models/Network/PveSdnVnet.cs new file mode 100644 index 0000000..f8612e6 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Network/PveSdnVnet.cs @@ -0,0 +1,62 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Network; + +/// +/// Represents a Software-Defined Networking (SDN) virtual network (VNet) as returned by +/// the /cluster/sdn/vnets endpoint. +/// Available in Proxmox VE 8.0+. +/// +public class PveSdnVnet +{ + /// + /// The unique VNet identifier. + /// + [JsonPropertyName("vnet")] + [JsonProperty("vnet")] + public string Vnet { get; set; } = string.Empty; + + /// + /// The SDN zone this VNet belongs to. + /// + [JsonPropertyName("zone")] + [JsonProperty("zone")] + public string? Zone { get; set; } + + /// + /// The VLAN or VXLAN tag associated with this VNet. + /// + [JsonPropertyName("tag")] + [JsonProperty("tag")] + public int? Tag { get; set; } + + /// + /// An optional human-readable alias for this VNet. + /// + [JsonPropertyName("alias")] + [JsonProperty("alias")] + public string? Alias { get; set; } + + /// + /// Indicates whether VLAN-aware mode is enabled on this VNet (1) or not (0). + /// + [JsonPropertyName("vlanaware")] + [JsonProperty("vlanaware")] + public int? VlanAware { get; set; } + + /// + /// Optional comment or description for this SDN VNet. + /// + [JsonPropertyName("comments")] + [JsonProperty("comments")] + public string? Comments { get; set; } + + /// + public override string ToString() + { + var vlanAwareStr = VlanAware is 1 ? " [vlan-aware]" : string.Empty; + return $"SDN VNet: {Vnet}{vlanAwareStr} | Zone: {Zone ?? "N/A"} | " + + $"Tag: {Tag?.ToString() ?? "N/A"} | Alias: {Alias ?? "N/A"}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Network/PveSdnZone.cs b/src/PSProxmoxVE.Core/Models/Network/PveSdnZone.cs new file mode 100644 index 0000000..39813fd --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Network/PveSdnZone.cs @@ -0,0 +1,90 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Network; + +/// +/// Represents a Software-Defined Networking (SDN) zone as returned by +/// the /cluster/sdn/zones endpoint. +/// Available in Proxmox VE 8.0+. +/// +public class PveSdnZone +{ + /// + /// The unique zone identifier. + /// + [JsonPropertyName("zone")] + [JsonProperty("zone")] + public string Zone { get; set; } = string.Empty; + + /// + /// The zone type (e.g., "simple", "vlan", "qinq", "vxlan", "evpn"). + /// + [JsonPropertyName("type")] + [JsonProperty("type")] + public string? Type { get; set; } + + /// + /// The DNS server address associated with this zone. + /// + [JsonPropertyName("dns")] + [JsonProperty("dns")] + public string? Dns { get; set; } + + /// + /// The reverse DNS zone name for this SDN zone. + /// + [JsonPropertyName("reversedns")] + [JsonProperty("reversedns")] + public string? Reversedns { get; set; } + + /// + /// The DNS zone name used for forward lookups. + /// + [JsonPropertyName("dnszone")] + [JsonProperty("dnszone")] + public string? DnsZone { get; set; } + + /// + /// The IPAM plugin to use for IP address management in this zone. + /// + [JsonPropertyName("ipam")] + [JsonProperty("ipam")] + public string? Ipam { get; set; } + + /// + /// The Maximum Transmission Unit in bytes for this zone. + /// + [JsonPropertyName("mtu")] + [JsonProperty("mtu")] + public int? Mtu { get; set; } + + /// + /// Comma-separated list of nodes that participate in this SDN zone. + /// + [JsonPropertyName("nodes")] + [JsonProperty("nodes")] + public string? Nodes { get; set; } + + /// + /// Indicates whether the zone has pending (unapplied) configuration changes (1) or not (0). + /// + [JsonPropertyName("pending")] + [JsonProperty("pending")] + public int? Pending { get; set; } + + /// + /// Optional comment or description for this SDN zone. + /// + [JsonPropertyName("comments")] + [JsonProperty("comments")] + public string? Comments { get; set; } + + /// + public override string ToString() + { + var pendingStr = Pending is 1 ? " [pending]" : string.Empty; + return $"SDN Zone: {Zone}{pendingStr} | Type: {Type ?? "N/A"} | DNS: {Dns ?? "N/A"} | " + + $"IPAM: {Ipam ?? "N/A"} | Nodes: {Nodes ?? "all"}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Nodes/PveNode.cs b/src/PSProxmoxVE.Core/Models/Nodes/PveNode.cs new file mode 100644 index 0000000..d0e67b7 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Nodes/PveNode.cs @@ -0,0 +1,84 @@ +using System; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Nodes; + +/// +/// Represents a Proxmox VE cluster node as returned by the /nodes endpoint. +/// +public class PveNode +{ + /// + /// The name of the node. + /// + [JsonPropertyName("node")] + [JsonProperty("node")] + public string Name { get; set; } = string.Empty; + + /// + /// The current status of the node (e.g., "online" or "offline"). + /// + [JsonPropertyName("status")] + [JsonProperty("status")] + public string Status { get; set; } = string.Empty; + + /// + /// The total number of CPU cores available on the node. + /// + [JsonPropertyName("maxcpu")] + [JsonProperty("maxcpu")] + public int? CpuCount { get; set; } + + /// + /// Total memory in bytes available on the node. + /// + [JsonPropertyName("maxmem")] + [JsonProperty("maxmem")] + public long? MemoryTotal { get; set; } + + /// + /// Memory currently in use on the node, in bytes. + /// + [JsonPropertyName("mem")] + [JsonProperty("mem")] + public long? MemoryUsed { get; set; } + + /// + /// Node uptime in seconds. + /// + [JsonPropertyName("uptime")] + [JsonProperty("uptime")] + public long? Uptime { get; set; } + + /// + /// The version of Proxmox VE running on the node. + /// + [JsonPropertyName("pveversion")] + [JsonProperty("pveversion")] + public string? PveVersion { get; set; } + + /// + /// The Linux kernel version running on the node. + /// + [JsonPropertyName("kversion")] + [JsonProperty("kversion")] + public string? KernelVersion { get; set; } + + /// + /// The 1-minute, 5-minute, and 15-minute load averages for the node. + /// + [JsonPropertyName("loadavg")] + [JsonProperty("loadavg")] + public double[]? LoadAverage { get; set; } + + /// + public override string ToString() + { + var memUsedMb = MemoryUsed.HasValue ? $"{MemoryUsed.Value / 1024 / 1024} MB" : "N/A"; + var memTotalMb = MemoryTotal.HasValue ? $"{MemoryTotal.Value / 1024 / 1024} MB" : "N/A"; + var uptimeStr = Uptime.HasValue ? TimeSpan.FromSeconds(Uptime.Value).ToString(@"d\.hh\:mm\:ss") : "N/A"; + return $"Node: {Name} | Status: {Status} | CPUs: {CpuCount?.ToString() ?? "N/A"} | " + + $"Mem: {memUsedMb}/{memTotalMb} | Uptime: {uptimeStr} | PVE: {PveVersion ?? "N/A"}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Nodes/PveNodeStatus.cs b/src/PSProxmoxVE.Core/Models/Nodes/PveNodeStatus.cs new file mode 100644 index 0000000..2d11423 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Nodes/PveNodeStatus.cs @@ -0,0 +1,148 @@ +using System; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Nodes; + +/// +/// Represents detailed status information for a single Proxmox VE node, +/// as returned by the /nodes/{node}/status endpoint. +/// +public class PveNodeStatus +{ + /// + /// The name of the node. + /// + [JsonPropertyName("node")] + [JsonProperty("node")] + public string Name { get; set; } = string.Empty; + + /// + /// The current status of the node (e.g., "online" or "offline"). + /// + [JsonPropertyName("status")] + [JsonProperty("status")] + public string Status { get; set; } = string.Empty; + + /// + /// The total number of CPU cores available on the node. + /// + [JsonPropertyName("maxcpu")] + [JsonProperty("maxcpu")] + public int? CpuCount { get; set; } + + /// + /// Total memory in bytes available on the node. + /// + [JsonPropertyName("maxmem")] + [JsonProperty("maxmem")] + public long? MemoryTotal { get; set; } + + /// + /// Memory currently in use on the node, in bytes. + /// + [JsonPropertyName("mem")] + [JsonProperty("mem")] + public long? MemoryUsed { get; set; } + + /// + /// Node uptime in seconds. + /// + [JsonPropertyName("uptime")] + [JsonProperty("uptime")] + public long? Uptime { get; set; } + + /// + /// The version of Proxmox VE running on the node. + /// + [JsonPropertyName("pveversion")] + [JsonProperty("pveversion")] + public string? PveVersion { get; set; } + + /// + /// The Linux kernel version running on the node. + /// + [JsonPropertyName("kversion")] + [JsonProperty("kversion")] + public string? KernelVersion { get; set; } + + /// + /// The 1-minute, 5-minute, and 15-minute load averages for the node. + /// + [JsonPropertyName("loadavg")] + [JsonProperty("loadavg")] + public double[]? LoadAverage { get; set; } + + /// + /// Total root filesystem size in bytes. + /// + [JsonPropertyName("rootfs.total")] + [JsonProperty("rootfs.total")] + public long? DiskTotal { get; set; } + + /// + /// Used root filesystem space in bytes. + /// + [JsonPropertyName("rootfs.used")] + [JsonProperty("rootfs.used")] + public long? DiskUsed { get; set; } + + /// + /// Current CPU utilization as a fraction between 0.0 and 1.0. + /// + [JsonPropertyName("cpu")] + [JsonProperty("cpu")] + public double? CpuUsage { get; set; } + + /// + /// Memory utilization as a percentage (0–100), computed from MemoryUsed / MemoryTotal. + /// + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] + public double? MemoryUsage => + MemoryTotal.HasValue && MemoryTotal.Value > 0 && MemoryUsed.HasValue + ? (double)MemoryUsed.Value / MemoryTotal.Value * 100.0 + : null; + + /// + /// Total swap space in bytes. + /// + [JsonPropertyName("swap.total")] + [JsonProperty("swap.total")] + public long? SwapTotal { get; set; } + + /// + /// Used swap space in bytes. + /// + [JsonPropertyName("swap.used")] + [JsonProperty("swap.used")] + public long? SwapUsed { get; set; } + + /// + /// Total bytes received over the network since boot. + /// + [JsonPropertyName("netin")] + [JsonProperty("netin")] + public long? NetworkIn { get; set; } + + /// + /// Total bytes transmitted over the network since boot. + /// + [JsonPropertyName("netout")] + [JsonProperty("netout")] + public long? NetworkOut { get; set; } + + /// + public override string ToString() + { + var memUsedMb = MemoryUsed.HasValue ? $"{MemoryUsed.Value / 1024 / 1024} MB" : "N/A"; + var memTotalMb = MemoryTotal.HasValue ? $"{MemoryTotal.Value / 1024 / 1024} MB" : "N/A"; + var cpuPct = CpuUsage.HasValue ? $"{CpuUsage.Value * 100:F1}%" : "N/A"; + var diskUsedGb = DiskUsed.HasValue ? $"{DiskUsed.Value / 1024 / 1024 / 1024} GB" : "N/A"; + var diskTotalGb = DiskTotal.HasValue ? $"{DiskTotal.Value / 1024 / 1024 / 1024} GB" : "N/A"; + var uptimeStr = Uptime.HasValue ? TimeSpan.FromSeconds(Uptime.Value).ToString(@"d\.hh\:mm\:ss") : "N/A"; + return $"Node: {Name} | Status: {Status} | CPU: {cpuPct} | " + + $"Mem: {memUsedMb}/{memTotalMb} ({MemoryUsage:F1}%) | " + + $"Disk: {diskUsedGb}/{diskTotalGb} | Uptime: {uptimeStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Storage/PveStorage.cs b/src/PSProxmoxVE.Core/Models/Storage/PveStorage.cs new file mode 100644 index 0000000..32ae490 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Storage/PveStorage.cs @@ -0,0 +1,89 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Storage; + +/// +/// Represents a Proxmox VE storage entry as returned by the /storage or /nodes/{node}/storage endpoints. +/// +public class PveStorage +{ + /// + /// The unique identifier / name of the storage pool. + /// + [JsonPropertyName("storage")] + [JsonProperty("storage")] + public string Storage { get; set; } = string.Empty; + + /// + /// The storage backend type (e.g., "dir", "lvm", "zfspool", "nfs", "cifs", "rbd"). + /// + [JsonPropertyName("type")] + [JsonProperty("type")] + public string? Type { get; set; } + + /// + /// Comma-separated list of content types this storage supports + /// (e.g., "images,rootdir,backup,snippets,iso,vztmpl"). + /// + [JsonPropertyName("content")] + [JsonProperty("content")] + public string? Content { get; set; } + + /// + /// Total capacity of the storage pool, in bytes. + /// + [JsonPropertyName("total")] + [JsonProperty("total")] + public long? Total { get; set; } + + /// + /// Used capacity of the storage pool, in bytes. + /// + [JsonPropertyName("used")] + [JsonProperty("used")] + public long? Used { get; set; } + + /// + /// Available (free) capacity of the storage pool, in bytes. + /// + [JsonPropertyName("avail")] + [JsonProperty("avail")] + public long? Available { get; set; } + + /// + /// Indicates whether the storage is enabled (1) or disabled (0) in the configuration. + /// + [JsonPropertyName("enabled")] + [JsonProperty("enabled")] + public int? Enabled { get; set; } + + /// + /// Indicates whether the storage is shared across all cluster nodes (1) or node-local (0). + /// + [JsonPropertyName("shared")] + [JsonProperty("shared")] + public int? Shared { get; set; } + + /// + /// Indicates whether the storage is currently active and reachable (1) or not (0). + /// + [JsonPropertyName("active")] + [JsonProperty("active")] + public int? Active { get; set; } + + /// The node this storage entry is associated with. Populated by cmdlets after retrieval. + public string? Node { get; set; } + + /// + public override string ToString() + { + var usedGb = Used.HasValue ? $"{Used.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A"; + var totalGb = Total.HasValue ? $"{Total.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A"; + var availGb = Available.HasValue ? $"{Available.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A"; + var activeStr = Active is 1 ? "active" : "inactive"; + var sharedStr = Shared is 1 ? "shared" : "local"; + return $"Storage: {Storage} | Type: {Type ?? "N/A"} | {activeStr} | {sharedStr} | " + + $"Used: {usedGb}/{totalGb} | Free: {availGb} | Content: {Content ?? "N/A"}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Storage/PveStorageContent.cs b/src/PSProxmoxVE.Core/Models/Storage/PveStorageContent.cs new file mode 100644 index 0000000..b56a982 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Storage/PveStorageContent.cs @@ -0,0 +1,87 @@ +using System; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Storage; + +/// +/// Represents a single content item (disk image, backup, ISO, template, etc.) +/// stored within a Proxmox VE storage pool, as returned by +/// the /nodes/{node}/storage/{storage}/content endpoint. +/// +public class PveStorageContent +{ + /// + /// The full volume identifier in the form "storage:volumename" + /// (e.g., "local-lvm:vm-100-disk-0"). + /// + [JsonPropertyName("volid")] + [JsonProperty("volid")] + public string VolId { get; set; } = string.Empty; + + /// + /// The content type of this volume (e.g., "images", "backup", "iso", "vztmpl", "rootdir"). + /// + [JsonPropertyName("content")] + [JsonProperty("content")] + public string? Content { get; set; } + + /// + /// The disk image format (e.g., "raw", "qcow2", "vmdk", "subvol"). + /// + [JsonPropertyName("format")] + [JsonProperty("format")] + public string? Format { get; set; } + + /// + /// The size of the volume in bytes. + /// + [JsonPropertyName("size")] + [JsonProperty("size")] + public long? Size { get; set; } + + /// + /// Unix timestamp of when the volume was created. + /// + [JsonPropertyName("ctime")] + [JsonProperty("ctime")] + public long? CreationTime { get; set; } + + /// + /// The VM or container ID that owns this volume, if applicable (e.g., for disk images and backups). + /// + [JsonPropertyName("vmid")] + [JsonProperty("vmid")] + public int? VmId { get; set; } + + /// + /// Optional notes or description attached to the volume (commonly used on backup files). + /// + [JsonPropertyName("notes")] + [JsonProperty("notes")] + public string? Notes { get; set; } + + /// + /// Verification status of the volume, if applicable (e.g., for backups verified with vzdump). + /// + [JsonPropertyName("verification")] + [JsonProperty("verification")] + public string? Verification { get; set; } + + /// The storage identifier this content resides on. Populated by cmdlets after retrieval. + public string? Storage { get; set; } + + /// The node this content was retrieved from. Populated by cmdlets after retrieval. + public string? Node { get; set; } + + /// + public override string ToString() + { + var sizeGb = Size.HasValue ? $"{Size.Value / 1024.0 / 1024 / 1024:F2} GB" : "N/A"; + var created = CreationTime.HasValue + ? DateTimeOffset.FromUnixTimeSeconds(CreationTime.Value).ToString("yyyy-MM-dd HH:mm:ss") + : "N/A"; + return $"Volume: {VolId} | Content: {Content ?? "N/A"} | Format: {Format ?? "N/A"} | " + + $"Size: {sizeGb} | Created: {created}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Users/PvePermission.cs b/src/PSProxmoxVE.Core/Models/Users/PvePermission.cs new file mode 100644 index 0000000..b14b397 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Users/PvePermission.cs @@ -0,0 +1,47 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Users; + +/// +/// Represents a single Access Control List (ACL) entry as returned by the /access/acl endpoint. +/// An ACL entry grants a specific role to a user or group on a particular resource path. +/// +public class PvePermission +{ + /// + /// The resource path this ACL entry applies to (e.g., "/", "/nodes/pve", "/vms/100"). + /// + [JsonPropertyName("path")] + [JsonProperty("path")] + public string? Path { get; set; } + + /// + /// The role ID granted by this ACL entry (e.g., "Administrator", "PVEVMAdmin"). + /// + [JsonPropertyName("roleid")] + [JsonProperty("roleid")] + public string? RoleId { get; set; } + + /// + /// Indicates whether the permission propagates to sub-paths (1) or is restricted to the exact path (0). + /// + [JsonPropertyName("propagate")] + [JsonProperty("propagate")] + public int? Propagate { get; set; } + + /// + /// The user ID or group ID (with "@" prefix for groups) that this ACL entry applies to. + /// Maps to the "ugid" JSON field. + /// + [JsonPropertyName("ugid")] + [JsonProperty("ugid")] + public string? UserId { get; set; } + + /// + public override string ToString() + { + var propagateStr = Propagate is 1 ? " [propagate]" : string.Empty; + return $"ACL: {UserId ?? "N/A"} | Path: {Path ?? "/"} | Role: {RoleId ?? "N/A"}{propagateStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Users/PveRole.cs b/src/PSProxmoxVE.Core/Models/Users/PveRole.cs new file mode 100644 index 0000000..49a23b7 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Users/PveRole.cs @@ -0,0 +1,41 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Users; + +/// +/// Represents a Proxmox VE role as returned by the /access/roles endpoint. +/// Roles are named collections of privileges that can be assigned to users or groups via ACLs. +/// +public class PveRole +{ + /// + /// The unique role identifier (e.g., "Administrator", "PVEVMAdmin", "PVEDatastoreUser"). + /// + [JsonPropertyName("roleid")] + [JsonProperty("roleid")] + public string RoleId { get; set; } = string.Empty; + + /// + /// Comma-separated list of privilege strings assigned to this role + /// (e.g., "VM.Allocate,VM.Config.Disk,Datastore.AllocateSpace"). + /// + [JsonPropertyName("privs")] + [JsonProperty("privs")] + public string? Privileges { get; set; } + + /// + /// Indicates whether this is a built-in / special role (1) that cannot be deleted, + /// or a custom role (0). + /// + [JsonPropertyName("special")] + [JsonProperty("special")] + public int? Special { get; set; } + + /// + public override string ToString() + { + var specialStr = Special is 1 ? " [built-in]" : string.Empty; + return $"Role: {RoleId}{specialStr} | Privs: {Privileges ?? "none"}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Users/PveUser.cs b/src/PSProxmoxVE.Core/Models/Users/PveUser.cs new file mode 100644 index 0000000..f1ca079 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Users/PveUser.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Users; + +/// +/// Represents a Proxmox VE user account as returned by the /access/users endpoint. +/// +public class PveUser +{ + /// + /// The full user ID in the format "username@realm" (e.g., "admin@pam", "user@pve"). + /// + [JsonPropertyName("userid")] + [JsonProperty("userid")] + public string UserId { get; set; } = string.Empty; + + /// + /// The user's first name. + /// + [JsonPropertyName("firstname")] + [JsonProperty("firstname")] + public string? FirstName { get; set; } + + /// + /// The user's last name. + /// + [JsonPropertyName("lastname")] + [JsonProperty("lastname")] + public string? LastName { get; set; } + + /// + /// The user's email address. + /// + [JsonPropertyName("email")] + [JsonProperty("email")] + public string? Email { get; set; } + + /// + /// Indicates whether the user account is enabled (1) or disabled (0). + /// + [JsonPropertyName("enable")] + [JsonProperty("enable")] + public int? Enabled { get; set; } + + /// + /// The authentication realm for this user (e.g., "pam", "pve", "ldap"). + /// + [JsonPropertyName("realm")] + [JsonProperty("realm")] + public string? Realm { get; set; } + + /// + /// Comma-separated list of groups this user is a member of. + /// + [JsonPropertyName("groups")] + [JsonProperty("groups")] + public string? Groups { get; set; } + + /// + /// Optional comment or description for this user account. + /// + [JsonPropertyName("comment")] + [JsonProperty("comment")] + public string? Comment { get; set; } + + /// + /// Account expiry as a Unix timestamp. 0 or null means the account never expires. + /// + [JsonPropertyName("expire")] + [JsonProperty("expire")] + public long? Expire { get; set; } + + /// + public override string ToString() + { + var fullName = string.Join(" ", new[] { FirstName, LastName }.Where(s => !string.IsNullOrWhiteSpace(s))); + var enabledStr = Enabled is 1 ? "enabled" : "disabled"; + var expireStr = Expire is > 0 ? DateTimeOffset.FromUnixTimeSeconds(Expire.Value).ToString("yyyy-MM-dd") : "never"; + return $"User: {UserId} | Name: {(string.IsNullOrEmpty(fullName) ? "N/A" : fullName)} | " + + $"{enabledStr} | Groups: {Groups ?? "none"} | Expires: {expireStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Vms/PveCloudInitConfig.cs b/src/PSProxmoxVE.Core/Models/Vms/PveCloudInitConfig.cs new file mode 100644 index 0000000..63aa9ab --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Vms/PveCloudInitConfig.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Vms +{ + /// + /// Represents the Cloud-Init specific configuration fields for a VM. + /// Fields are extracted from the full VM config. + /// + public class PveCloudInitConfig + { + /// Default user name injected by Cloud-Init. + [JsonProperty("ciuser")] + public string? CiUser { get; set; } + + /// Default user password (may be hashed). + [JsonProperty("cipassword")] + public string? CiPassword { get; set; } + + /// URL-encoded SSH public keys. + [JsonProperty("sshkeys")] + public string? SshKeys { get; set; } + + /// IP configuration for interface 0 (e.g. "ip=dhcp" or "ip=192.168.1.10/24,gw=192.168.1.1"). + [JsonProperty("ipconfig0")] + public string? IpConfig0 { get; set; } + + /// IP configuration for interface 1. + [JsonProperty("ipconfig1")] + public string? IpConfig1 { get; set; } + + /// IP configuration for interface 2. + [JsonProperty("ipconfig2")] + public string? IpConfig2 { get; set; } + + /// IP configuration for interface 3. + [JsonProperty("ipconfig3")] + public string? IpConfig3 { get; set; } + + /// DNS nameserver(s) space-separated. + [JsonProperty("nameserver")] + public string? Nameserver { get; set; } + + /// DNS search domain. + [JsonProperty("searchdomain")] + public string? Searchdomain { get; set; } + + /// Custom Cloud-Init config files (cicustom field). + [JsonProperty("cicustom")] + public string? CiCustom { get; set; } + + public override string ToString() => + $"CloudInit: User={CiUser ?? "N/A"} | IP0={IpConfig0 ?? "N/A"} | NS={Nameserver ?? "N/A"}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Vms/PveSnapshot.cs b/src/PSProxmoxVE.Core/Models/Vms/PveSnapshot.cs new file mode 100644 index 0000000..af17441 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Vms/PveSnapshot.cs @@ -0,0 +1,64 @@ +using System; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Vms; + +/// +/// Represents a VM snapshot as returned by the /nodes/{node}/qemu/{vmid}/snapshot endpoint. +/// +public class PveSnapshot +{ + /// + /// The snapshot name / identifier. + /// + [JsonPropertyName("name")] + [JsonProperty("name")] + public string Name { get; set; } = string.Empty; + + /// + /// Optional human-readable description of what this snapshot captures. + /// + [JsonPropertyName("description")] + [JsonProperty("description")] + public string? Description { get; set; } + + /// + /// Unix timestamp when the snapshot was created. + /// + [JsonPropertyName("snaptime")] + [JsonProperty("snaptime")] + public long? SnapTime { get; set; } + + /// + /// Indicates whether the VM RAM state was saved with this snapshot (1) or not (0). + /// + [JsonPropertyName("vmstate")] + [JsonProperty("vmstate")] + public int? VmState { get; set; } + + /// + /// The name of the parent snapshot, or null if this is the root snapshot. + /// + [JsonPropertyName("parent")] + [JsonProperty("parent")] + public string? Parent { get; set; } + + /// The VM ID this snapshot belongs to. Populated by cmdlets after retrieval. + public int VmId { get; set; } + + /// The node the VM resides on. Populated by cmdlets after retrieval. + public string? Node { get; set; } + + /// + public override string ToString() + { + var timeStr = SnapTime.HasValue + ? DateTimeOffset.FromUnixTimeSeconds(SnapTime.Value).ToString("yyyy-MM-dd HH:mm:ss") + : "N/A"; + var stateStr = VmState is 1 ? " [+vmstate]" : string.Empty; + var parentStr = Parent is not null ? $" | Parent: {Parent}" : string.Empty; + return $"Snapshot: {Name}{stateStr} | Created: {timeStr} | " + + $"Description: {Description ?? "(none)"}{parentStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Vms/PveTask.cs b/src/PSProxmoxVE.Core/Models/Vms/PveTask.cs new file mode 100644 index 0000000..617c2cb --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Vms/PveTask.cs @@ -0,0 +1,95 @@ +using System; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Vms; + +/// +/// Represents a Proxmox VE asynchronous task as returned by the task-related endpoints +/// (e.g., /nodes/{node}/tasks, /nodes/{node}/tasks/{upid}/status). +/// Most mutating API calls return a UPID string that identifies the created task. +/// +public class PveTask +{ + /// + /// The Unique Process Identifier for this task + /// (e.g., "UPID:pve:000ABC:00000001:5F1234AB:qmstart:100:root@pam:"). + /// + [JsonPropertyName("upid")] + [JsonProperty("upid")] + public string Upid { get; set; } = string.Empty; + + /// + /// The task type string (e.g., "qmstart", "qmstop", "qmmigrate", "vzrestore"). + /// + [JsonPropertyName("type")] + [JsonProperty("type")] + public string? Type { get; set; } + + /// + /// The current task status: "running" while in progress, "stopped" when complete. + /// + [JsonPropertyName("status")] + [JsonProperty("status")] + public string? Status { get; set; } + + /// + /// The exit status when the task has stopped (e.g., "OK" on success, or an error message). + /// + [JsonPropertyName("exitstatus")] + [JsonProperty("exitstatus")] + public string? ExitStatus { get; set; } + + /// + /// The node on which this task is executing. + /// + [JsonPropertyName("node")] + [JsonProperty("node")] + public string? Node { get; set; } + + /// + /// Unix timestamp of when the task started. + /// + [JsonPropertyName("starttime")] + [JsonProperty("starttime")] + public long? StartTime { get; set; } + + /// + /// Unix timestamp of when the task ended. Null if still running. + /// + [JsonPropertyName("endtime")] + [JsonProperty("endtime")] + public long? EndTime { get; set; } + + /// + /// The user that initiated the task (e.g., "root@pam"). + /// + [JsonPropertyName("user")] + [JsonProperty("user")] + public string? User { get; set; } + + /// + /// The object ID the task is operating on (e.g., a VM ID or storage name). + /// + [JsonPropertyName("id")] + [JsonProperty("id")] + public string? Id { get; set; } + + /// + /// Returns true when the task completed successfully (stopped with exit status "OK"). + /// + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] + public bool IsSuccessful => Status == "stopped" && ExitStatus == "OK"; + + /// + public override string ToString() + { + var startStr = StartTime.HasValue + ? DateTimeOffset.FromUnixTimeSeconds(StartTime.Value).ToString("yyyy-MM-dd HH:mm:ss") + : "N/A"; + return $"Task [{Type ?? "unknown"}] UPID: {Upid} | Node: {Node ?? "N/A"} | " + + $"Status: {Status ?? "unknown"} | ExitStatus: {ExitStatus ?? "-"} | " + + $"User: {User ?? "N/A"} | Started: {startStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Vms/PveTaskLog.cs b/src/PSProxmoxVE.Core/Models/Vms/PveTaskLog.cs new file mode 100644 index 0000000..4158923 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Vms/PveTaskLog.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Vms +{ + /// + /// Represents a single log line from a Proxmox VE task log, + /// as returned by the /nodes/{node}/tasks/{upid}/log endpoint. + /// + public class PveTaskLog + { + /// Line number (1-based). + [JsonProperty("n")] + public int LineNumber { get; set; } + + /// Log line text. + [JsonProperty("t")] + public string Text { get; set; } = string.Empty; + + public override string ToString() => $"{LineNumber,4}: {Text}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Vms/PveVm.cs b/src/PSProxmoxVE.Core/Models/Vms/PveVm.cs new file mode 100644 index 0000000..88d1f32 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Vms/PveVm.cs @@ -0,0 +1,121 @@ +using System; +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Vms; + +/// +/// Represents a QEMU/KVM virtual machine as returned by the cluster or node VM list endpoints. +/// +public class PveVm +{ + /// + /// The unique VM identifier. + /// + [JsonPropertyName("vmid")] + [JsonProperty("vmid")] + public int VmId { get; set; } + + /// + /// The name of the virtual machine. + /// + [JsonPropertyName("name")] + [JsonProperty("name")] + public string? Name { get; set; } + + /// + /// The current runtime status of the VM (e.g., "running", "stopped"). + /// + [JsonPropertyName("status")] + [JsonProperty("status")] + public string? Status { get; set; } + + /// + /// The node on which the VM resides. + /// + [JsonPropertyName("node")] + [JsonProperty("node")] + public string? Node { get; set; } + + /// + /// The number of virtual CPU cores assigned to the VM. + /// + [JsonPropertyName("cpus")] + [JsonProperty("cpus")] + public int? CpuCount { get; set; } + + /// + /// Maximum memory allocated to the VM, in bytes. + /// + [JsonPropertyName("maxmem")] + [JsonProperty("maxmem")] + public long? MaxMem { get; set; } + + /// + /// Maximum disk size allocated to the VM, in bytes. + /// + [JsonPropertyName("maxdisk")] + [JsonProperty("maxdisk")] + public long? MaxDisk { get; set; } + + /// + /// VM uptime in seconds. + /// + [JsonPropertyName("uptime")] + [JsonProperty("uptime")] + public long? Uptime { get; set; } + + /// + /// Semicolon-separated list of tags assigned to the VM. + /// + [JsonPropertyName("tags")] + [JsonProperty("tags")] + public string? Tags { get; set; } + + /// + /// Indicates whether the VM is a template (1) or a regular VM (0). + /// + [JsonPropertyName("template")] + [JsonProperty("template")] + public int? Template { get; set; } + + /// + /// The current lock type applied to the VM, if any (e.g., "migrate", "backup"). + /// + [JsonPropertyName("lock")] + [JsonProperty("lock")] + public string? Lock { get; set; } + + /// + /// The process ID of the running QEMU process, if applicable. + /// + [JsonPropertyName("pid")] + [JsonProperty("pid")] + public int? Pid { get; set; } + + /// + /// The QMP (QEMU Machine Protocol) status string. + /// + [JsonPropertyName("qmpstatus")] + [JsonProperty("qmpstatus")] + public string? QmpStatus { get; set; } + + /// + /// Indicates whether the QEMU guest agent is running (non-zero) inside the VM. + /// + [JsonPropertyName("agent")] + [JsonProperty("agent")] + public int? AgentStatus { get; set; } + + /// + public override string ToString() + { + var maxMemMb = MaxMem.HasValue ? $"{MaxMem.Value / 1024 / 1024} MB" : "N/A"; + var maxDiskGb = MaxDisk.HasValue ? $"{MaxDisk.Value / 1024 / 1024 / 1024} GB" : "N/A"; + var uptimeStr = Uptime.HasValue ? TimeSpan.FromSeconds(Uptime.Value).ToString(@"d\.hh\:mm\:ss") : "N/A"; + var templateStr = Template is 1 ? " [template]" : string.Empty; + return $"VM {VmId}{templateStr}: {Name ?? "(unnamed)"} | Node: {Node ?? "N/A"} | " + + $"Status: {Status ?? "N/A"} | CPUs: {CpuCount?.ToString() ?? "N/A"} | " + + $"Mem: {maxMemMb} | Disk: {maxDiskGb} | Uptime: {uptimeStr}"; + } +} diff --git a/src/PSProxmoxVE.Core/Models/Vms/PveVmConfig.cs b/src/PSProxmoxVE.Core/Models/Vms/PveVmConfig.cs new file mode 100644 index 0000000..ebf90a5 --- /dev/null +++ b/src/PSProxmoxVE.Core/Models/Vms/PveVmConfig.cs @@ -0,0 +1,353 @@ +using System.Text.Json.Serialization; +using Newtonsoft.Json; + +namespace PSProxmoxVE.Core.Models.Vms; + +/// +/// Represents the configuration of a QEMU/KVM virtual machine, +/// as returned by the /nodes/{node}/qemu/{vmid}/config endpoint. +/// +public class PveVmConfig +{ + // ------------------------------------------------------------------------- + // CPU / Memory + // ------------------------------------------------------------------------- + + /// + /// Number of CPU cores per socket. + /// + [JsonPropertyName("cores")] + [JsonProperty("cores")] + public int? Cores { get; set; } + + /// + /// Number of CPU sockets. + /// + [JsonPropertyName("sockets")] + [JsonProperty("sockets")] + public int? Sockets { get; set; } + + /// + /// Memory size in megabytes. + /// + [JsonPropertyName("memory")] + [JsonProperty("memory")] + public int? Memory { get; set; } + + /// + /// Emulated CPU type (e.g., "host", "x86-64-v2-AES"). + /// + [JsonPropertyName("cpu")] + [JsonProperty("cpu")] + public string? CpuType { get; set; } + + // ------------------------------------------------------------------------- + // Firmware / Machine + // ------------------------------------------------------------------------- + + /// + /// BIOS implementation to use: "seabios" (default) or "ovmf" (UEFI). + /// + [JsonPropertyName("bios")] + [JsonProperty("bios")] + public string? Bios { get; set; } + + /// + /// Emulated machine type (e.g., "q35", "i440fx"). + /// + [JsonPropertyName("machine")] + [JsonProperty("machine")] + public string? Machine { get; set; } + + // ------------------------------------------------------------------------- + // Boot / Args + // ------------------------------------------------------------------------- + + /// + /// Boot order specification string. + /// + [JsonPropertyName("boot")] + [JsonProperty("boot")] + public string? Boot { get; set; } + + /// + /// Arbitrary QEMU command-line arguments appended to the QEMU launch command. + /// + [JsonPropertyName("args")] + [JsonProperty("args")] + public string? Args { get; set; } + + // ------------------------------------------------------------------------- + // Metadata + // ------------------------------------------------------------------------- + + /// + /// Human-readable description or notes for the VM. + /// + [JsonPropertyName("description")] + [JsonProperty("description")] + public string? Description { get; set; } + + /// + /// Semicolon-separated list of tags assigned to the VM. + /// + [JsonPropertyName("tags")] + [JsonProperty("tags")] + public string? Tags { get; set; } + + /// + /// When set to 1, prevents the VM from being deleted or modified accidentally. + /// + [JsonPropertyName("protection")] + [JsonProperty("protection")] + public int? Protection { get; set; } + + /// + /// NUMA topology enabled (1) or disabled (0). + /// + [JsonPropertyName("numa")] + [JsonProperty("numa")] + public int? Numa { get; set; } + + /// + /// VirtIO balloon device target memory in MB. 0 disables ballooning. + /// + [JsonPropertyName("balloon")] + [JsonProperty("balloon")] + public int? Balloon { get; set; } + + /// + /// Guest OS type hint (e.g., "l26" for Linux 2.6+, "win10"). + /// + [JsonPropertyName("ostype")] + [JsonProperty("ostype")] + public string? OsType { get; set; } + + // ------------------------------------------------------------------------- + // VirtIO disk slots (0–3, most commonly used) + // ------------------------------------------------------------------------- + + /// VirtIO disk slot 0 configuration string. + [JsonPropertyName("virtio0")] + [JsonProperty("virtio0")] + public string? Virtio0 { get; set; } + + /// VirtIO disk slot 1 configuration string. + [JsonPropertyName("virtio1")] + [JsonProperty("virtio1")] + public string? Virtio1 { get; set; } + + /// VirtIO disk slot 2 configuration string. + [JsonPropertyName("virtio2")] + [JsonProperty("virtio2")] + public string? Virtio2 { get; set; } + + /// VirtIO disk slot 3 configuration string. + [JsonPropertyName("virtio3")] + [JsonProperty("virtio3")] + public string? Virtio3 { get; set; } + + // ------------------------------------------------------------------------- + // SCSI disk slots (0–7) + // ------------------------------------------------------------------------- + + /// SCSI disk slot 0 configuration string. + [JsonPropertyName("scsi0")] + [JsonProperty("scsi0")] + public string? Scsi0 { get; set; } + + /// SCSI disk slot 1 configuration string. + [JsonPropertyName("scsi1")] + [JsonProperty("scsi1")] + public string? Scsi1 { get; set; } + + /// SCSI disk slot 2 configuration string. + [JsonPropertyName("scsi2")] + [JsonProperty("scsi2")] + public string? Scsi2 { get; set; } + + /// SCSI disk slot 3 configuration string. + [JsonPropertyName("scsi3")] + [JsonProperty("scsi3")] + public string? Scsi3 { get; set; } + + /// SCSI disk slot 4 configuration string. + [JsonPropertyName("scsi4")] + [JsonProperty("scsi4")] + public string? Scsi4 { get; set; } + + /// SCSI disk slot 5 configuration string. + [JsonPropertyName("scsi5")] + [JsonProperty("scsi5")] + public string? Scsi5 { get; set; } + + /// SCSI disk slot 6 configuration string. + [JsonPropertyName("scsi6")] + [JsonProperty("scsi6")] + public string? Scsi6 { get; set; } + + /// SCSI disk slot 7 configuration string. + [JsonPropertyName("scsi7")] + [JsonProperty("scsi7")] + public string? Scsi7 { get; set; } + + // ------------------------------------------------------------------------- + // IDE disk slots (0–3) + // ------------------------------------------------------------------------- + + /// IDE disk/CDROM slot 0 configuration string. + [JsonPropertyName("ide0")] + [JsonProperty("ide0")] + public string? Ide0 { get; set; } + + /// IDE disk/CDROM slot 1 configuration string. + [JsonPropertyName("ide1")] + [JsonProperty("ide1")] + public string? Ide1 { get; set; } + + /// IDE disk/CDROM slot 2 configuration string. + [JsonPropertyName("ide2")] + [JsonProperty("ide2")] + public string? Ide2 { get; set; } + + /// IDE disk/CDROM slot 3 configuration string. + [JsonPropertyName("ide3")] + [JsonProperty("ide3")] + public string? Ide3 { get; set; } + + // ------------------------------------------------------------------------- + // SATA disk slots (0–5) + // ------------------------------------------------------------------------- + + /// SATA disk slot 0 configuration string. + [JsonPropertyName("sata0")] + [JsonProperty("sata0")] + public string? Sata0 { get; set; } + + /// SATA disk slot 1 configuration string. + [JsonPropertyName("sata1")] + [JsonProperty("sata1")] + public string? Sata1 { get; set; } + + /// SATA disk slot 2 configuration string. + [JsonPropertyName("sata2")] + [JsonProperty("sata2")] + public string? Sata2 { get; set; } + + /// SATA disk slot 3 configuration string. + [JsonPropertyName("sata3")] + [JsonProperty("sata3")] + public string? Sata3 { get; set; } + + /// SATA disk slot 4 configuration string. + [JsonPropertyName("sata4")] + [JsonProperty("sata4")] + public string? Sata4 { get; set; } + + /// SATA disk slot 5 configuration string. + [JsonPropertyName("sata5")] + [JsonProperty("sata5")] + public string? Sata5 { get; set; } + + // ------------------------------------------------------------------------- + // Network interface slots (0–7) + // ------------------------------------------------------------------------- + + /// Network interface 0 configuration string (e.g., "virtio=XX:XX:XX:XX:XX:XX,bridge=vmbr0"). + [JsonPropertyName("net0")] + [JsonProperty("net0")] + public string? Net0 { get; set; } + + /// Network interface 1 configuration string. + [JsonPropertyName("net1")] + [JsonProperty("net1")] + public string? Net1 { get; set; } + + /// Network interface 2 configuration string. + [JsonPropertyName("net2")] + [JsonProperty("net2")] + public string? Net2 { get; set; } + + /// Network interface 3 configuration string. + [JsonPropertyName("net3")] + [JsonProperty("net3")] + public string? Net3 { get; set; } + + /// Network interface 4 configuration string. + [JsonPropertyName("net4")] + [JsonProperty("net4")] + public string? Net4 { get; set; } + + /// Network interface 5 configuration string. + [JsonPropertyName("net5")] + [JsonProperty("net5")] + public string? Net5 { get; set; } + + /// Network interface 6 configuration string. + [JsonPropertyName("net6")] + [JsonProperty("net6")] + public string? Net6 { get; set; } + + /// Network interface 7 configuration string. + [JsonPropertyName("net7")] + [JsonProperty("net7")] + public string? Net7 { get; set; } + + // ------------------------------------------------------------------------- + // Cloud-Init + // ------------------------------------------------------------------------- + + /// Cloud-Init default user name. + [JsonPropertyName("ciuser")] + [JsonProperty("ciuser")] + public string? CiUser { get; set; } + + /// Cloud-Init default user password (hashed or plaintext depending on PVE version). + [JsonPropertyName("cipassword")] + [JsonProperty("cipassword")] + public string? CiPassword { get; set; } + + /// URL-encoded SSH public keys injected by Cloud-Init. + [JsonPropertyName("sshkeys")] + [JsonProperty("sshkeys")] + public string? SshKeys { get; set; } + + /// Cloud-Init IP configuration for interface 0. + [JsonPropertyName("ipconfig0")] + [JsonProperty("ipconfig0")] + public string? IpConfig0 { get; set; } + + /// Cloud-Init IP configuration for interface 1. + [JsonPropertyName("ipconfig1")] + [JsonProperty("ipconfig1")] + public string? IpConfig1 { get; set; } + + /// Cloud-Init IP configuration for interface 2. + [JsonPropertyName("ipconfig2")] + [JsonProperty("ipconfig2")] + public string? IpConfig2 { get; set; } + + /// Cloud-Init IP configuration for interface 3. + [JsonPropertyName("ipconfig3")] + [JsonProperty("ipconfig3")] + public string? IpConfig3 { get; set; } + + /// DNS nameserver(s) injected via Cloud-Init. + [JsonPropertyName("nameserver")] + [JsonProperty("nameserver")] + public string? Nameserver { get; set; } + + /// DNS search domain injected via Cloud-Init. + [JsonPropertyName("searchdomain")] + [JsonProperty("searchdomain")] + public string? Searchdomain { get; set; } + + /// + public override string ToString() + { + var totalCores = (Cores ?? 1) * (Sockets ?? 1); + return $"Config | CPUs: {totalCores} ({Sockets ?? 1}S x {Cores ?? 1}C) | " + + $"Memory: {Memory?.ToString() ?? "N/A"} MB | BIOS: {Bios ?? "seabios"} | " + + $"Machine: {Machine ?? "default"} | OS: {OsType ?? "N/A"}"; + } +}