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, GuestAgentStatus: v.GuestAgentStatus, GuestAgentExpected: v.GuestAgentExpected, } // 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, AppliedConfig: cloneAgentConfigFingerprint(h.AppliedConfig), AgentUpdate: cloneAgentUpdateStatus(h.AgentUpdate), AgentModules: cloneAgentModuleStatuses(h.AgentModules), PackageUpdates: cloneHostPackageUpdateStatus(h.PackageUpdates), StorageCleanup: cloneHostStorageCleanupStatus(h.StorageCleanup), 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() } func cloneAgentConfigFingerprint(value *AgentConfigFingerprint) *AgentConfigFingerprint { if value == nil { return nil } copy := *value return © } func cloneAgentUpdateStatus(value *AgentUpdateStatus) *AgentUpdateStatus { if value == nil { return nil } copy := *value if value.LastCheckedAt != nil { checked := *value.LastCheckedAt copy.LastCheckedAt = &checked } if value.LastAttemptAt != nil { attempt := *value.LastAttemptAt copy.LastAttemptAt = &attempt } if value.LastSuccessAt != nil { success := *value.LastSuccessAt copy.LastSuccessAt = &success } return © } func cloneAgentModuleStatuses(values []AgentModuleStatus) []AgentModuleStatus { if len(values) == 0 { return nil } return append([]AgentModuleStatus(nil), values...) } // 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, CPUCapacityPercent: c.CPUCapacityPercent, MemoryUsage: c.MemoryUsage, MemoryLimit: c.MemoryLimit, MemoryPercent: c.MemoryPercent, UptimeSeconds: c.UptimeSeconds, RestartCount: c.RestartCount, ExitCode: c.ExitCode, OOMKilled: cloneBoolPtr(c.OOMKilled), 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.PowerWatts) == 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.PowerWatts) > 0 { dest.PowerWatts = copyStringFloatMap(src.PowerWatts) } 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, SizeBytes: disk.SizeBytes, 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() }