Pulse/internal/resources/converters.go
2025-12-12 21:31:11 +00:00

1085 lines
27 KiB
Go

package resources
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
)
// FromNode converts a Proxmox Node to a unified Resource.
func FromNode(n models.Node) Resource {
// Calculate primary temperature if available
var temp *float64
if n.Temperature != nil && n.Temperature.Available && n.Temperature.HasCPU {
// Use CPU package temperature if available, otherwise average core temps
if n.Temperature.CPUPackage > 0 {
temp = &n.Temperature.CPUPackage
} else if len(n.Temperature.Cores) > 0 {
avg := 0.0
for _, c := range n.Temperature.Cores {
avg += c.Temp
}
avg /= float64(len(n.Temperature.Cores))
temp = &avg
}
}
// Build memory metric
var memory *MetricValue
if n.Memory.Total > 0 {
memory = &MetricValue{
Current: n.Memory.Usage,
Total: &n.Memory.Total,
Used: &n.Memory.Used,
Free: &n.Memory.Free,
}
}
// Build disk metric
var disk *MetricValue
if n.Disk.Total > 0 {
disk = &MetricValue{
Current: n.Disk.Usage,
Total: &n.Disk.Total,
Used: &n.Disk.Used,
Free: &n.Disk.Free,
}
}
// Build platform data
platformData := NodePlatformData{
Instance: n.Instance,
Host: n.Host,
GuestURL: n.GuestURL,
PVEVersion: n.PVEVersion,
KernelVersion: n.KernelVersion,
LoadAverage: n.LoadAverage,
IsClusterMember: n.IsClusterMember,
ClusterName: n.ClusterName,
ConnectionHealth: n.ConnectionHealth,
CPUInfo: CPUInfo{
Model: n.CPUInfo.Model,
Cores: n.CPUInfo.Cores,
Sockets: n.CPUInfo.Sockets,
},
}
platformDataJSON, _ := json.Marshal(platformData)
// Determine cluster ID
clusterID := ""
if n.IsClusterMember && n.ClusterName != "" {
clusterID = fmt.Sprintf("pve-cluster/%s", n.ClusterName)
}
return Resource{
ID: n.ID,
Type: ResourceTypeNode,
Name: n.Name,
DisplayName: n.DisplayName,
PlatformID: n.Instance,
PlatformType: PlatformProxmoxPVE,
SourceType: SourceAPI,
ClusterID: clusterID,
Status: mapNodeStatus(n.Status),
CPU: &MetricValue{
Current: n.CPU * 100, // Node CPU is 0-1, convert to percentage
},
Memory: memory,
Disk: disk,
Temperature: temp,
Uptime: &n.Uptime,
LastSeen: n.LastSeen,
PlatformData: platformDataJSON,
Identity: &ResourceIdentity{
Hostname: n.Name,
},
SchemaVersion: CurrentSchemaVersion,
}
}
// FromVM converts a Proxmox VM to a unified Resource.
func FromVM(vm models.VM) Resource {
// Build memory metric
var memory *MetricValue
if vm.Memory.Total > 0 {
memory = &MetricValue{
Current: vm.Memory.Usage,
Total: &vm.Memory.Total,
Used: &vm.Memory.Used,
Free: &vm.Memory.Free,
}
}
// Build disk metric
var disk *MetricValue
if vm.Disk.Total > 0 {
disk = &MetricValue{
Current: vm.Disk.Usage,
Total: &vm.Disk.Total,
Used: &vm.Disk.Used,
Free: &vm.Disk.Free,
}
}
// Build network metric
network := &NetworkMetric{
RXBytes: vm.NetworkIn,
TXBytes: vm.NetworkOut,
}
// Build platform data
var lastBackup *time.Time
if !vm.LastBackup.IsZero() {
lastBackup = &vm.LastBackup
}
platformData := VMPlatformData{
VMID: vm.VMID,
Node: vm.Node,
Instance: vm.Instance,
CPUs: vm.CPUs,
Template: vm.Template,
Lock: vm.Lock,
AgentVersion: vm.AgentVersion,
OSName: vm.OSName,
OSVersion: vm.OSVersion,
IPAddresses: vm.IPAddresses,
NetworkIn: vm.NetworkIn,
NetworkOut: vm.NetworkOut,
DiskRead: vm.DiskRead,
DiskWrite: vm.DiskWrite,
LastBackup: lastBackup,
}
platformDataJSON, _ := json.Marshal(platformData)
// Parent is the node - format matches Node.ID: instance-nodename
parentID := fmt.Sprintf("%s-%s", vm.Instance, vm.Node)
return Resource{
ID: vm.ID,
Type: ResourceTypeVM,
Name: vm.Name,
PlatformID: vm.Instance,
PlatformType: PlatformProxmoxPVE,
SourceType: SourceAPI,
ParentID: parentID,
Status: mapGuestStatus(vm.Status),
CPU: &MetricValue{
Current: vm.CPU * 100, // VM CPU is 0-1, convert to percentage
},
Memory: memory,
Disk: disk,
Network: network,
Uptime: &vm.Uptime,
Tags: vm.Tags,
LastSeen: vm.LastSeen,
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromContainer converts a Proxmox LXC Container to a unified Resource.
func FromContainer(ct models.Container) Resource {
// Build memory metric
var memory *MetricValue
if ct.Memory.Total > 0 {
memory = &MetricValue{
Current: ct.Memory.Usage,
Total: &ct.Memory.Total,
Used: &ct.Memory.Used,
Free: &ct.Memory.Free,
}
}
// Build disk metric
var disk *MetricValue
if ct.Disk.Total > 0 {
disk = &MetricValue{
Current: ct.Disk.Usage,
Total: &ct.Disk.Total,
Used: &ct.Disk.Used,
Free: &ct.Disk.Free,
}
}
// Build network metric
network := &NetworkMetric{
RXBytes: ct.NetworkIn,
TXBytes: ct.NetworkOut,
}
// Build platform data
var lastBackup *time.Time
if !ct.LastBackup.IsZero() {
lastBackup = &ct.LastBackup
}
platformData := ContainerPlatformData{
VMID: ct.VMID,
Node: ct.Node,
Instance: ct.Instance,
Type: ct.Type,
CPUs: ct.CPUs,
Template: ct.Template,
Lock: ct.Lock,
OSName: ct.OSName,
IPAddresses: ct.IPAddresses,
IsOCI: ct.IsOCI || strings.EqualFold(strings.TrimSpace(ct.Type), "oci"),
OSTemplate: ct.OSTemplate,
NetworkIn: ct.NetworkIn,
NetworkOut: ct.NetworkOut,
DiskRead: ct.DiskRead,
DiskWrite: ct.DiskWrite,
LastBackup: lastBackup,
}
platformDataJSON, _ := json.Marshal(platformData)
// Parent is the node - format matches Node.ID: instance-nodename
parentID := fmt.Sprintf("%s-%s", ct.Instance, ct.Node)
resourceType := ResourceTypeContainer
if platformData.IsOCI {
resourceType = ResourceTypeOCIContainer
}
return Resource{
ID: ct.ID,
Type: resourceType,
Name: ct.Name,
PlatformID: ct.Instance,
PlatformType: PlatformProxmoxPVE,
SourceType: SourceAPI,
ParentID: parentID,
Status: mapGuestStatus(ct.Status),
CPU: &MetricValue{
Current: ct.CPU * 100, // Container CPU is 0-1, convert to percentage
},
Memory: memory,
Disk: disk,
Network: network,
Uptime: &ct.Uptime,
Tags: ct.Tags,
LastSeen: ct.LastSeen,
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromHost converts a Host agent to a unified Resource.
func FromHost(h models.Host) Resource {
// Build memory metric
var memory *MetricValue
if h.Memory.Total > 0 {
memory = &MetricValue{
Current: h.Memory.Usage,
Total: &h.Memory.Total,
Used: &h.Memory.Used,
Free: &h.Memory.Free,
}
}
// Combine disk metrics from multiple disks
var disk *MetricValue
var totalDisk, usedDisk, freeDisk int64
for _, d := range h.Disks {
totalDisk += d.Total
usedDisk += d.Used
freeDisk += d.Free
}
if totalDisk > 0 {
usage := float64(usedDisk) / float64(totalDisk) * 100
disk = &MetricValue{
Current: usage,
Total: &totalDisk,
Used: &usedDisk,
Free: &freeDisk,
}
}
// Calculate network totals
var rxTotal, txTotal int64
for _, iface := range h.NetworkInterfaces {
rxTotal += int64(iface.RXBytes)
txTotal += int64(iface.TXBytes)
}
network := &NetworkMetric{
RXBytes: rxTotal,
TXBytes: txTotal,
}
// Get primary temperature
var temp *float64
if len(h.Sensors.TemperatureCelsius) > 0 {
// Pick the first available temperature
for _, t := range h.Sensors.TemperatureCelsius {
temp = &t
break
}
}
// Build platform data
disks := make([]DiskInfo, len(h.Disks))
for i, d := range h.Disks {
disks[i] = DiskInfo{
Mountpoint: d.Mountpoint,
Device: d.Device,
Type: d.Type,
Total: d.Total,
Used: d.Used,
Free: d.Free,
Usage: d.Usage,
}
}
interfaces := make([]NetworkInterface, len(h.NetworkInterfaces))
for i, iface := range h.NetworkInterfaces {
interfaces[i] = NetworkInterface{
Name: iface.Name,
MAC: iface.MAC,
Addresses: iface.Addresses,
RXBytes: iface.RXBytes,
TXBytes: iface.TXBytes,
SpeedMbps: iface.SpeedMbps,
}
}
diskIO := make([]DiskIOStats, len(h.DiskIO))
for i, d := range h.DiskIO {
diskIO[i] = DiskIOStats{
Device: d.Device,
ReadBytes: d.ReadBytes,
WriteBytes: d.WriteBytes,
ReadOps: d.ReadOps,
WriteOps: d.WriteOps,
ReadTimeMs: d.ReadTime,
WriteTimeMs: d.WriteTime,
IOTimeMs: d.IOTime,
}
}
raid := make([]HostRAIDArray, len(h.RAID))
for i, r := range h.RAID {
devices := make([]HostRAIDDevice, len(r.Devices))
for j, d := range r.Devices {
devices[j] = HostRAIDDevice{
Device: d.Device,
State: d.State,
Slot: d.Slot,
}
}
raid[i] = HostRAIDArray{
Device: r.Device,
Name: r.Name,
Level: r.Level,
State: r.State,
TotalDevices: r.TotalDevices,
ActiveDevices: r.ActiveDevices,
WorkingDevices: r.WorkingDevices,
FailedDevices: r.FailedDevices,
SpareDevices: r.SpareDevices,
UUID: r.UUID,
Devices: devices,
RebuildPercent: r.RebuildPercent,
RebuildSpeed: r.RebuildSpeed,
}
}
platformData := HostPlatformData{
Platform: h.Platform,
OSName: h.OSName,
OSVersion: h.OSVersion,
KernelVersion: h.KernelVersion,
Architecture: h.Architecture,
CPUCount: h.CPUCount,
LoadAverage: h.LoadAverage,
AgentVersion: h.AgentVersion,
IsLegacy: h.IsLegacy,
Disks: disks,
Interfaces: interfaces,
DiskIO: diskIO,
RAID: raid,
TokenID: h.TokenID,
TokenName: h.TokenName,
TokenHint: h.TokenHint,
TokenLastUsedAt: h.TokenLastUsedAt,
Sensors: HostSensorSummary{
TemperatureCelsius: h.Sensors.TemperatureCelsius,
FanRPM: h.Sensors.FanRPM,
Additional: h.Sensors.Additional,
},
}
platformDataJSON, _ := json.Marshal(platformData)
// Collect IPs for identity
var ips []string
for _, iface := range h.NetworkInterfaces {
ips = append(ips, iface.Addresses...)
}
return Resource{
ID: h.ID,
Type: ResourceTypeHost,
Name: h.Hostname,
DisplayName: h.DisplayName,
PlatformID: "host-agent", // No specific platform ID for host agents
PlatformType: PlatformHostAgent,
SourceType: SourceAgent,
Status: mapHostStatus(h.Status),
CPU: &MetricValue{
Current: h.CPUUsage,
},
Memory: memory,
Disk: disk,
Network: network,
Temperature: temp,
Uptime: &h.UptimeSeconds,
Tags: h.Tags,
LastSeen: h.LastSeen,
PlatformData: platformDataJSON,
Identity: &ResourceIdentity{
Hostname: h.Hostname,
IPs: ips,
},
SchemaVersion: CurrentSchemaVersion,
}
}
// FromDockerHost converts a DockerHost to a unified Resource.
func FromDockerHost(dh models.DockerHost) Resource {
// Build memory metric
var memory *MetricValue
if dh.Memory.Total > 0 {
memory = &MetricValue{
Current: dh.Memory.Usage,
Total: &dh.Memory.Total,
Used: &dh.Memory.Used,
Free: &dh.Memory.Free,
}
}
// Combine disk metrics
var disk *MetricValue
var totalDisk, usedDisk, freeDisk int64
for _, d := range dh.Disks {
totalDisk += d.Total
usedDisk += d.Used
freeDisk += d.Free
}
if totalDisk > 0 {
usage := float64(usedDisk) / float64(totalDisk) * 100
disk = &MetricValue{
Current: usage,
Total: &totalDisk,
Used: &usedDisk,
Free: &freeDisk,
}
}
// Calculate network totals
var rxTotal, txTotal int64
for _, iface := range dh.NetworkInterfaces {
rxTotal += int64(iface.RXBytes)
txTotal += int64(iface.TXBytes)
}
network := &NetworkMetric{
RXBytes: rxTotal,
TXBytes: txTotal,
}
// Build platform data
disks := make([]DiskInfo, len(dh.Disks))
for i, d := range dh.Disks {
disks[i] = DiskInfo{
Mountpoint: d.Mountpoint,
Device: d.Device,
Type: d.Type,
Total: d.Total,
Used: d.Used,
Free: d.Free,
Usage: d.Usage,
}
}
interfaces := make([]NetworkInterface, len(dh.NetworkInterfaces))
for i, iface := range dh.NetworkInterfaces {
interfaces[i] = NetworkInterface{
Name: iface.Name,
MAC: iface.MAC,
Addresses: iface.Addresses,
RXBytes: iface.RXBytes,
TXBytes: iface.TXBytes,
SpeedMbps: iface.SpeedMbps,
}
}
var swarm *DockerSwarmInfo
if dh.Swarm != nil {
swarm = &DockerSwarmInfo{
NodeID: dh.Swarm.NodeID,
NodeRole: dh.Swarm.NodeRole,
LocalState: dh.Swarm.LocalState,
ControlAvailable: dh.Swarm.ControlAvailable,
ClusterID: dh.Swarm.ClusterID,
ClusterName: dh.Swarm.ClusterName,
Scope: dh.Swarm.Scope,
Error: dh.Swarm.Error,
}
}
platformData := DockerHostPlatformData{
AgentID: dh.AgentID,
MachineID: dh.MachineID,
OS: dh.OS,
KernelVersion: dh.KernelVersion,
Architecture: dh.Architecture,
Runtime: dh.Runtime,
RuntimeVersion: dh.RuntimeVersion,
DockerVersion: dh.DockerVersion,
LoadAverage: dh.LoadAverage,
AgentVersion: dh.AgentVersion,
CPUs: dh.CPUs,
IsLegacy: dh.IsLegacy,
Disks: disks,
Interfaces: interfaces,
CustomDisplayName: dh.CustomDisplayName,
Hidden: dh.Hidden,
PendingUninstall: dh.PendingUninstall,
Swarm: swarm,
TokenID: dh.TokenID,
TokenName: dh.TokenName,
TokenHint: dh.TokenHint,
TokenLastUsedAt: dh.TokenLastUsedAt,
}
platformDataJSON, _ := json.Marshal(platformData)
// Collect IPs for identity
var ips []string
for _, iface := range dh.NetworkInterfaces {
ips = append(ips, iface.Addresses...)
}
// Determine display name
displayName := dh.DisplayName
if dh.CustomDisplayName != "" {
displayName = dh.CustomDisplayName
}
// Determine cluster ID from swarm
clusterID := ""
if dh.Swarm != nil && dh.Swarm.ClusterID != "" {
clusterID = fmt.Sprintf("docker-swarm/%s", dh.Swarm.ClusterID)
}
return Resource{
ID: dh.ID,
Type: ResourceTypeDockerHost,
Name: dh.Hostname,
DisplayName: displayName,
PlatformID: dh.AgentID,
PlatformType: PlatformDocker,
SourceType: SourceAgent,
ClusterID: clusterID,
Status: mapDockerHostStatus(dh.Status),
CPU: &MetricValue{
Current: dh.CPUUsage,
},
Memory: memory,
Disk: disk,
Network: network,
Uptime: &dh.UptimeSeconds,
LastSeen: dh.LastSeen,
PlatformData: platformDataJSON,
Identity: &ResourceIdentity{
Hostname: dh.Hostname,
MachineID: dh.MachineID,
IPs: ips,
},
SchemaVersion: CurrentSchemaVersion,
}
}
// FromDockerContainer converts a DockerContainer to a unified Resource.
func FromDockerContainer(dc models.DockerContainer, hostID, hostName string) Resource {
// Build memory metric
memTotal := dc.MemoryLimit
memUsed := dc.MemoryUsage
var memory *MetricValue
if memTotal > 0 {
memory = &MetricValue{
Current: dc.MemoryPercent,
Total: &memTotal,
Used: &memUsed,
}
}
// Build platform data
ports := make([]ContainerPort, len(dc.Ports))
for i, p := range dc.Ports {
ports[i] = ContainerPort{
PrivatePort: p.PrivatePort,
PublicPort: p.PublicPort,
Protocol: p.Protocol,
IP: p.IP,
}
}
networks := make([]ContainerNetwork, len(dc.Networks))
for i, n := range dc.Networks {
networks[i] = ContainerNetwork{
Name: n.Name,
IPv4: n.IPv4,
IPv6: n.IPv6,
}
}
var podman *PodmanContainerInfo
if dc.Podman != nil {
podman = &PodmanContainerInfo{
PodName: dc.Podman.PodName,
PodID: dc.Podman.PodID,
Infra: dc.Podman.Infra,
ComposeProject: dc.Podman.ComposeProject,
ComposeService: dc.Podman.ComposeService,
}
}
platformData := DockerContainerPlatformData{
HostID: hostID,
HostName: hostName,
Image: dc.Image,
State: dc.State,
Status: dc.Status,
Health: dc.Health,
RestartCount: dc.RestartCount,
ExitCode: dc.ExitCode,
CreatedAt: dc.CreatedAt,
StartedAt: dc.StartedAt,
FinishedAt: dc.FinishedAt,
Labels: dc.Labels,
Ports: ports,
Networks: networks,
Podman: podman,
}
platformDataJSON, _ := json.Marshal(platformData)
// Create unique ID combining host and container
resourceID := fmt.Sprintf("%s/%s", hostID, dc.ID)
return Resource{
ID: resourceID,
Type: ResourceTypeDockerContainer,
Name: dc.Name,
PlatformID: hostID,
PlatformType: PlatformDocker,
SourceType: SourceAgent,
ParentID: hostID,
Status: mapDockerContainerStatus(dc.State),
CPU: &MetricValue{
Current: dc.CPUPercent,
},
Memory: memory,
Uptime: &dc.UptimeSeconds,
Labels: dc.Labels,
LastSeen: time.Now(), // Containers don't have their own LastSeen
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromKubernetesCluster converts a KubernetesCluster to a unified Resource.
func FromKubernetesCluster(cluster models.KubernetesCluster) Resource {
platformData := KubernetesClusterPlatformData{
AgentID: cluster.AgentID,
Server: cluster.Server,
Context: cluster.Context,
Version: cluster.Version,
CustomDisplayName: cluster.CustomDisplayName,
Hidden: cluster.Hidden,
PendingUninstall: cluster.PendingUninstall,
NodeCount: len(cluster.Nodes),
PodCount: len(cluster.Pods),
DeploymentCount: len(cluster.Deployments),
TokenID: cluster.TokenID,
TokenName: cluster.TokenName,
TokenHint: cluster.TokenHint,
TokenLastUsedAt: cluster.TokenLastUsedAt,
}
platformDataJSON, _ := json.Marshal(platformData)
displayName := strings.TrimSpace(cluster.DisplayName)
if displayName == "" {
displayName = strings.TrimSpace(cluster.Name)
}
if cluster.CustomDisplayName != "" {
displayName = cluster.CustomDisplayName
}
if displayName == "" {
displayName = cluster.ID
}
name := strings.TrimSpace(cluster.Name)
if name == "" {
name = displayName
}
return Resource{
ID: cluster.ID,
Type: ResourceTypeK8sCluster,
Name: name,
DisplayName: displayName,
PlatformID: cluster.AgentID,
PlatformType: PlatformKubernetes,
SourceType: SourceAgent,
Status: mapKubernetesClusterStatus(cluster.Status),
LastSeen: cluster.LastSeen,
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromKubernetesNode converts a KubernetesNode to a unified Resource.
func FromKubernetesNode(node models.KubernetesNode, cluster models.KubernetesCluster) Resource {
platformData := KubernetesNodePlatformData{
ClusterID: cluster.ID,
Ready: node.Ready,
Unschedulable: node.Unschedulable,
KubeletVersion: node.KubeletVersion,
ContainerRuntimeVersion: node.ContainerRuntimeVersion,
OSImage: node.OSImage,
KernelVersion: node.KernelVersion,
Architecture: node.Architecture,
CapacityCPUCores: node.CapacityCPU,
CapacityMemoryBytes: node.CapacityMemoryBytes,
CapacityPods: node.CapacityPods,
AllocatableCPUCores: node.AllocCPU,
AllocatableMemoryBytes: node.AllocMemoryBytes,
AllocatablePods: node.AllocPods,
Roles: append([]string(nil), node.Roles...),
}
platformDataJSON, _ := json.Marshal(platformData)
nodeID := strings.TrimSpace(node.UID)
if nodeID == "" {
nodeID = node.Name
}
resourceID := fmt.Sprintf("%s/node/%s", cluster.ID, nodeID)
status := StatusUnknown
if node.Ready {
status = StatusOnline
} else {
status = StatusOffline
}
if node.Unschedulable && status == StatusOnline {
status = StatusDegraded
}
return Resource{
ID: resourceID,
Type: ResourceTypeK8sNode,
Name: node.Name,
PlatformID: cluster.ID,
PlatformType: PlatformKubernetes,
SourceType: SourceAgent,
ParentID: cluster.ID,
Status: status,
LastSeen: cluster.LastSeen,
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromKubernetesPod converts a KubernetesPod to a unified Resource.
func FromKubernetesPod(pod models.KubernetesPod, cluster models.KubernetesCluster) Resource {
containers := make([]KubernetesPodContainerInfo, 0, len(pod.Containers))
for _, c := range pod.Containers {
containers = append(containers, KubernetesPodContainerInfo{
Name: c.Name,
Image: c.Image,
Ready: c.Ready,
RestartCount: c.RestartCount,
State: c.State,
Reason: c.Reason,
Message: c.Message,
})
}
platformData := KubernetesPodPlatformData{
ClusterID: cluster.ID,
Namespace: pod.Namespace,
NodeName: pod.NodeName,
Phase: pod.Phase,
Reason: pod.Reason,
Message: pod.Message,
QoSClass: pod.QoSClass,
Restarts: pod.Restarts,
OwnerKind: pod.OwnerKind,
OwnerName: pod.OwnerName,
Containers: containers,
}
platformDataJSON, _ := json.Marshal(platformData)
podID := strings.TrimSpace(pod.UID)
if podID == "" {
podID = fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
}
resourceID := fmt.Sprintf("%s/pod/%s", cluster.ID, podID)
parentID := cluster.ID
if strings.TrimSpace(pod.NodeName) != "" {
parentID = fmt.Sprintf("%s/node/%s", cluster.ID, pod.NodeName)
}
lastSeen := cluster.LastSeen
return Resource{
ID: resourceID,
Type: ResourceTypePod,
Name: pod.Name,
PlatformID: cluster.ID,
PlatformType: PlatformKubernetes,
SourceType: SourceAgent,
ParentID: parentID,
Status: mapKubernetesPodStatus(pod.Phase),
Labels: pod.Labels,
LastSeen: lastSeen,
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromKubernetesDeployment converts a KubernetesDeployment to a unified Resource.
func FromKubernetesDeployment(dep models.KubernetesDeployment, cluster models.KubernetesCluster) Resource {
platformData := KubernetesDeploymentPlatformData{
ClusterID: cluster.ID,
Namespace: dep.Namespace,
DesiredReplicas: dep.DesiredReplicas,
UpdatedReplicas: dep.UpdatedReplicas,
ReadyReplicas: dep.ReadyReplicas,
AvailableReplicas: dep.AvailableReplicas,
}
platformDataJSON, _ := json.Marshal(platformData)
depID := strings.TrimSpace(dep.UID)
if depID == "" {
depID = fmt.Sprintf("%s/%s", dep.Namespace, dep.Name)
}
resourceID := fmt.Sprintf("%s/deployment/%s", cluster.ID, depID)
status := StatusUnknown
switch {
case dep.DesiredReplicas == 0:
status = StatusStopped
case dep.AvailableReplicas >= dep.DesiredReplicas:
status = StatusRunning
case dep.AvailableReplicas > 0:
status = StatusDegraded
}
return Resource{
ID: resourceID,
Type: ResourceTypeK8sDeployment,
Name: dep.Name,
PlatformID: cluster.ID,
PlatformType: PlatformKubernetes,
SourceType: SourceAgent,
ParentID: cluster.ID,
Status: status,
Labels: dep.Labels,
LastSeen: cluster.LastSeen,
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromPBSInstance converts a PBS instance to a unified Resource.
func FromPBSInstance(pbs models.PBSInstance) Resource {
// Build memory metric
var memory *MetricValue
if pbs.MemoryTotal > 0 {
memory = &MetricValue{
Current: pbs.Memory,
Total: &pbs.MemoryTotal,
Used: &pbs.MemoryUsed,
}
}
platformData := PBSPlatformData{
Host: pbs.Host,
Version: pbs.Version,
ConnectionHealth: pbs.ConnectionHealth,
MemoryUsed: pbs.MemoryUsed,
MemoryTotal: pbs.MemoryTotal,
NumDatastores: len(pbs.Datastores),
}
platformDataJSON, _ := json.Marshal(platformData)
return Resource{
ID: pbs.ID,
Type: ResourceTypePBS,
Name: pbs.Name,
PlatformID: pbs.Host,
PlatformType: PlatformProxmoxPBS,
SourceType: SourceAPI,
Status: mapPBSStatus(pbs.Status, pbs.ConnectionHealth),
CPU: &MetricValue{
Current: pbs.CPU,
},
Memory: memory,
Uptime: &pbs.Uptime,
LastSeen: pbs.LastSeen,
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// FromStorage converts a Proxmox Storage to a unified Resource.
func FromStorage(s models.Storage) Resource {
var disk *MetricValue
if s.Total > 0 {
disk = &MetricValue{
Current: s.Usage,
Total: &s.Total,
Used: &s.Used,
Free: &s.Free,
}
}
platformData := StoragePlatformData{
Instance: s.Instance,
Node: s.Node,
Nodes: s.Nodes,
Type: s.Type,
Content: s.Content,
Shared: s.Shared,
Enabled: s.Enabled,
Active: s.Active,
}
platformDataJSON, _ := json.Marshal(platformData)
status := StatusOnline
if !s.Active {
status = StatusOffline
} else if !s.Enabled {
status = StatusStopped
}
return Resource{
ID: s.ID,
Type: ResourceTypeStorage,
Name: s.Name,
PlatformID: s.Instance,
PlatformType: PlatformProxmoxPVE,
SourceType: SourceAPI,
ParentID: fmt.Sprintf("%s-%s", s.Instance, s.Node), // Format matches Node.ID: instance-nodename
Status: status,
Disk: disk,
LastSeen: time.Now(), // Storage doesn't have LastSeen
PlatformData: platformDataJSON,
SchemaVersion: CurrentSchemaVersion,
}
}
// Status mapping helpers
func mapNodeStatus(status string) ResourceStatus {
switch strings.ToLower(status) {
case "online":
return StatusOnline
case "offline":
return StatusOffline
default:
return StatusUnknown
}
}
func mapGuestStatus(status string) ResourceStatus {
switch strings.ToLower(status) {
case "running":
return StatusRunning
case "stopped":
return StatusStopped
case "paused":
return StatusPaused
default:
return StatusUnknown
}
}
func mapHostStatus(status string) ResourceStatus {
switch strings.ToLower(status) {
case "online":
return StatusOnline
case "offline":
return StatusOffline
case "degraded":
return StatusDegraded
default:
return StatusUnknown
}
}
func mapDockerHostStatus(status string) ResourceStatus {
switch strings.ToLower(status) {
case "online":
return StatusOnline
case "offline":
return StatusOffline
default:
return StatusUnknown
}
}
func mapDockerContainerStatus(state string) ResourceStatus {
switch strings.ToLower(state) {
case "running":
return StatusRunning
case "exited", "dead":
return StatusStopped
case "paused":
return StatusPaused
case "restarting", "created":
return StatusUnknown
default:
return StatusUnknown
}
}
func mapKubernetesClusterStatus(status string) ResourceStatus {
switch strings.ToLower(strings.TrimSpace(status)) {
case "online":
return StatusOnline
case "offline":
return StatusOffline
default:
return StatusUnknown
}
}
func mapKubernetesPodStatus(phase string) ResourceStatus {
switch strings.ToLower(strings.TrimSpace(phase)) {
case "running":
return StatusRunning
case "succeeded", "failed":
return StatusStopped
case "pending":
return StatusUnknown
default:
return StatusUnknown
}
}
func mapPBSStatus(status, connectionHealth string) ResourceStatus {
if connectionHealth != "healthy" {
return StatusDegraded
}
switch strings.ToLower(status) {
case "online":
return StatusOnline
case "offline":
return StatusOffline
default:
return StatusUnknown
}
}