Pulse/internal/models/converters.go
2026-06-30 09:43:33 +01:00

1273 lines
36 KiB
Go

package models
import (
"encoding/json"
"strings"
"time"
)
// ToFrontend converts a State to StateFrontend
func (s *State) ToFrontend() StateFrontend {
return s.GetSnapshot().ToFrontend()
}
// ToFrontend converts a Node to NodeFrontend
func (n Node) ToFrontend() NodeFrontend {
nf := NodeFrontend{
ID: n.ID,
Node: n.Name,
Name: n.Name,
DisplayName: n.DisplayName,
Instance: n.Instance,
Host: n.Host,
GuestURL: n.GuestURL,
Status: n.Status,
Type: n.Type,
CPU: n.CPU,
Mem: n.Memory.Used,
MaxMem: n.Memory.Total,
MaxDisk: n.Disk.Total,
Uptime: n.Uptime,
LoadAverage: n.LoadAverage,
KernelVersion: n.KernelVersion,
PVEVersion: n.PVEVersion,
CPUInfo: n.CPUInfo,
LastSeen: timeToUnixMillis(n.LastSeen),
ConnectionHealth: n.ConnectionHealth,
IsClusterMember: n.IsClusterMember,
ClusterName: n.ClusterName,
TemperatureMonitoringEnabled: n.TemperatureMonitoringEnabled,
LinkedAgentID: n.LinkedAgentID,
}
// Include full Memory object if it has data
if n.Memory.Total > 0 {
nf.Memory = &n.Memory
}
// Include full Disk object if it has data
if n.Disk.Total > 0 {
nf.Disk = &n.Disk
}
// Include temperature data if available
if n.Temperature != nil && n.Temperature.Available {
nf.Temperature = n.Temperature
}
if nf.DisplayName == "" {
nf.DisplayName = nf.Name
}
return nf.NormalizeCollections()
}
// ToFrontend converts a VM to VMFrontend
func (v VM) ToFrontend() VMFrontend {
vm := VMFrontend{
ID: v.ID,
VMID: v.VMID,
Name: v.Name,
Node: v.Node,
Pool: v.Pool,
Instance: v.Instance,
Status: v.Status,
Type: v.Type,
CPU: v.CPU,
CPUs: v.CPUs,
Mem: v.Memory.Used,
MaxMem: v.Memory.Total,
NetIn: zeroIfNegative(v.NetworkIn),
NetOut: zeroIfNegative(v.NetworkOut),
DiskRead: zeroIfNegative(v.DiskRead),
DiskWrite: zeroIfNegative(v.DiskWrite),
Uptime: v.Uptime,
Template: v.Template,
Lock: v.Lock,
LastSeen: timeToUnixMillis(v.LastSeen),
DiskStatusReason: v.DiskStatusReason,
}
// Convert tags array to string
if len(v.Tags) > 0 {
vm.Tags = strings.Join(v.Tags, ",")
}
vm.LastBackup = timeToUnixMillis(v.LastBackup)
// Include full Memory object if it has data
if v.Memory.Total > 0 {
vm.Memory = &v.Memory
}
// Include full Disk object if it has data
if v.Disk.Total > 0 {
vm.DiskObj = &v.Disk
}
// Include individual disks array if available
if len(v.Disks) > 0 {
vm.Disks = v.Disks
}
if len(v.IPAddresses) > 0 {
vm.IPAddresses = append([]string(nil), v.IPAddresses...)
}
if v.OSName != "" {
vm.OSName = v.OSName
}
if v.OSVersion != "" {
vm.OSVersion = v.OSVersion
}
if v.AgentVersion != "" {
vm.AgentVersion = v.AgentVersion
}
if len(v.NetworkInterfaces) > 0 {
vm.NetworkInterfaces = make([]GuestNetworkInterface, len(v.NetworkInterfaces))
copy(vm.NetworkInterfaces, v.NetworkInterfaces)
}
return vm.NormalizeCollections()
}
// ToFrontend converts a Container to ContainerFrontend
func (c Container) ToFrontend() ContainerFrontend {
ct := ContainerFrontend{
ID: c.ID,
VMID: c.VMID,
Name: c.Name,
Node: c.Node,
Pool: c.Pool,
Instance: c.Instance,
Status: c.Status,
Type: c.Type,
CPU: c.CPU,
CPUs: c.CPUs,
Mem: c.Memory.Used,
MaxMem: c.Memory.Total,
NetIn: zeroIfNegative(c.NetworkIn),
NetOut: zeroIfNegative(c.NetworkOut),
DiskRead: zeroIfNegative(c.DiskRead),
DiskWrite: zeroIfNegative(c.DiskWrite),
Uptime: c.Uptime,
Template: c.Template,
Lock: c.Lock,
LastSeen: timeToUnixMillis(c.LastSeen),
}
// OCI containers are classified separately from traditional LXC containers in the UI.
// Keep the frontend payload stable even if some internal sources only persist Type.
isOCI := c.IsOCI || strings.EqualFold(strings.TrimSpace(c.Type), "oci")
if isOCI {
ct.IsOCI = true
ct.Type = "oci"
}
// Preserve template metadata (LXC template or OCI image reference).
if c.OSTemplate != "" {
ct.OSTemplate = c.OSTemplate
}
// Convert tags array to string
if len(c.Tags) > 0 {
ct.Tags = strings.Join(c.Tags, ",")
}
ct.LastBackup = timeToUnixMillis(c.LastBackup)
// Include full Memory object if it has data
if c.Memory.Total > 0 {
ct.Memory = &c.Memory
}
// Include full Disk object if it has data
if c.Disk.Total > 0 {
ct.DiskObj = &c.Disk
}
// Include individual disks array if available
if len(c.Disks) > 0 {
ct.Disks = c.Disks
}
if len(c.IPAddresses) > 0 {
ct.IPAddresses = append([]string(nil), c.IPAddresses...)
}
if len(c.NetworkInterfaces) > 0 {
ct.NetworkInterfaces = make([]GuestNetworkInterface, len(c.NetworkInterfaces))
copy(ct.NetworkInterfaces, c.NetworkInterfaces)
}
if c.OSName != "" {
ct.OSName = c.OSName
}
return ct.NormalizeCollections()
}
// ToFrontend converts a DockerHost to DockerHostFrontend
func (d DockerHost) ToFrontend() DockerHostFrontend {
h := DockerHostFrontend{
ID: d.ID,
AgentID: d.AgentID,
Hostname: d.Hostname,
DisplayName: d.DisplayName,
CustomDisplayName: d.CustomDisplayName,
MachineID: d.MachineID,
OS: d.OS,
KernelVersion: d.KernelVersion,
Architecture: d.Architecture,
Runtime: d.Runtime,
RuntimeVersion: d.RuntimeVersion,
DockerVersion: d.DockerVersion,
CPUs: d.CPUs,
TotalMemoryBytes: d.TotalMemoryBytes,
UptimeSeconds: d.UptimeSeconds,
CPUUsagePercent: d.CPUUsage,
Status: d.Status,
LastSeen: timeToUnixMillis(d.LastSeen),
IntervalSeconds: d.IntervalSeconds,
AgentVersion: d.AgentVersion,
Containers: make([]DockerContainerFrontend, len(d.Containers)),
}
if h.DisplayName == "" {
h.DisplayName = h.Hostname
}
h.PendingUninstall = d.PendingUninstall
if d.TokenID != "" {
h.TokenID = d.TokenID
h.TokenName = d.TokenName
h.TokenHint = d.TokenHint
h.TokenLastUsedAt = optionalTimeToUnixMillis(d.TokenLastUsedAt)
}
for i, ct := range d.Containers {
h.Containers[i] = ct.ToFrontend()
}
if len(d.Services) > 0 {
h.Services = make([]DockerServiceFrontend, len(d.Services))
for i, svc := range d.Services {
h.Services[i] = svc.ToFrontend()
}
}
if len(d.Tasks) > 0 {
h.Tasks = make([]DockerTaskFrontend, len(d.Tasks))
for i, task := range d.Tasks {
h.Tasks[i] = task.ToFrontend()
}
}
if len(d.Nodes) > 0 {
h.Nodes = make([]DockerNodeFrontend, len(d.Nodes))
for i, node := range d.Nodes {
h.Nodes[i] = node.ToFrontend()
}
}
if len(d.Secrets) > 0 {
h.Secrets = make([]DockerSecretFrontend, len(d.Secrets))
for i, secret := range d.Secrets {
h.Secrets[i] = secret.ToFrontend()
}
}
if len(d.Configs) > 0 {
h.Configs = make([]DockerConfigFrontend, len(d.Configs))
for i, config := range d.Configs {
h.Configs[i] = config.ToFrontend()
}
}
if d.Swarm != nil {
sw := d.Swarm.ToFrontend()
h.Swarm = &sw
}
if d.Security != nil {
security := DockerHostSecurityFrontend{
AuthorizationPlugins: append([]string(nil), d.Security.AuthorizationPlugins...),
MutatingCommandsBlocked: d.Security.MutatingCommandsBlocked,
MutatingCommandsBlockedReason: d.Security.MutatingCommandsBlockedReason,
}.NormalizeCollections()
h.Security = &security
}
if len(d.LoadAverage) > 0 {
h.LoadAverage = append([]float64(nil), d.LoadAverage...)
}
if (d.Memory != Memory{}) {
mem := d.Memory
h.Memory = &mem
}
if len(d.Disks) > 0 {
h.Disks = append([]Disk(nil), d.Disks...)
}
if len(d.NetworkInterfaces) > 0 {
h.NetworkInterfaces = make([]HostNetworkInterface, len(d.NetworkInterfaces))
copy(h.NetworkInterfaces, d.NetworkInterfaces)
}
if d.Command != nil {
h.Command = toDockerHostCommandFrontend(*d.Command)
}
return h.NormalizeCollections()
}
// ToFrontend converts a KubernetesCluster to its frontend representation.
func (c KubernetesCluster) ToFrontend() KubernetesClusterFrontend {
cluster := KubernetesClusterFrontend{
ID: c.ID,
AgentID: c.AgentID,
Name: c.Name,
DisplayName: c.DisplayName,
CustomDisplayName: c.CustomDisplayName,
Server: c.Server,
Context: c.Context,
Version: c.Version,
Status: c.Status,
LastSeen: timeToUnixMillis(c.LastSeen),
IntervalSeconds: c.IntervalSeconds,
AgentVersion: c.AgentVersion,
TokenID: c.TokenID,
TokenName: c.TokenName,
TokenHint: c.TokenHint,
Hidden: c.Hidden,
PendingUninstall: c.PendingUninstall,
}
if c.TokenLastUsedAt != nil && !c.TokenLastUsedAt.IsZero() {
ts := c.TokenLastUsedAt.Unix() * 1000
cluster.TokenLastUsedAt = &ts
}
if len(c.Nodes) > 0 {
cluster.Nodes = make([]KubernetesNodeFrontend, len(c.Nodes))
for i, n := range c.Nodes {
cluster.Nodes[i] = KubernetesNodeFrontend{
UID: n.UID,
Name: n.Name,
Ready: n.Ready,
Unschedulable: n.Unschedulable,
KubeletVersion: n.KubeletVersion,
ContainerRuntimeVersion: n.ContainerRuntimeVersion,
OSImage: n.OSImage,
KernelVersion: n.KernelVersion,
Architecture: n.Architecture,
CapacityCPU: n.CapacityCPU,
CapacityMemoryBytes: n.CapacityMemoryBytes,
CapacityPods: n.CapacityPods,
AllocCPU: n.AllocCPU,
AllocMemoryBytes: n.AllocMemoryBytes,
AllocPods: n.AllocPods,
UsageCPUMilliCores: n.UsageCPUMilliCores,
UsageMemoryBytes: n.UsageMemoryBytes,
UsageCPUPercent: n.UsageCPUPercent,
UsageMemoryPercent: n.UsageMemoryPercent,
Roles: append([]string(nil), n.Roles...),
}.NormalizeCollections()
}
}
if len(c.Pods) > 0 {
cluster.Pods = make([]KubernetesPodFrontend, len(c.Pods))
for i, p := range c.Pods {
labels := make(map[string]string, len(p.Labels))
for k, v := range p.Labels {
labels[k] = v
}
containers := make([]KubernetesPodContainerFrontend, 0, len(p.Containers))
for _, cn := range p.Containers {
containers = append(containers, KubernetesPodContainerFrontend{
Name: cn.Name,
Image: cn.Image,
Ready: cn.Ready,
RestartCount: cn.RestartCount,
State: cn.State,
Reason: cn.Reason,
Message: cn.Message,
})
}
cluster.Pods[i] = KubernetesPodFrontend{
UID: p.UID,
Name: p.Name,
Namespace: p.Namespace,
NodeName: p.NodeName,
Phase: p.Phase,
Reason: p.Reason,
Message: p.Message,
QoSClass: p.QoSClass,
CreatedAt: timeToUnixMillis(p.CreatedAt),
StartTime: optionalTimeToUnixMillis(p.StartTime),
Restarts: p.Restarts,
UsageCPUMilliCores: p.UsageCPUMilliCores,
UsageMemoryBytes: p.UsageMemoryBytes,
UsageCPUPercent: p.UsageCPUPercent,
UsageMemoryPercent: p.UsageMemoryPercent,
NetworkRxBytes: p.NetworkRxBytes,
NetworkTxBytes: p.NetworkTxBytes,
NetInRate: p.NetInRate,
NetOutRate: p.NetOutRate,
EphemeralStorageUsedBytes: p.EphemeralStorageUsedBytes,
EphemeralStorageCapacityBytes: p.EphemeralStorageCapacityBytes,
DiskUsagePercent: p.DiskUsagePercent,
Labels: labels,
OwnerKind: p.OwnerKind,
OwnerName: p.OwnerName,
Containers: containers,
}.NormalizeCollections()
}
}
if len(c.Deployments) > 0 {
cluster.Deployments = make([]KubernetesDeploymentFrontend, len(c.Deployments))
for i, d := range c.Deployments {
labels := make(map[string]string, len(d.Labels))
for k, v := range d.Labels {
labels[k] = v
}
cluster.Deployments[i] = KubernetesDeploymentFrontend{
UID: d.UID,
Name: d.Name,
Namespace: d.Namespace,
CreatedAt: timeToUnixMillis(d.CreatedAt),
DesiredReplicas: d.DesiredReplicas,
UpdatedReplicas: d.UpdatedReplicas,
ReadyReplicas: d.ReadyReplicas,
AvailableReplicas: d.AvailableReplicas,
ObservedGeneration: d.ObservedGeneration,
Labels: labels,
}.NormalizeCollections()
}
}
if cluster.DisplayName == "" && cluster.Name != "" {
cluster.DisplayName = cluster.Name
}
if cluster.DisplayName == "" {
cluster.DisplayName = cluster.ID
}
return cluster.NormalizeCollections()
}
func timeToUnixMillis(t time.Time) int64 {
if t.IsZero() {
return 0
}
return t.Unix() * 1000
}
// optionalTimeToUnixMillis converts a *time.Time to *int64 (Unix milliseconds).
// Returns nil if the pointer is nil or the time is zero.
func optionalTimeToUnixMillis(t *time.Time) *int64 {
if t == nil || t.IsZero() {
return nil
}
ms := t.Unix() * 1000
return &ms
}
// ToFrontend converts a Host to HostFrontend.
func (h Host) ToFrontend() HostFrontend {
host := HostFrontend{
ID: h.ID,
Hostname: h.Hostname,
DisplayName: h.DisplayName,
Platform: h.Platform,
OSName: h.OSName,
OSVersion: h.OSVersion,
KernelVersion: h.KernelVersion,
Architecture: h.Architecture,
CPUCount: h.CPUCount,
CPUUsage: h.CPUUsage,
Status: h.Status,
UptimeSeconds: h.UptimeSeconds,
IntervalSeconds: h.IntervalSeconds,
AgentVersion: h.AgentVersion,
MachineID: h.MachineID,
TokenID: h.TokenID,
TokenName: h.TokenName,
TokenHint: h.TokenHint,
Tags: append([]string(nil), h.Tags...),
LastSeen: timeToUnixMillis(h.LastSeen),
CommandsEnabled: h.CommandsEnabled,
IsLegacy: h.IsLegacy,
LinkedNodeID: h.LinkedNodeID,
}
// Fall back to Hostname if DisplayName is empty
if host.DisplayName == "" && h.Hostname != "" {
host.DisplayName = h.Hostname
}
if len(h.LoadAverage) > 0 {
host.LoadAverage = append([]float64(nil), h.LoadAverage...)
}
if (h.Memory != Memory{}) {
mem := h.Memory
host.Memory = &mem
}
if len(h.Disks) > 0 {
host.Disks = append([]Disk(nil), h.Disks...)
}
if len(h.DiskIO) > 0 {
host.DiskIO = append([]DiskIO(nil), h.DiskIO...)
}
if len(h.NetworkInterfaces) > 0 {
host.NetworkInterfaces = make([]HostNetworkInterface, len(h.NetworkInterfaces))
copy(host.NetworkInterfaces, h.NetworkInterfaces)
}
if s := hostSensorSummaryToFrontend(h.Sensors); s != nil {
host.Sensors = s
}
host.TokenLastUsedAt = optionalTimeToUnixMillis(h.TokenLastUsedAt)
return host.NormalizeCollections()
}
// ToFrontend converts a DockerContainer to DockerContainerFrontend
func (c DockerContainer) ToFrontend() DockerContainerFrontend {
container := DockerContainerFrontend{
ID: c.ID,
Name: c.Name,
Image: c.Image,
State: c.State,
Status: c.Status,
Health: c.Health,
CPUPercent: c.CPUPercent,
MemoryUsage: c.MemoryUsage,
MemoryLimit: c.MemoryLimit,
MemoryPercent: c.MemoryPercent,
UptimeSeconds: c.UptimeSeconds,
RestartCount: c.RestartCount,
ExitCode: c.ExitCode,
CreatedAt: c.CreatedAt.Unix() * 1000,
Labels: nil,
WritableLayerBytes: c.WritableLayerBytes,
RootFilesystemBytes: c.RootFilesystemBytes,
}
if c.StartedAt != nil {
ms := c.StartedAt.Unix() * 1000
container.StartedAt = &ms
}
if c.FinishedAt != nil {
ms := c.FinishedAt.Unix() * 1000
container.FinishedAt = &ms
}
if len(c.Ports) > 0 {
ports := make([]DockerContainerPortFrontend, len(c.Ports))
for i, port := range c.Ports {
ports[i] = DockerContainerPortFrontend(port)
}
container.Ports = ports
}
if len(c.Labels) > 0 {
container.Labels = make(map[string]string, len(c.Labels))
for k, v := range c.Labels {
container.Labels[k] = v
}
}
if len(c.Networks) > 0 {
networks := make([]DockerContainerNetworkFrontend, len(c.Networks))
for i, net := range c.Networks {
networks[i] = DockerContainerNetworkFrontend(net)
}
container.Networks = networks
}
if c.BlockIO != nil {
container.BlockIO = &DockerContainerBlockIOFrontend{
ReadBytes: c.BlockIO.ReadBytes,
WriteBytes: c.BlockIO.WriteBytes,
ReadRateBytesPerSecond: c.BlockIO.ReadRateBytesPerSecond,
WriteRateBytesPerSecond: c.BlockIO.WriteRateBytesPerSecond,
}
}
if len(c.Mounts) > 0 {
mounts := make([]DockerContainerMountFrontend, len(c.Mounts))
for i, mount := range c.Mounts {
mounts[i] = DockerContainerMountFrontend(mount)
}
container.Mounts = mounts
}
if c.Podman != nil {
container.Podman = &DockerPodmanContainerFrontend{
PodName: c.Podman.PodName,
PodID: c.Podman.PodID,
Infra: c.Podman.Infra,
ComposeProject: c.Podman.ComposeProject,
ComposeService: c.Podman.ComposeService,
ComposeWorkdir: c.Podman.ComposeWorkdir,
ComposeConfigHash: c.Podman.ComposeConfigHash,
AutoUpdatePolicy: c.Podman.AutoUpdatePolicy,
AutoUpdateRestart: c.Podman.AutoUpdateRestart,
UserNamespace: c.Podman.UserNamespace,
}
}
if c.UpdateStatus != nil {
container.UpdateStatus = &DockerContainerUpdateStatusFrontend{
UpdateAvailable: c.UpdateStatus.UpdateAvailable,
CurrentDigest: c.UpdateStatus.CurrentDigest,
LatestDigest: c.UpdateStatus.LatestDigest,
LastChecked: timeToUnixMillis(c.UpdateStatus.LastChecked),
Error: c.UpdateStatus.Error,
}
}
return container.NormalizeCollections()
}
// ToFrontend converts a DockerService to DockerServiceFrontend.
func (s DockerService) ToFrontend() DockerServiceFrontend {
service := DockerServiceFrontend{
ID: s.ID,
Name: s.Name,
Stack: s.Stack,
Image: s.Image,
Mode: s.Mode,
DesiredTasks: s.DesiredTasks,
RunningTasks: s.RunningTasks,
CompletedTasks: s.CompletedTasks,
Labels: nil,
}
if len(s.Labels) > 0 {
service.Labels = make(map[string]string, len(s.Labels))
for k, v := range s.Labels {
service.Labels[k] = v
}
}
if len(s.EndpointPorts) > 0 {
service.EndpointPorts = make([]DockerServicePortFrontend, len(s.EndpointPorts))
for i, port := range s.EndpointPorts {
service.EndpointPorts[i] = port.ToFrontend()
}
}
if s.UpdateStatus != nil {
update := s.UpdateStatus.ToFrontend()
service.UpdateStatus = &update
}
if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
ts := s.CreatedAt.Unix() * 1000
service.CreatedAt = &ts
}
if s.UpdatedAt != nil && !s.UpdatedAt.IsZero() {
ts := s.UpdatedAt.Unix() * 1000
service.UpdatedAt = &ts
}
return service.NormalizeCollections()
}
// ToFrontend converts a DockerServicePort to DockerServicePortFrontend.
func (p DockerServicePort) ToFrontend() DockerServicePortFrontend {
return DockerServicePortFrontend(p)
}
// ToFrontend converts a DockerServiceUpdate to DockerServiceUpdateFrontend.
func (u DockerServiceUpdate) ToFrontend() DockerServiceUpdateFrontend {
update := DockerServiceUpdateFrontend{
State: u.State,
Message: u.Message,
}
if u.CompletedAt != nil && !u.CompletedAt.IsZero() {
ts := u.CompletedAt.Unix() * 1000
update.CompletedAt = &ts
}
return update
}
// ToFrontend converts a DockerTask to DockerTaskFrontend.
func (t DockerTask) ToFrontend() DockerTaskFrontend {
task := DockerTaskFrontend{
ID: t.ID,
ServiceID: t.ServiceID,
ServiceName: t.ServiceName,
Slot: t.Slot,
NodeID: t.NodeID,
NodeName: t.NodeName,
DesiredState: t.DesiredState,
CurrentState: t.CurrentState,
Error: t.Error,
Message: t.Message,
ContainerID: t.ContainerID,
ContainerName: t.ContainerName,
}
if !t.CreatedAt.IsZero() {
ts := t.CreatedAt.Unix() * 1000
task.CreatedAt = &ts
}
if t.UpdatedAt != nil && !t.UpdatedAt.IsZero() {
ts := t.UpdatedAt.Unix() * 1000
task.UpdatedAt = &ts
}
if t.StartedAt != nil && !t.StartedAt.IsZero() {
ts := t.StartedAt.Unix() * 1000
task.StartedAt = &ts
}
if t.CompletedAt != nil && !t.CompletedAt.IsZero() {
ts := t.CompletedAt.Unix() * 1000
task.CompletedAt = &ts
}
return task
}
// ToFrontend converts a DockerNode to DockerNodeFrontend.
func (n DockerNode) ToFrontend() DockerNodeFrontend {
node := DockerNodeFrontend{
ID: n.ID,
Hostname: n.Hostname,
Role: n.Role,
Availability: n.Availability,
State: n.State,
Message: n.Message,
Address: n.Address,
ManagerReachability: n.ManagerReachability,
ManagerAddress: n.ManagerAddress,
Leader: n.Leader,
EngineVersion: n.EngineVersion,
OS: n.OS,
Architecture: n.Architecture,
NanoCPUs: n.NanoCPUs,
MemoryBytes: n.MemoryBytes,
Labels: make(map[string]string, len(n.Labels)),
EngineLabels: make(map[string]string, len(n.EngineLabels)),
}
for key, value := range n.Labels {
node.Labels[key] = value
}
for key, value := range n.EngineLabels {
node.EngineLabels[key] = value
}
if !n.CreatedAt.IsZero() {
ts := n.CreatedAt.Unix() * 1000
node.CreatedAt = &ts
}
if n.UpdatedAt != nil && !n.UpdatedAt.IsZero() {
ts := n.UpdatedAt.Unix() * 1000
node.UpdatedAt = &ts
}
return node
}
// ToFrontend converts a DockerSecret to DockerSecretFrontend.
func (s DockerSecret) ToFrontend() DockerSecretFrontend {
secret := DockerSecretFrontend{
ID: s.ID,
Name: s.Name,
DriverName: s.DriverName,
TemplatingDriver: s.TemplatingDriver,
}
if len(s.Labels) > 0 {
secret.Labels = make(map[string]string, len(s.Labels))
for key, value := range s.Labels {
secret.Labels[key] = value
}
}
if !s.CreatedAt.IsZero() {
ts := s.CreatedAt.Unix() * 1000
secret.CreatedAt = &ts
}
if s.UpdatedAt != nil && !s.UpdatedAt.IsZero() {
ts := s.UpdatedAt.Unix() * 1000
secret.UpdatedAt = &ts
}
return secret.NormalizeCollections()
}
// ToFrontend converts a DockerConfig to DockerConfigFrontend.
func (c DockerConfig) ToFrontend() DockerConfigFrontend {
config := DockerConfigFrontend{
ID: c.ID,
Name: c.Name,
TemplatingDriver: c.TemplatingDriver,
}
if len(c.Labels) > 0 {
config.Labels = make(map[string]string, len(c.Labels))
for key, value := range c.Labels {
config.Labels[key] = value
}
}
if !c.CreatedAt.IsZero() {
ts := c.CreatedAt.Unix() * 1000
config.CreatedAt = &ts
}
if c.UpdatedAt != nil && !c.UpdatedAt.IsZero() {
ts := c.UpdatedAt.Unix() * 1000
config.UpdatedAt = &ts
}
return config.NormalizeCollections()
}
// ToFrontend converts DockerSwarmInfo to DockerSwarmFrontend.
func (s DockerSwarmInfo) ToFrontend() DockerSwarmFrontend {
return DockerSwarmFrontend(s)
}
func hostSensorSummaryToFrontend(src HostSensorSummary) *HostSensorSummaryFrontend {
if len(src.TemperatureCelsius) == 0 && len(src.FanRPM) == 0 && len(src.Additional) == 0 && len(src.GPU) == 0 && src.ThermalState == nil && len(src.SMART) == 0 {
return nil
}
dest := &HostSensorSummaryFrontend{}
if len(src.TemperatureCelsius) > 0 {
dest.TemperatureCelsius = copyStringFloatMap(src.TemperatureCelsius)
}
if len(src.FanRPM) > 0 {
dest.FanRPM = copyStringFloatMap(src.FanRPM)
}
if len(src.Additional) > 0 {
dest.Additional = copyStringFloatMap(src.Additional)
}
if len(src.GPU) > 0 {
dest.GPU = make([]HostGPUSensorFrontend, len(src.GPU))
for i, gpu := range src.GPU {
dest.GPU[i] = HostGPUSensorFrontend{
ID: gpu.ID,
Name: gpu.Name,
TemperatureCelsius: cloneFloat64Ptr(gpu.TemperatureCelsius),
UtilizationPercent: cloneFloat64Ptr(gpu.UtilizationPercent),
MemoryUsedBytes: cloneInt64Ptr(gpu.MemoryUsedBytes),
MemoryTotalBytes: cloneInt64Ptr(gpu.MemoryTotalBytes),
}
}
}
if src.ThermalState != nil {
dest.ThermalState = copyHostThermalState(src.ThermalState)
}
if len(src.SMART) > 0 {
dest.SMART = make([]HostDiskSMARTFrontend, len(src.SMART))
for i, disk := range src.SMART {
dest.SMART[i] = HostDiskSMARTFrontend{
Device: disk.Device,
Model: disk.Model,
Serial: disk.Serial,
WWN: disk.WWN,
Type: disk.Type,
Temperature: disk.Temperature,
Health: disk.Health,
Standby: disk.Standby,
Attributes: disk.Attributes,
}
}
}
normalized := dest.NormalizeCollections()
return &normalized
}
func copyHostThermalState(src *HostThermalState) *HostThermalState {
if src == nil {
return nil
}
dest := *src
dest.ThermalWarningLevel = cloneIntPtr(src.ThermalWarningLevel)
dest.PerformanceWarningLevel = cloneIntPtr(src.PerformanceWarningLevel)
dest.CPUPowerStatus = cloneIntPtr(src.CPUPowerStatus)
dest.LimitsPercent = copyStringIntMap(src.LimitsPercent)
return &dest
}
func copyStringFloatMap(src map[string]float64) map[string]float64 {
if len(src) == 0 {
return nil
}
dest := make(map[string]float64, len(src))
for k, v := range src {
dest[k] = v
}
return dest
}
func copyStringIntMap(src map[string]int) map[string]int {
if len(src) == 0 {
return nil
}
dest := make(map[string]int, len(src))
for k, v := range src {
dest[k] = v
}
return dest
}
func toDockerHostCommandFrontend(cmd DockerHostCommandStatus) *DockerHostCommandFrontend {
result := &DockerHostCommandFrontend{
ID: cmd.ID,
Type: cmd.Type,
Status: cmd.Status,
Message: cmd.Message,
CreatedAt: cmd.CreatedAt.Unix() * 1000,
UpdatedAt: cmd.UpdatedAt.Unix() * 1000,
}
if cmd.DispatchedAt != nil {
ms := cmd.DispatchedAt.Unix() * 1000
result.DispatchedAt = &ms
}
if cmd.AcknowledgedAt != nil {
ms := cmd.AcknowledgedAt.Unix() * 1000
result.AcknowledgedAt = &ms
}
if cmd.CompletedAt != nil {
ms := cmd.CompletedAt.Unix() * 1000
result.CompletedAt = &ms
}
if cmd.FailedAt != nil {
ms := cmd.FailedAt.Unix() * 1000
result.FailedAt = &ms
}
if cmd.FailureReason != "" {
result.FailureReason = cmd.FailureReason
}
if cmd.ExpiresAt != nil {
ms := cmd.ExpiresAt.Unix() * 1000
result.ExpiresAt = &ms
}
return result
}
// ToFrontend converts Storage to StorageFrontend
func (s Storage) ToFrontend() StorageFrontend {
return StorageFrontend{
ID: s.ID,
Storage: s.Name,
Name: s.Name,
Node: s.Node,
Instance: s.Instance,
Nodes: append([]string(nil), s.Nodes...),
NodeIDs: append([]string(nil), s.NodeIDs...),
NodeCount: s.NodeCount,
Type: s.Type,
Status: s.Status,
Pool: s.Pool,
Total: s.Total,
Used: s.Used,
Avail: s.Free,
Free: s.Free,
Usage: s.Usage,
Content: s.Content,
Shared: s.Shared,
Enabled: s.Enabled,
Active: s.Active,
ZFSPool: s.ZFSPool,
}.NormalizeCollections()
}
// ToFrontend converts a replication job to a frontend representation.
func (r ReplicationJob) ToFrontend() ReplicationJobFrontend {
frontend := ReplicationJobFrontend{
ID: r.ID,
Instance: r.Instance,
JobID: r.JobID,
JobNumber: r.JobNumber,
Guest: r.Guest,
GuestID: r.GuestID,
GuestName: r.GuestName,
GuestType: r.GuestType,
GuestNode: r.GuestNode,
SourceNode: r.SourceNode,
SourceStorage: r.SourceStorage,
TargetNode: r.TargetNode,
TargetStorage: r.TargetStorage,
Schedule: r.Schedule,
Type: r.Type,
Enabled: r.Enabled,
State: r.State,
Status: r.Status,
LastSyncStatus: r.LastSyncStatus,
LastSyncUnix: r.LastSyncUnix,
LastSyncDurationSeconds: r.LastSyncDurationSeconds,
LastSyncDurationHuman: r.LastSyncDurationHuman,
NextSyncUnix: r.NextSyncUnix,
DurationSeconds: r.DurationSeconds,
DurationHuman: r.DurationHuman,
FailCount: r.FailCount,
Error: r.Error,
Comment: r.Comment,
RemoveJob: r.RemoveJob,
RateLimitMbps: r.RateLimitMbps,
}
if r.LastSyncTime != nil {
frontend.LastSyncTime = r.LastSyncTime.UnixMilli()
}
if r.NextSyncTime != nil {
frontend.NextSyncTime = r.NextSyncTime.UnixMilli()
}
polledAt := r.LastPolled
if polledAt.IsZero() {
polledAt = time.Now()
}
frontend.PolledAt = polledAt.UnixMilli()
return frontend
}
// zeroIfNegative returns 0 for negative values (used for I/O metrics)
func zeroIfNegative(val int64) int64 {
if val < 0 {
return 0
}
return val
}
// ResourceToFrontend converts a resources.Resource to ResourceFrontend.
// This function is in models package to avoid circular imports.
// It takes individual fields rather than the whole Resource to avoid importing resources package.
type ResourceConvertInput struct {
ID string
Type string
Technology string
Name string
DisplayName string
PlatformID string
PlatformType string
SourceType string
Sources []string
ParentID string
ClusterID string
Status string
CPU *ResourceMetricInput
Memory *ResourceMetricInput
Disk *ResourceMetricInput
NetworkRX int64
NetworkTX int64
HasNetwork bool
DiskReadRate int64
DiskWriteRate int64
HasDiskIO bool
Temperature *float64
Uptime *int64
Tags []string
Labels map[string]string
CustomURL string
LastSeenUnix int64
Alerts []ResourceAlertInput
IncidentCount int
IncidentCode string
IncidentSeverity string
IncidentSummary string
IncidentCategory string
IncidentLabel string
IncidentPriority int
IncidentImpactSummary string
IncidentUrgency string
IncidentAction string
Identity *ResourceIdentityInput
DiscoveryTarget json.RawMessage
MetricsTarget json.RawMessage
Canonical json.RawMessage
Policy json.RawMessage
AISafeSummary string
Capabilities json.RawMessage
Relationships json.RawMessage
RecentChanges json.RawMessage
FacetCounts json.RawMessage
Incidents json.RawMessage
Proxmox json.RawMessage
Storage json.RawMessage
Agent json.RawMessage
Docker json.RawMessage
PBS json.RawMessage
PMG json.RawMessage
Kubernetes json.RawMessage
PhysicalDisk json.RawMessage
Ceph json.RawMessage
TrueNAS json.RawMessage
VMware json.RawMessage
Availability json.RawMessage
PlatformData json.RawMessage
}
// ResourceMetricInput represents a metric value for resource conversion.
type ResourceMetricInput struct {
Current float64
Total *int64
Used *int64
Free *int64
}
type ResourceAlertInput struct {
ID string
Type string
Level string
Message string
Value float64
Threshold float64
StartTimeUnix int64
}
type ResourceIdentityInput struct {
Hostname string
MachineID string
IPs []string
}
// ConvertResourceToFrontend converts input to ResourceFrontend.
func ConvertResourceToFrontend(input ResourceConvertInput) ResourceFrontend {
rf := ResourceFrontend{
ID: input.ID,
Type: input.Type,
Technology: input.Technology,
Name: input.Name,
DisplayName: input.DisplayName,
PlatformID: input.PlatformID,
PlatformType: input.PlatformType,
SourceType: input.SourceType,
Sources: append([]string(nil), input.Sources...),
ParentID: input.ParentID,
ClusterID: input.ClusterID,
Status: input.Status,
Temperature: input.Temperature,
Uptime: input.Uptime,
Tags: append([]string(nil), input.Tags...),
Labels: nil,
CustomURL: input.CustomURL,
LastSeen: input.LastSeenUnix,
IncidentCount: input.IncidentCount,
IncidentCode: input.IncidentCode,
IncidentSeverity: input.IncidentSeverity,
IncidentSummary: input.IncidentSummary,
IncidentCategory: input.IncidentCategory,
IncidentLabel: input.IncidentLabel,
IncidentPriority: input.IncidentPriority,
IncidentImpactSummary: input.IncidentImpactSummary,
IncidentUrgency: input.IncidentUrgency,
IncidentAction: input.IncidentAction,
DiscoveryTarget: input.DiscoveryTarget,
MetricsTarget: input.MetricsTarget,
Canonical: input.Canonical,
Policy: input.Policy,
AISafeSummary: input.AISafeSummary,
Capabilities: input.Capabilities,
Relationships: input.Relationships,
RecentChanges: input.RecentChanges,
FacetCounts: input.FacetCounts,
Incidents: input.Incidents,
Proxmox: input.Proxmox,
Storage: input.Storage,
Agent: input.Agent,
Docker: input.Docker,
PBS: input.PBS,
PMG: input.PMG,
Kubernetes: input.Kubernetes,
PhysicalDisk: input.PhysicalDisk,
Ceph: input.Ceph,
TrueNAS: input.TrueNAS,
VMware: input.VMware,
Availability: input.Availability,
PlatformData: input.PlatformData,
}
if len(input.Labels) > 0 {
rf.Labels = make(map[string]string, len(input.Labels))
for k, v := range input.Labels {
rf.Labels[k] = v
}
}
// Convert metrics
if input.CPU != nil {
rf.CPU = &ResourceMetricFrontend{
Current: input.CPU.Current,
Total: input.CPU.Total,
Used: input.CPU.Used,
Free: input.CPU.Free,
}
}
if input.Memory != nil {
rf.Memory = &ResourceMetricFrontend{
Current: input.Memory.Current,
Total: input.Memory.Total,
Used: input.Memory.Used,
Free: input.Memory.Free,
}
}
if input.Disk != nil {
rf.Disk = &ResourceMetricFrontend{
Current: input.Disk.Current,
Total: input.Disk.Total,
Used: input.Disk.Used,
Free: input.Disk.Free,
}
}
if input.HasNetwork {
rf.Network = &ResourceNetworkFrontend{
RXBytes: input.NetworkRX,
TXBytes: input.NetworkTX,
}
}
if input.HasDiskIO {
rf.DiskIO = &ResourceDiskIOFrontend{
ReadRate: input.DiskReadRate,
WriteRate: input.DiskWriteRate,
}
}
// Convert alerts
if len(input.Alerts) > 0 {
rf.Alerts = make([]ResourceAlertFrontend, len(input.Alerts))
for i, a := range input.Alerts {
rf.Alerts[i] = ResourceAlertFrontend{
ID: a.ID,
Type: a.Type,
Level: a.Level,
Message: a.Message,
Value: a.Value,
Threshold: a.Threshold,
StartTime: a.StartTimeUnix,
}
}
}
// Convert identity
if input.Identity != nil {
rf.Identity = &ResourceIdentityFrontend{
Hostname: input.Identity.Hostname,
MachineID: input.Identity.MachineID,
IPs: append([]string(nil), input.Identity.IPs...),
}
}
return rf.NormalizeCollections()
}