mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
3a4a3fd62b
Expose confined, identity-bound filesystem observations through the shared resource pipeline so investigations can distinguish an exhausted container mount from unrelated host capacity. Keep unavailable measurements explicit. Isolate alert-history reads from durable writes and reuse one chronological fold across polling. Catch up through bounded durable event IDs so simultaneous readers do not replay every retained snapshot. Retain expired actions when investigation outcomes move back to needs attention, and keep attached Assistant context focused. Record live storage diagnosis, healthy and dependency controls, approved and rejected Docker outcomes, source-bound browser proof and exact test limits. Missing-access continuity, VM dispatch completion and remaining Assistant orchestration defects stay open in the redesign plan.
1185 lines
56 KiB
Go
1185 lines
56 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/pkg/agents/filesystem"
|
|
"github.com/rcourtman/pulse-go-rewrite/pkg/diskinventory"
|
|
)
|
|
|
|
// Frontend-friendly type aliases with proper JSON tags
|
|
// These extend the base types with additional computed fields
|
|
|
|
// NodeFrontend represents a Node with frontend-friendly field names
|
|
type NodeFrontend struct {
|
|
ID string `json:"id"`
|
|
Node string `json:"node"` // Maps to Name
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
Instance string `json:"instance"`
|
|
Host string `json:"host,omitempty"`
|
|
GuestURL string `json:"guestURL,omitempty"` // Optional guest-accessible URL (for navigation)
|
|
Status string `json:"status"`
|
|
Type string `json:"type"`
|
|
CPU float64 `json:"cpu"`
|
|
Memory *Memory `json:"memory,omitempty"` // Full memory object with usage percentage
|
|
Mem int64 `json:"mem"` // Maps to Memory.Used (kept for backward compat)
|
|
MaxMem int64 `json:"maxmem"` // Maps to Memory.Total (kept for backward compat)
|
|
Disk *Disk `json:"disk,omitempty"` // Full disk object with usage percentage
|
|
MaxDisk int64 `json:"maxdisk"` // Maps to Disk.Total (kept for backward compat)
|
|
Uptime int64 `json:"uptime"`
|
|
LoadAverage []float64 `json:"loadAverage"`
|
|
KernelVersion string `json:"kernelVersion"`
|
|
PVEVersion string `json:"pveVersion"`
|
|
CPUInfo CPUInfo `json:"cpuInfo"`
|
|
Temperature *Temperature `json:"temperature,omitempty"` // CPU/NVMe temperatures
|
|
LastSeen int64 `json:"lastSeen"` // Unix timestamp
|
|
ConnectionHealth string `json:"connectionHealth"`
|
|
IsClusterMember bool `json:"isClusterMember,omitempty"`
|
|
ClusterName string `json:"clusterName,omitempty"`
|
|
TemperatureMonitoringEnabled *bool `json:"temperatureMonitoringEnabled,omitempty"` // Per-node temperature monitoring override
|
|
LinkedAgentID string `json:"linkedAgentId,omitempty"` // ID of linked agent (for "Via agent" badge)
|
|
}
|
|
|
|
func (n NodeFrontend) NormalizeCollections() NodeFrontend {
|
|
if n.LoadAverage == nil {
|
|
n.LoadAverage = []float64{}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// VMFrontend represents a VM with frontend-friendly field names
|
|
type VMFrontend struct {
|
|
ID string `json:"id"`
|
|
VMID int `json:"vmid"`
|
|
Name string `json:"name"`
|
|
Node string `json:"node"`
|
|
Pool string `json:"pool,omitempty"`
|
|
Instance string `json:"instance"`
|
|
Status string `json:"status"`
|
|
Type string `json:"type"`
|
|
CPU float64 `json:"cpu"`
|
|
CPUs int `json:"cpus"`
|
|
Memory *Memory `json:"memory,omitempty"` // Full memory object
|
|
Mem int64 `json:"mem"` // Maps to Memory.Used
|
|
MaxMem int64 `json:"maxmem"` // Maps to Memory.Total
|
|
DiskObj *Disk `json:"disk,omitempty"` // Full disk object
|
|
Disks []Disk `json:"disks"` // Individual filesystem/disk usage
|
|
DiskStatusReason string `json:"diskStatusReason,omitempty"` // Why disk stats are unavailable
|
|
GuestAgentStatus string `json:"guestAgentStatus,omitempty"` // QEMU guest-agent runtime status
|
|
GuestAgentExpected bool `json:"guestAgentExpected,omitempty"`
|
|
OSName string `json:"osName,omitempty"`
|
|
OSVersion string `json:"osVersion,omitempty"`
|
|
AgentVersion string `json:"agentVersion,omitempty"`
|
|
NetworkInterfaces []GuestNetworkInterface `json:"networkInterfaces"`
|
|
IPAddresses []string `json:"ipAddresses"`
|
|
NetIn int64 `json:"networkIn"` // Maps to NetworkIn (camelCase for frontend)
|
|
NetOut int64 `json:"networkOut"` // Maps to NetworkOut (camelCase for frontend)
|
|
DiskRead int64 `json:"diskRead"` // Maps to DiskRead (camelCase for frontend)
|
|
DiskWrite int64 `json:"diskWrite"` // Maps to DiskWrite (camelCase for frontend)
|
|
Uptime int64 `json:"uptime"`
|
|
Template bool `json:"template"`
|
|
LastBackup int64 `json:"lastBackup,omitempty"` // Unix timestamp
|
|
Tags string `json:"tags,omitempty"` // Joined string
|
|
Lock string `json:"lock,omitempty"`
|
|
LastSeen int64 `json:"lastSeen"` // Unix timestamp
|
|
}
|
|
|
|
func (v VMFrontend) NormalizeCollections() VMFrontend {
|
|
if v.Disks == nil {
|
|
v.Disks = []Disk{}
|
|
}
|
|
if v.NetworkInterfaces == nil {
|
|
v.NetworkInterfaces = []GuestNetworkInterface{}
|
|
}
|
|
if v.IPAddresses == nil {
|
|
v.IPAddresses = []string{}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// ContainerFrontend represents a Container with frontend-friendly field names
|
|
type ContainerFrontend struct {
|
|
ID string `json:"id"`
|
|
VMID int `json:"vmid"`
|
|
Name string `json:"name"`
|
|
Node string `json:"node"`
|
|
Pool string `json:"pool,omitempty"`
|
|
Instance string `json:"instance"`
|
|
Status string `json:"status"`
|
|
Type string `json:"type"`
|
|
// OCI container support (Proxmox VE 9.1+)
|
|
IsOCI bool `json:"isOci,omitempty"` // True if this is an OCI container
|
|
OSTemplate string `json:"osTemplate,omitempty"` // Template or OCI image used (e.g., "oci:docker.io/library/alpine:latest")
|
|
CPU float64 `json:"cpu"`
|
|
CPUs int `json:"cpus"`
|
|
Memory *Memory `json:"memory,omitempty"` // Full memory object
|
|
Mem int64 `json:"mem"` // Maps to Memory.Used
|
|
MaxMem int64 `json:"maxmem"` // Maps to Memory.Total
|
|
DiskObj *Disk `json:"disk,omitempty"` // Full disk object
|
|
Disks []Disk `json:"disks"` // Individual filesystem/disk usage
|
|
NetworkInterfaces []GuestNetworkInterface `json:"networkInterfaces"`
|
|
IPAddresses []string `json:"ipAddresses"`
|
|
NetIn int64 `json:"networkIn"` // Maps to NetworkIn (camelCase for frontend)
|
|
NetOut int64 `json:"networkOut"` // Maps to NetworkOut (camelCase for frontend)
|
|
DiskRead int64 `json:"diskRead"` // Maps to DiskRead (camelCase for frontend)
|
|
DiskWrite int64 `json:"diskWrite"` // Maps to DiskWrite (camelCase for frontend)
|
|
Uptime int64 `json:"uptime"`
|
|
Template bool `json:"template"`
|
|
LastBackup int64 `json:"lastBackup,omitempty"` // Unix timestamp
|
|
Tags string `json:"tags,omitempty"` // Joined string
|
|
Lock string `json:"lock,omitempty"`
|
|
LastSeen int64 `json:"lastSeen"` // Unix timestamp
|
|
OSName string `json:"osName,omitempty"`
|
|
}
|
|
|
|
func (c ContainerFrontend) NormalizeCollections() ContainerFrontend {
|
|
if c.Disks == nil {
|
|
c.Disks = []Disk{}
|
|
}
|
|
if c.NetworkInterfaces == nil {
|
|
c.NetworkInterfaces = []GuestNetworkInterface{}
|
|
}
|
|
if c.IPAddresses == nil {
|
|
c.IPAddresses = []string{}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// DockerHostFrontend represents a Docker host with frontend-friendly fields
|
|
type DockerHostFrontend struct {
|
|
ID string `json:"id"`
|
|
AgentID string `json:"agentId"`
|
|
Hostname string `json:"hostname"`
|
|
DisplayName string `json:"displayName"`
|
|
CustomDisplayName string `json:"customDisplayName,omitempty"`
|
|
MachineID string `json:"machineId,omitempty"`
|
|
OS string `json:"os,omitempty"`
|
|
KernelVersion string `json:"kernelVersion,omitempty"`
|
|
Architecture string `json:"architecture,omitempty"`
|
|
Runtime string `json:"runtime"`
|
|
RuntimeVersion string `json:"runtimeVersion,omitempty"`
|
|
DockerVersion string `json:"dockerVersion,omitempty"`
|
|
CollectionMode string `json:"collectionMode,omitempty"`
|
|
CPUs int `json:"cpus"`
|
|
TotalMemoryBytes int64 `json:"totalMemoryBytes"`
|
|
UptimeSeconds int64 `json:"uptimeSeconds"`
|
|
CPUUsagePercent float64 `json:"cpuUsagePercent"`
|
|
LoadAverage []float64 `json:"loadAverage"`
|
|
Memory *Memory `json:"memory,omitempty"`
|
|
Disks []Disk `json:"disks"`
|
|
NetworkInterfaces []HostNetworkInterface `json:"networkInterfaces"`
|
|
Status string `json:"status"`
|
|
LastSeen int64 `json:"lastSeen"`
|
|
IntervalSeconds int `json:"intervalSeconds"`
|
|
AgentVersion string `json:"agentVersion,omitempty"`
|
|
Containers []DockerContainerFrontend `json:"containers"`
|
|
Services []DockerServiceFrontend `json:"services"`
|
|
Tasks []DockerTaskFrontend `json:"tasks"`
|
|
Nodes []DockerNodeFrontend `json:"nodes"`
|
|
Secrets []DockerSecretFrontend `json:"secrets"`
|
|
Configs []DockerConfigFrontend `json:"configs"`
|
|
Swarm *DockerSwarmFrontend `json:"swarm,omitempty"`
|
|
Security *DockerHostSecurityFrontend `json:"security,omitempty"`
|
|
TokenID string `json:"tokenId,omitempty"`
|
|
TokenName string `json:"tokenName,omitempty"`
|
|
TokenHint string `json:"tokenHint,omitempty"`
|
|
TokenLastUsedAt *int64 `json:"tokenLastUsedAt,omitempty"`
|
|
PendingUninstall bool `json:"pendingUninstall"`
|
|
Command *DockerHostCommandFrontend `json:"command,omitempty"`
|
|
}
|
|
|
|
func (h DockerHostFrontend) NormalizeCollections() DockerHostFrontend {
|
|
if h.LoadAverage == nil {
|
|
h.LoadAverage = []float64{}
|
|
}
|
|
if h.Disks == nil {
|
|
h.Disks = []Disk{}
|
|
}
|
|
if h.NetworkInterfaces == nil {
|
|
h.NetworkInterfaces = []HostNetworkInterface{}
|
|
}
|
|
if h.Containers == nil {
|
|
h.Containers = []DockerContainerFrontend{}
|
|
}
|
|
if h.Services == nil {
|
|
h.Services = []DockerServiceFrontend{}
|
|
}
|
|
if h.Tasks == nil {
|
|
h.Tasks = []DockerTaskFrontend{}
|
|
}
|
|
if h.Nodes == nil {
|
|
h.Nodes = []DockerNodeFrontend{}
|
|
}
|
|
if h.Secrets == nil {
|
|
h.Secrets = []DockerSecretFrontend{}
|
|
}
|
|
if h.Configs == nil {
|
|
h.Configs = []DockerConfigFrontend{}
|
|
}
|
|
for i := range h.Containers {
|
|
h.Containers[i] = h.Containers[i].NormalizeCollections()
|
|
}
|
|
for i := range h.Services {
|
|
h.Services[i] = h.Services[i].NormalizeCollections()
|
|
}
|
|
for i := range h.Secrets {
|
|
h.Secrets[i] = h.Secrets[i].NormalizeCollections()
|
|
}
|
|
for i := range h.Configs {
|
|
h.Configs[i] = h.Configs[i].NormalizeCollections()
|
|
}
|
|
if h.Security != nil {
|
|
security := h.Security.NormalizeCollections()
|
|
h.Security = &security
|
|
}
|
|
return h
|
|
}
|
|
|
|
// DockerHostSecurityFrontend exposes docker-host security posture to the UI.
|
|
type DockerHostSecurityFrontend struct {
|
|
AuthorizationPlugins []string `json:"authorizationPlugins"`
|
|
MutatingCommandsBlocked bool `json:"mutatingCommandsBlocked,omitempty"`
|
|
MutatingCommandsBlockedReason string `json:"mutatingCommandsBlockedReason,omitempty"`
|
|
}
|
|
|
|
func (s DockerHostSecurityFrontend) NormalizeCollections() DockerHostSecurityFrontend {
|
|
if s.AuthorizationPlugins == nil {
|
|
s.AuthorizationPlugins = []string{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ConnectedInfrastructureSurfaceFrontend describes one reporting surface that
|
|
// Pulse associates with an infrastructure item in the connected-infrastructure
|
|
// settings view.
|
|
type ConnectedInfrastructureSurfaceFrontend struct {
|
|
ID string `json:"id"`
|
|
Kind string `json:"kind"`
|
|
Label string `json:"label"`
|
|
Detail string `json:"detail,omitempty"`
|
|
ControlID string `json:"controlId,omitempty"`
|
|
Action string `json:"action,omitempty"`
|
|
IDLabel string `json:"idLabel,omitempty"`
|
|
IDValue string `json:"idValue,omitempty"`
|
|
}
|
|
|
|
// ConnectedInfrastructureItemFrontend is the canonical Connected
|
|
// infrastructure projection for the settings surface. It is derived from the
|
|
// unified resource model plus reporting-ignore state, so the frontend does not
|
|
// need to reconstruct machine rows from raw resource facets.
|
|
type ConnectedInfrastructureItemFrontend struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
Hostname string `json:"hostname,omitempty"`
|
|
Status string `json:"status"`
|
|
HealthStatus string `json:"healthStatus,omitempty"`
|
|
LastSeen int64 `json:"lastSeen,omitempty"`
|
|
RemovedAt int64 `json:"removedAt,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
IsOutdatedBinary bool `json:"isOutdatedBinary,omitempty"`
|
|
LinkedNodeID string `json:"linkedNodeId,omitempty"`
|
|
LinkedVMID string `json:"linkedVmId,omitempty"`
|
|
LinkedContainerID string `json:"linkedContainerId,omitempty"`
|
|
CommandsEnabled bool `json:"commandsEnabled,omitempty"`
|
|
ScopeAgentID string `json:"scopeAgentId,omitempty"`
|
|
UpgradePlatform string `json:"upgradePlatform,omitempty"`
|
|
UninstallAgentID string `json:"uninstallAgentId,omitempty"`
|
|
UninstallHostname string `json:"uninstallHostname,omitempty"`
|
|
Surfaces []ConnectedInfrastructureSurfaceFrontend `json:"surfaces"`
|
|
}
|
|
|
|
func (i ConnectedInfrastructureItemFrontend) NormalizeCollections() ConnectedInfrastructureItemFrontend {
|
|
if i.Surfaces == nil {
|
|
i.Surfaces = []ConnectedInfrastructureSurfaceFrontend{}
|
|
}
|
|
return i
|
|
}
|
|
|
|
// KubernetesClusterFrontend represents a Kubernetes cluster for the frontend.
|
|
type KubernetesClusterFrontend struct {
|
|
ID string `json:"id"`
|
|
AgentID string `json:"agentId"`
|
|
Name string `json:"name,omitempty"`
|
|
DisplayName string `json:"displayName,omitempty"`
|
|
CustomDisplayName string `json:"customDisplayName,omitempty"`
|
|
Server string `json:"server,omitempty"`
|
|
Context string `json:"context,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
Status string `json:"status"`
|
|
LastSeen int64 `json:"lastSeen"`
|
|
IntervalSeconds int `json:"intervalSeconds"`
|
|
AgentVersion string `json:"agentVersion,omitempty"`
|
|
TokenID string `json:"tokenId,omitempty"`
|
|
TokenName string `json:"tokenName,omitempty"`
|
|
TokenHint string `json:"tokenHint,omitempty"`
|
|
TokenLastUsedAt *int64 `json:"tokenLastUsedAt,omitempty"`
|
|
Hidden bool `json:"hidden"`
|
|
PendingUninstall bool `json:"pendingUninstall"`
|
|
|
|
Nodes []KubernetesNodeFrontend `json:"nodes"`
|
|
Pods []KubernetesPodFrontend `json:"pods"`
|
|
Deployments []KubernetesDeploymentFrontend `json:"deployments"`
|
|
}
|
|
|
|
func (c KubernetesClusterFrontend) NormalizeCollections() KubernetesClusterFrontend {
|
|
if c.Nodes == nil {
|
|
c.Nodes = []KubernetesNodeFrontend{}
|
|
}
|
|
if c.Pods == nil {
|
|
c.Pods = []KubernetesPodFrontend{}
|
|
}
|
|
if c.Deployments == nil {
|
|
c.Deployments = []KubernetesDeploymentFrontend{}
|
|
}
|
|
for i := range c.Nodes {
|
|
c.Nodes[i] = c.Nodes[i].NormalizeCollections()
|
|
}
|
|
for i := range c.Pods {
|
|
c.Pods[i] = c.Pods[i].NormalizeCollections()
|
|
}
|
|
for i := range c.Deployments {
|
|
c.Deployments[i] = c.Deployments[i].NormalizeCollections()
|
|
}
|
|
return c
|
|
}
|
|
|
|
type KubernetesNodeFrontend struct {
|
|
UID string `json:"uid"`
|
|
Name string `json:"name"`
|
|
Ready bool `json:"ready"`
|
|
Unschedulable bool `json:"unschedulable,omitempty"`
|
|
KubeletVersion string `json:"kubeletVersion,omitempty"`
|
|
ContainerRuntimeVersion string `json:"containerRuntimeVersion,omitempty"`
|
|
OSImage string `json:"osImage,omitempty"`
|
|
KernelVersion string `json:"kernelVersion,omitempty"`
|
|
Architecture string `json:"architecture,omitempty"`
|
|
CapacityCPU int64 `json:"capacityCpuCores,omitempty"`
|
|
CapacityMemoryBytes int64 `json:"capacityMemoryBytes,omitempty"`
|
|
CapacityPods int64 `json:"capacityPods,omitempty"`
|
|
AllocCPU int64 `json:"allocatableCpuCores,omitempty"`
|
|
AllocMemoryBytes int64 `json:"allocatableMemoryBytes,omitempty"`
|
|
AllocPods int64 `json:"allocatablePods,omitempty"`
|
|
UsageCPUMilliCores int64 `json:"usageCpuMilliCores,omitempty"`
|
|
UsageMemoryBytes int64 `json:"usageMemoryBytes,omitempty"`
|
|
UsageCPUPercent float64 `json:"usageCpuPercent,omitempty"`
|
|
UsageMemoryPercent float64 `json:"usageMemoryPercent,omitempty"`
|
|
Roles []string `json:"roles"`
|
|
}
|
|
|
|
func (n KubernetesNodeFrontend) NormalizeCollections() KubernetesNodeFrontend {
|
|
if n.Roles == nil {
|
|
n.Roles = []string{}
|
|
}
|
|
return n
|
|
}
|
|
|
|
type KubernetesPodFrontend struct {
|
|
UID string `json:"uid"`
|
|
Name string `json:"name"`
|
|
Namespace string `json:"namespace"`
|
|
NodeName string `json:"nodeName,omitempty"`
|
|
Phase string `json:"phase,omitempty"`
|
|
Reason string `json:"reason,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
QoSClass string `json:"qosClass,omitempty"`
|
|
CreatedAt int64 `json:"createdAt,omitempty"`
|
|
StartTime *int64 `json:"startTime,omitempty"`
|
|
Restarts int `json:"restarts,omitempty"`
|
|
UsageCPUMilliCores int `json:"usageCpuMilliCores,omitempty"`
|
|
UsageMemoryBytes int64 `json:"usageMemoryBytes,omitempty"`
|
|
UsageCPUPercent float64 `json:"usageCpuPercent,omitempty"`
|
|
UsageMemoryPercent float64 `json:"usageMemoryPercent,omitempty"`
|
|
NetworkRxBytes int64 `json:"networkRxBytes,omitempty"`
|
|
NetworkTxBytes int64 `json:"networkTxBytes,omitempty"`
|
|
NetInRate float64 `json:"netInRate,omitempty"`
|
|
NetOutRate float64 `json:"netOutRate,omitempty"`
|
|
EphemeralStorageUsedBytes int64 `json:"ephemeralStorageUsedBytes,omitempty"`
|
|
EphemeralStorageCapacityBytes int64 `json:"ephemeralStorageCapacityBytes,omitempty"`
|
|
DiskUsagePercent float64 `json:"diskUsagePercent,omitempty"`
|
|
Labels map[string]string `json:"labels"`
|
|
OwnerKind string `json:"ownerKind,omitempty"`
|
|
OwnerName string `json:"ownerName,omitempty"`
|
|
Containers []KubernetesPodContainerFrontend `json:"containers"`
|
|
}
|
|
|
|
func (p KubernetesPodFrontend) NormalizeCollections() KubernetesPodFrontend {
|
|
if p.Labels == nil {
|
|
p.Labels = map[string]string{}
|
|
}
|
|
if p.Containers == nil {
|
|
p.Containers = []KubernetesPodContainerFrontend{}
|
|
}
|
|
return p
|
|
}
|
|
|
|
type KubernetesPodContainerFrontend struct {
|
|
Name string `json:"name"`
|
|
Image string `json:"image,omitempty"`
|
|
Ready bool `json:"ready"`
|
|
RestartCount int32 `json:"restartCount,omitempty"`
|
|
State string `json:"state,omitempty"`
|
|
Reason string `json:"reason,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
type KubernetesDeploymentFrontend struct {
|
|
UID string `json:"uid"`
|
|
Name string `json:"name"`
|
|
Namespace string `json:"namespace"`
|
|
CreatedAt int64 `json:"createdAt,omitempty"`
|
|
DesiredReplicas int32 `json:"desiredReplicas,omitempty"`
|
|
UpdatedReplicas int32 `json:"updatedReplicas,omitempty"`
|
|
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
|
|
AvailableReplicas int32 `json:"availableReplicas,omitempty"`
|
|
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
|
Labels map[string]string `json:"labels"`
|
|
}
|
|
|
|
func (d KubernetesDeploymentFrontend) NormalizeCollections() KubernetesDeploymentFrontend {
|
|
if d.Labels == nil {
|
|
d.Labels = map[string]string{}
|
|
}
|
|
return d
|
|
}
|
|
|
|
// DockerContainerFrontend represents a Docker container for the frontend
|
|
type DockerContainerFrontend struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Image string `json:"image"`
|
|
State string `json:"state"`
|
|
Status string `json:"status"`
|
|
Health string `json:"health,omitempty"`
|
|
CPUPercent float64 `json:"cpuPercent"`
|
|
CPUCapacityPercent float64 `json:"cpuCapacityPercent,omitempty"`
|
|
MemoryUsage int64 `json:"memoryUsageBytes"`
|
|
MemoryLimit int64 `json:"memoryLimitBytes"`
|
|
MemoryPercent float64 `json:"memoryPercent"`
|
|
UptimeSeconds int64 `json:"uptimeSeconds"`
|
|
RestartCount int `json:"restartCount"`
|
|
ExitCode int `json:"exitCode"`
|
|
OOMKilled *bool `json:"oomKilled,omitempty"`
|
|
CreatedAt int64 `json:"createdAt"`
|
|
StartedAt *int64 `json:"startedAt,omitempty"`
|
|
FinishedAt *int64 `json:"finishedAt,omitempty"`
|
|
Ports []DockerContainerPortFrontend `json:"ports"`
|
|
Labels map[string]string `json:"labels"`
|
|
Networks []DockerContainerNetworkFrontend `json:"networks"`
|
|
WritableLayerBytes int64 `json:"writableLayerBytes,omitempty"`
|
|
RootFilesystemBytes int64 `json:"rootFilesystemBytes,omitempty"`
|
|
BlockIO *DockerContainerBlockIOFrontend `json:"blockIo,omitempty"`
|
|
Mounts []DockerContainerMountFrontend `json:"mounts"`
|
|
Filesystems []filesystem.Observation `json:"filesystems,omitempty"`
|
|
Podman *DockerPodmanContainerFrontend `json:"podman,omitempty"`
|
|
UpdateStatus *DockerContainerUpdateStatusFrontend `json:"updateStatus,omitempty"`
|
|
}
|
|
|
|
func (c DockerContainerFrontend) NormalizeCollections() DockerContainerFrontend {
|
|
if c.Ports == nil {
|
|
c.Ports = []DockerContainerPortFrontend{}
|
|
}
|
|
if c.Labels == nil {
|
|
c.Labels = map[string]string{}
|
|
}
|
|
if c.Networks == nil {
|
|
c.Networks = []DockerContainerNetworkFrontend{}
|
|
}
|
|
if c.Mounts == nil {
|
|
c.Mounts = []DockerContainerMountFrontend{}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// DockerContainerUpdateStatusFrontend tracks the image update status for a container.
|
|
type DockerContainerUpdateStatusFrontend struct {
|
|
UpdateAvailable bool `json:"updateAvailable"`
|
|
CurrentDigest string `json:"currentDigest,omitempty"`
|
|
LatestDigest string `json:"latestDigest,omitempty"`
|
|
LastChecked int64 `json:"lastChecked"`
|
|
Error string `json:"error,omitempty"` // e.g., "rate limited", "auth required"
|
|
}
|
|
|
|
// DockerContainerPortFrontend represents a container port mapping
|
|
type DockerContainerPortFrontend struct {
|
|
PrivatePort int `json:"privatePort"`
|
|
PublicPort int `json:"publicPort,omitempty"`
|
|
Protocol string `json:"protocol"`
|
|
IP string `json:"ip,omitempty"`
|
|
}
|
|
|
|
// DockerContainerNetworkFrontend represents container network addresses
|
|
type DockerContainerNetworkFrontend struct {
|
|
Name string `json:"name"`
|
|
IPv4 string `json:"ipv4,omitempty"`
|
|
IPv6 string `json:"ipv6,omitempty"`
|
|
}
|
|
|
|
// DockerContainerBlockIOFrontend exposes aggregate block IO counters.
|
|
type DockerContainerBlockIOFrontend struct {
|
|
ReadBytes uint64 `json:"readBytes,omitempty"`
|
|
WriteBytes uint64 `json:"writeBytes,omitempty"`
|
|
ReadRateBytesPerSecond *float64 `json:"readRateBytesPerSecond,omitempty"`
|
|
WriteRateBytesPerSecond *float64 `json:"writeRateBytesPerSecond,omitempty"`
|
|
}
|
|
|
|
// DockerContainerMountFrontend represents a container mount for the UI.
|
|
type DockerContainerMountFrontend struct {
|
|
Type string `json:"type,omitempty"`
|
|
Source string `json:"source,omitempty"`
|
|
Destination string `json:"destination,omitempty"`
|
|
Mode string `json:"mode,omitempty"`
|
|
RW bool `json:"rw"`
|
|
Propagation string `json:"propagation,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Driver string `json:"driver,omitempty"`
|
|
}
|
|
|
|
// DockerPodmanContainerFrontend exposes podman-specific metadata.
|
|
type DockerPodmanContainerFrontend struct {
|
|
PodName string `json:"podName,omitempty"`
|
|
PodID string `json:"podId,omitempty"`
|
|
Infra bool `json:"infra,omitempty"`
|
|
ComposeProject string `json:"composeProject,omitempty"`
|
|
ComposeService string `json:"composeService,omitempty"`
|
|
ComposeWorkdir string `json:"composeWorkdir,omitempty"`
|
|
ComposeConfigHash string `json:"composeConfigHash,omitempty"`
|
|
AutoUpdatePolicy string `json:"autoUpdatePolicy,omitempty"`
|
|
AutoUpdateRestart string `json:"autoUpdateRestart,omitempty"`
|
|
UserNamespace string `json:"userNamespace,omitempty"`
|
|
}
|
|
|
|
// DockerServiceFrontend represents a Swarm service for the frontend.
|
|
type DockerServiceFrontend struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Stack string `json:"stack,omitempty"`
|
|
Image string `json:"image,omitempty"`
|
|
Mode string `json:"mode,omitempty"`
|
|
DesiredTasks int `json:"desiredTasks,omitempty"`
|
|
RunningTasks int `json:"runningTasks,omitempty"`
|
|
CompletedTasks int `json:"completedTasks,omitempty"`
|
|
Labels map[string]string `json:"labels"`
|
|
EndpointPorts []DockerServicePortFrontend `json:"endpointPorts"`
|
|
UpdateStatus *DockerServiceUpdateFrontend `json:"updateStatus,omitempty"`
|
|
CreatedAt *int64 `json:"createdAt,omitempty"`
|
|
UpdatedAt *int64 `json:"updatedAt,omitempty"`
|
|
}
|
|
|
|
func (s DockerServiceFrontend) NormalizeCollections() DockerServiceFrontend {
|
|
if s.Labels == nil {
|
|
s.Labels = map[string]string{}
|
|
}
|
|
if s.EndpointPorts == nil {
|
|
s.EndpointPorts = []DockerServicePortFrontend{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// DockerServicePortFrontend represents a published service port.
|
|
type DockerServicePortFrontend struct {
|
|
Name string `json:"name,omitempty"`
|
|
Protocol string `json:"protocol,omitempty"`
|
|
TargetPort uint32 `json:"targetPort,omitempty"`
|
|
PublishedPort uint32 `json:"publishedPort,omitempty"`
|
|
PublishMode string `json:"publishMode,omitempty"`
|
|
}
|
|
|
|
// DockerServiceUpdateFrontend exposes service update status to the UI.
|
|
type DockerServiceUpdateFrontend struct {
|
|
State string `json:"state,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
CompletedAt *int64 `json:"completedAt,omitempty"`
|
|
}
|
|
|
|
// DockerTaskFrontend represents a Swarm task replica.
|
|
type DockerTaskFrontend struct {
|
|
ID string `json:"id"`
|
|
ServiceID string `json:"serviceId,omitempty"`
|
|
ServiceName string `json:"serviceName,omitempty"`
|
|
Slot int `json:"slot,omitempty"`
|
|
NodeID string `json:"nodeId,omitempty"`
|
|
NodeName string `json:"nodeName,omitempty"`
|
|
DesiredState string `json:"desiredState,omitempty"`
|
|
CurrentState string `json:"currentState,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
ContainerID string `json:"containerId,omitempty"`
|
|
ContainerName string `json:"containerName,omitempty"`
|
|
CreatedAt *int64 `json:"createdAt,omitempty"`
|
|
UpdatedAt *int64 `json:"updatedAt,omitempty"`
|
|
StartedAt *int64 `json:"startedAt,omitempty"`
|
|
CompletedAt *int64 `json:"completedAt,omitempty"`
|
|
}
|
|
|
|
// DockerNodeFrontend represents a Swarm node.
|
|
type DockerNodeFrontend struct {
|
|
ID string `json:"id"`
|
|
Hostname string `json:"hostname,omitempty"`
|
|
Role string `json:"role,omitempty"`
|
|
Availability string `json:"availability,omitempty"`
|
|
State string `json:"state,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
Address string `json:"address,omitempty"`
|
|
ManagerReachability string `json:"managerReachability,omitempty"`
|
|
ManagerAddress string `json:"managerAddress,omitempty"`
|
|
Leader bool `json:"leader,omitempty"`
|
|
EngineVersion string `json:"engineVersion,omitempty"`
|
|
OS string `json:"os,omitempty"`
|
|
Architecture string `json:"architecture,omitempty"`
|
|
NanoCPUs int64 `json:"nanoCpus,omitempty"`
|
|
MemoryBytes int64 `json:"memoryBytes,omitempty"`
|
|
Labels map[string]string `json:"labels,omitempty"`
|
|
EngineLabels map[string]string `json:"engineLabels,omitempty"`
|
|
CreatedAt *int64 `json:"createdAt,omitempty"`
|
|
UpdatedAt *int64 `json:"updatedAt,omitempty"`
|
|
}
|
|
|
|
// DockerSecretFrontend represents Docker Swarm secret metadata for the frontend.
|
|
type DockerSecretFrontend struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Labels map[string]string `json:"labels"`
|
|
DriverName string `json:"driverName,omitempty"`
|
|
TemplatingDriver string `json:"templatingDriver,omitempty"`
|
|
CreatedAt *int64 `json:"createdAt,omitempty"`
|
|
UpdatedAt *int64 `json:"updatedAt,omitempty"`
|
|
}
|
|
|
|
func (s DockerSecretFrontend) NormalizeCollections() DockerSecretFrontend {
|
|
if s.Labels == nil {
|
|
s.Labels = map[string]string{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// DockerConfigFrontend represents Docker Swarm config metadata for the frontend.
|
|
type DockerConfigFrontend struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Labels map[string]string `json:"labels"`
|
|
TemplatingDriver string `json:"templatingDriver,omitempty"`
|
|
CreatedAt *int64 `json:"createdAt,omitempty"`
|
|
UpdatedAt *int64 `json:"updatedAt,omitempty"`
|
|
}
|
|
|
|
func (c DockerConfigFrontend) NormalizeCollections() DockerConfigFrontend {
|
|
if c.Labels == nil {
|
|
c.Labels = map[string]string{}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// DockerSwarmFrontend summarises node-level swarm details.
|
|
type DockerSwarmFrontend struct {
|
|
NodeID string `json:"nodeId,omitempty"`
|
|
NodeRole string `json:"nodeRole,omitempty"`
|
|
LocalState string `json:"localState,omitempty"`
|
|
ControlAvailable bool `json:"controlAvailable,omitempty"`
|
|
ClusterID string `json:"clusterId,omitempty"`
|
|
ClusterName string `json:"clusterName,omitempty"`
|
|
Scope string `json:"scope,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// DockerHostCommandFrontend exposes docker host command state to the UI.
|
|
type DockerHostCommandFrontend struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Message string `json:"message,omitempty"`
|
|
CreatedAt int64 `json:"createdAt"`
|
|
UpdatedAt int64 `json:"updatedAt"`
|
|
DispatchedAt *int64 `json:"dispatchedAt,omitempty"`
|
|
AcknowledgedAt *int64 `json:"acknowledgedAt,omitempty"`
|
|
CompletedAt *int64 `json:"completedAt,omitempty"`
|
|
FailedAt *int64 `json:"failedAt,omitempty"`
|
|
FailureReason string `json:"failureReason,omitempty"`
|
|
ExpiresAt *int64 `json:"expiresAt,omitempty"`
|
|
}
|
|
|
|
// HostFrontend represents a generic infrastructure host exposed to the UI.
|
|
type HostFrontend struct {
|
|
ID string `json:"id"`
|
|
Hostname string `json:"hostname"`
|
|
DisplayName string `json:"displayName"`
|
|
Platform string `json:"platform,omitempty"`
|
|
OSName string `json:"osName,omitempty"`
|
|
OSVersion string `json:"osVersion,omitempty"`
|
|
KernelVersion string `json:"kernelVersion,omitempty"`
|
|
Architecture string `json:"architecture,omitempty"`
|
|
CPUCount int `json:"cpuCount,omitempty"`
|
|
CPUUsage float64 `json:"cpuUsage,omitempty"`
|
|
LoadAverage []float64 `json:"loadAverage"`
|
|
Memory *Memory `json:"memory,omitempty"`
|
|
Disks []Disk `json:"disks"`
|
|
DiskIO []DiskIO `json:"diskIO"`
|
|
NetworkInterfaces []HostNetworkInterface `json:"networkInterfaces"`
|
|
Sensors *HostSensorSummaryFrontend `json:"sensors,omitempty"`
|
|
Status string `json:"status"`
|
|
UptimeSeconds int64 `json:"uptimeSeconds,omitempty"`
|
|
LastSeen int64 `json:"lastSeen"`
|
|
IntervalSeconds int `json:"intervalSeconds,omitempty"`
|
|
AgentVersion string `json:"agentVersion,omitempty"`
|
|
MachineID string `json:"machineId,omitempty"`
|
|
TokenID string `json:"tokenId,omitempty"`
|
|
TokenName string `json:"tokenName,omitempty"`
|
|
TokenHint string `json:"tokenHint,omitempty"`
|
|
TokenLastUsedAt *int64 `json:"tokenLastUsedAt,omitempty"`
|
|
Tags []string `json:"tags"`
|
|
CommandsEnabled bool `json:"commandsEnabled,omitempty"` // Whether AI command execution is enabled
|
|
AppliedConfig *AgentConfigFingerprint `json:"appliedConfig,omitempty"`
|
|
AgentUpdate *AgentUpdateStatus `json:"agentUpdate,omitempty"`
|
|
AgentModules []AgentModuleStatus `json:"agentModules,omitempty"`
|
|
AgentPrivilege *AgentPrivilegeStatus `json:"agentPrivilege,omitempty"`
|
|
PackageUpdates *HostPackageUpdateStatus `json:"packageUpdates,omitempty"`
|
|
StorageCleanup *HostStorageCleanupStatus `json:"storageCleanup,omitempty"`
|
|
IsLegacy bool `json:"isLegacy,omitempty"` // True if using legacy agent protocol
|
|
LinkedNodeID string `json:"linkedNodeId,omitempty"` // ID of linked PVE node (if running on a node)
|
|
}
|
|
|
|
func (h HostFrontend) NormalizeCollections() HostFrontend {
|
|
if h.LoadAverage == nil {
|
|
h.LoadAverage = []float64{}
|
|
}
|
|
if h.Disks == nil {
|
|
h.Disks = []Disk{}
|
|
}
|
|
if h.DiskIO == nil {
|
|
h.DiskIO = []DiskIO{}
|
|
}
|
|
if h.NetworkInterfaces == nil {
|
|
h.NetworkInterfaces = []HostNetworkInterface{}
|
|
}
|
|
if h.Tags == nil {
|
|
h.Tags = []string{}
|
|
}
|
|
if h.Sensors != nil {
|
|
sensors := h.Sensors.NormalizeCollections()
|
|
h.Sensors = &sensors
|
|
}
|
|
return h
|
|
}
|
|
|
|
// HostSensorSummaryFrontend mirrors HostSensorSummary with primitives for the frontend.
|
|
type HostSensorSummaryFrontend struct {
|
|
TemperatureCelsius map[string]float64 `json:"temperatureCelsius"`
|
|
FanRPM map[string]float64 `json:"fanRpm"`
|
|
PowerWatts map[string]float64 `json:"powerWatts"`
|
|
Additional map[string]float64 `json:"additional"`
|
|
Custom []HostCustomSensorMetricFrontend `json:"custom"`
|
|
GPU []HostGPUSensorFrontend `json:"gpu"`
|
|
ThermalState *HostThermalState `json:"thermalState,omitempty"`
|
|
SMART []HostDiskSMARTFrontend `json:"smart"` // S.M.A.R.T. disk data
|
|
}
|
|
|
|
type HostCustomSensorMetricFrontend struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Group string `json:"group,omitempty"`
|
|
Subgroup string `json:"subgroup,omitempty"`
|
|
Kind string `json:"kind,omitempty"`
|
|
Unit string `json:"unit,omitempty"`
|
|
Value *float64 `json:"value,omitempty"`
|
|
Status string `json:"status"`
|
|
ObservedAt time.Time `json:"observedAt"`
|
|
EventAt *time.Time `json:"eventAt,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
AlertOnError bool `json:"alertOnError,omitempty"`
|
|
Stale bool `json:"stale,omitempty"`
|
|
}
|
|
|
|
type HostGPUSensorFrontend struct {
|
|
ID string `json:"id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
TemperatureCelsius *float64 `json:"temperatureCelsius,omitempty"`
|
|
UtilizationPercent *float64 `json:"utilizationPercent,omitempty"`
|
|
MemoryUsedBytes *int64 `json:"memoryUsedBytes,omitempty"`
|
|
MemoryTotalBytes *int64 `json:"memoryTotalBytes,omitempty"`
|
|
}
|
|
|
|
func (s HostSensorSummaryFrontend) NormalizeCollections() HostSensorSummaryFrontend {
|
|
if s.TemperatureCelsius == nil {
|
|
s.TemperatureCelsius = map[string]float64{}
|
|
}
|
|
if s.FanRPM == nil {
|
|
s.FanRPM = map[string]float64{}
|
|
}
|
|
if s.PowerWatts == nil {
|
|
s.PowerWatts = map[string]float64{}
|
|
}
|
|
if s.Additional == nil {
|
|
s.Additional = map[string]float64{}
|
|
}
|
|
if s.Custom == nil {
|
|
s.Custom = []HostCustomSensorMetricFrontend{}
|
|
}
|
|
if s.GPU == nil {
|
|
s.GPU = []HostGPUSensorFrontend{}
|
|
}
|
|
if s.SMART == nil {
|
|
s.SMART = []HostDiskSMARTFrontend{}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// HostDiskSMARTFrontend represents S.M.A.R.T. data for a disk from a host agent.
|
|
type HostDiskSMARTFrontend struct {
|
|
Device string `json:"device"` // Device name (e.g., sda)
|
|
Model string `json:"model,omitempty"` // Disk model
|
|
Serial string `json:"serial,omitempty"` // Serial number
|
|
WWN string `json:"wwn,omitempty"` // World Wide Name
|
|
Type string `json:"type,omitempty"` // Transport type: sata, sas, nvme
|
|
Controller string `json:"controller,omitempty"` // Controller association
|
|
Target string `json:"target,omitempty"` // Controller target/HCTL
|
|
SizeBytes int64 `json:"sizeBytes,omitempty"`
|
|
Temperature int `json:"temperature"` // Temperature in Celsius
|
|
Health string `json:"health,omitempty"` // PASSED, FAILED, UNKNOWN
|
|
Standby bool `json:"standby,omitempty"` // True if disk was in standby
|
|
IO *DiskIO `json:"io,omitempty"`
|
|
Collection *diskinventory.CollectionStatus `json:"collection,omitempty"`
|
|
Attributes *SMARTAttributes `json:"attributes,omitempty"`
|
|
}
|
|
|
|
// StorageFrontend represents Storage with frontend-friendly field names
|
|
type StorageFrontend struct {
|
|
ID string `json:"id"`
|
|
Storage string `json:"storage"` // Maps to Name
|
|
Name string `json:"name"`
|
|
Node string `json:"node"`
|
|
Instance string `json:"instance"`
|
|
Nodes []string `json:"nodes"`
|
|
NodeIDs []string `json:"nodeIds"`
|
|
NodeCount int `json:"nodeCount,omitempty"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
Pool string `json:"pool,omitempty"`
|
|
Total int64 `json:"total"`
|
|
Used int64 `json:"used"`
|
|
Avail int64 `json:"avail"` // Maps to Free
|
|
Free int64 `json:"free"`
|
|
Usage float64 `json:"usage"`
|
|
Content string `json:"content"`
|
|
Shared bool `json:"shared"`
|
|
Enabled bool `json:"enabled"`
|
|
Active bool `json:"active"`
|
|
ZFSPool *ZFSPool `json:"zfsPool,omitempty"`
|
|
}
|
|
|
|
func (s StorageFrontend) NormalizeCollections() StorageFrontend {
|
|
if s.Nodes == nil {
|
|
s.Nodes = []string{}
|
|
}
|
|
if s.NodeIDs == nil {
|
|
s.NodeIDs = []string{}
|
|
}
|
|
if s.ZFSPool != nil {
|
|
pool := s.ZFSPool.NormalizeCollections()
|
|
s.ZFSPool = &pool
|
|
}
|
|
return s
|
|
}
|
|
|
|
// CephClusterFrontend represents a Ceph cluster with frontend-friendly field names
|
|
type CephClusterFrontend struct {
|
|
ID string `json:"id"`
|
|
Instance string `json:"instance"`
|
|
Name string `json:"name"`
|
|
FSID string `json:"fsid,omitempty"`
|
|
Health string `json:"health"`
|
|
HealthMessage string `json:"healthMessage,omitempty"`
|
|
TotalBytes int64 `json:"totalBytes"`
|
|
UsedBytes int64 `json:"usedBytes"`
|
|
AvailableBytes int64 `json:"availableBytes"`
|
|
UsagePercent float64 `json:"usagePercent"`
|
|
NumMons int `json:"numMons"`
|
|
NumMgrs int `json:"numMgrs"`
|
|
NumOSDs int `json:"numOsds"`
|
|
NumOSDsUp int `json:"numOsdsUp"`
|
|
NumOSDsIn int `json:"numOsdsIn"`
|
|
NumPGs int `json:"numPGs"`
|
|
Pools []CephPool `json:"pools"`
|
|
Services []CephServiceStatus `json:"services"`
|
|
LastUpdated int64 `json:"lastUpdated"`
|
|
}
|
|
|
|
func (c CephClusterFrontend) NormalizeCollections() CephClusterFrontend {
|
|
if c.Pools == nil {
|
|
c.Pools = []CephPool{}
|
|
}
|
|
if c.Services == nil {
|
|
c.Services = []CephServiceStatus{}
|
|
}
|
|
return c
|
|
}
|
|
|
|
// ReplicationJobFrontend represents a replication job for the frontend.
|
|
type ReplicationJobFrontend struct {
|
|
ID string `json:"id"`
|
|
Instance string `json:"instance"`
|
|
JobID string `json:"jobId"`
|
|
JobNumber int `json:"jobNumber,omitempty"`
|
|
Guest string `json:"guest,omitempty"`
|
|
GuestID int `json:"guestId,omitempty"`
|
|
GuestName string `json:"guestName,omitempty"`
|
|
GuestType string `json:"guestType,omitempty"`
|
|
GuestNode string `json:"guestNode,omitempty"`
|
|
SourceNode string `json:"sourceNode,omitempty"`
|
|
SourceStorage string `json:"sourceStorage,omitempty"`
|
|
TargetNode string `json:"targetNode,omitempty"`
|
|
TargetStorage string `json:"targetStorage,omitempty"`
|
|
Schedule string `json:"schedule,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Enabled bool `json:"enabled"`
|
|
State string `json:"state,omitempty"`
|
|
Status string `json:"status,omitempty"`
|
|
LastSyncStatus string `json:"lastSyncStatus,omitempty"`
|
|
LastSyncTime int64 `json:"lastSyncTime,omitempty"`
|
|
LastSyncUnix int64 `json:"lastSyncUnix,omitempty"`
|
|
LastSyncDurationSeconds int `json:"lastSyncDurationSeconds,omitempty"`
|
|
LastSyncDurationHuman string `json:"lastSyncDurationHuman,omitempty"`
|
|
NextSyncTime int64 `json:"nextSyncTime,omitempty"`
|
|
NextSyncUnix int64 `json:"nextSyncUnix,omitempty"`
|
|
DurationSeconds int `json:"durationSeconds,omitempty"`
|
|
DurationHuman string `json:"durationHuman,omitempty"`
|
|
FailCount int `json:"failCount,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Comment string `json:"comment,omitempty"`
|
|
RemoveJob string `json:"removeJob,omitempty"`
|
|
RateLimitMbps *float64 `json:"rateLimitMbps,omitempty"`
|
|
PolledAt int64 `json:"polledAt,omitempty"`
|
|
}
|
|
|
|
// StateFrontend represents the state with frontend-friendly field names
|
|
type StateFrontend struct {
|
|
ActiveAlerts []Alert `json:"activeAlerts"` // Active alerts
|
|
RecentlyResolved []ResolvedAlert `json:"recentlyResolved"` // Recently resolved alerts
|
|
Metrics []Metric `json:"metrics"` // Time-series metrics
|
|
Performance Performance `json:"performance"` // Polling/runtime performance
|
|
ConnectionHealth map[string]bool `json:"connectionHealth"` // Keep as is
|
|
Stats Stats `json:"stats"` // Runtime statistics
|
|
LastUpdate int64 `json:"lastUpdate"` // Unix timestamp
|
|
TemperatureMonitoringEnabled bool `json:"temperatureMonitoringEnabled"` // Global temperature monitoring setting
|
|
PVETagColors map[string]string `json:"pveTagColors"` // Tag name -> "#rrggbb" from Proxmox datacenter config
|
|
PVETagStyles map[string]PVETagStyle `json:"pveTagStyles"` // PVE instance -> Proxmox tag style
|
|
// Unified resources - the new way to access all monitored entities
|
|
Resources []ResourceFrontend `json:"resources"`
|
|
ConnectedInfrastructure []ConnectedInfrastructureItemFrontend `json:"connectedInfrastructure"`
|
|
// CapabilityCatalog maps content-hash ids to capability blobs referenced by
|
|
// ResourceFrontend.CapabilitiesRef. Estates carry only a handful of
|
|
// distinct capability sets, so the catalog replaces per-resource inline
|
|
// duplication in broadcast payloads. The websocket delta engine diffs
|
|
// top-level state fields generically, so catalog changes ride the same
|
|
// frame as the resource refs that need them.
|
|
CapabilityCatalog map[string]json.RawMessage `json:"capabilityCatalog,omitempty"`
|
|
// PolicyCatalog and AISafeSummaryCatalog dedupe the equally small sets of
|
|
// distinct non-default policy postures and templated AI-safe summaries the
|
|
// same way, referenced by ResourceFrontend.PolicyRef and
|
|
// ResourceFrontend.AISafeSummaryRef.
|
|
PolicyCatalog map[string]json.RawMessage `json:"policyCatalog,omitempty"`
|
|
AISafeSummaryCatalog map[string]string `json:"aiSafeSummaryCatalog,omitempty"`
|
|
}
|
|
|
|
// EmptyStateFrontend returns a canonical empty frontend state with stable
|
|
// collection/object semantics for API and websocket consumers.
|
|
func EmptyStateFrontend() StateFrontend {
|
|
return StateFrontend{}.NormalizeCollections()
|
|
}
|
|
|
|
// NormalizeCollections ensures StateFrontend preserves stable empty collection
|
|
// semantics instead of leaking nil slices or maps to clients.
|
|
func (s StateFrontend) NormalizeCollections() StateFrontend {
|
|
if s.ActiveAlerts == nil {
|
|
s.ActiveAlerts = []Alert{}
|
|
}
|
|
if s.RecentlyResolved == nil {
|
|
s.RecentlyResolved = []ResolvedAlert{}
|
|
}
|
|
if s.Metrics == nil {
|
|
s.Metrics = []Metric{}
|
|
}
|
|
if s.ConnectionHealth == nil {
|
|
s.ConnectionHealth = map[string]bool{}
|
|
}
|
|
if s.PVETagColors == nil {
|
|
s.PVETagColors = map[string]string{}
|
|
}
|
|
if s.PVETagStyles == nil {
|
|
s.PVETagStyles = map[string]PVETagStyle{}
|
|
}
|
|
if s.Performance.APICallDuration == nil {
|
|
s.Performance.APICallDuration = map[string]float64{}
|
|
}
|
|
if s.Resources == nil {
|
|
s.Resources = []ResourceFrontend{}
|
|
}
|
|
if s.ConnectedInfrastructure == nil {
|
|
s.ConnectedInfrastructure = []ConnectedInfrastructureItemFrontend{}
|
|
}
|
|
for i := range s.ConnectedInfrastructure {
|
|
s.ConnectedInfrastructure[i] = s.ConnectedInfrastructure[i].NormalizeCollections()
|
|
}
|
|
for i := range s.Resources {
|
|
s.Resources[i] = s.Resources[i].NormalizeCollections()
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ResourceFrontend is the frontend representation of a unified Resource.
|
|
// This mirrors resources.Resource but with time.Time converted to Unix milliseconds.
|
|
type ResourceFrontend struct {
|
|
// Identity
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Technology string `json:"technology,omitempty"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
|
|
// Platform/Source
|
|
PlatformID string `json:"platformId"`
|
|
PlatformType string `json:"platformType"`
|
|
SourceType string `json:"sourceType"`
|
|
Sources []string `json:"sources,omitempty"`
|
|
|
|
// Hierarchy
|
|
ParentID string `json:"parentId,omitempty"`
|
|
ParentName string `json:"parentName,omitempty"`
|
|
ChildCount int `json:"childCount,omitempty"`
|
|
ClusterID string `json:"clusterId,omitempty"`
|
|
|
|
// Universal Metrics
|
|
Status string `json:"status"`
|
|
CPU *ResourceMetricFrontend `json:"cpu,omitempty"`
|
|
Memory *ResourceMetricFrontend `json:"memory,omitempty"`
|
|
Disk *ResourceMetricFrontend `json:"disk,omitempty"`
|
|
Network *ResourceNetworkFrontend `json:"network,omitempty"`
|
|
DiskIO *ResourceDiskIOFrontend `json:"diskIO,omitempty"`
|
|
Temperature *float64 `json:"temperature,omitempty"`
|
|
Uptime *int64 `json:"uptime,omitempty"`
|
|
|
|
// Metadata
|
|
Tags []string `json:"tags"`
|
|
Labels map[string]string `json:"labels"`
|
|
CustomURL string `json:"customUrl,omitempty"`
|
|
LastSeen int64 `json:"lastSeen"` // Unix milliseconds
|
|
Health json.RawMessage `json:"health,omitempty"`
|
|
Alerts []ResourceAlertFrontend `json:"alerts"`
|
|
|
|
IncidentCount int `json:"incidentCount,omitempty"`
|
|
IncidentCode string `json:"incidentCode,omitempty"`
|
|
IncidentSeverity string `json:"incidentSeverity,omitempty"`
|
|
IncidentSummary string `json:"incidentSummary,omitempty"`
|
|
IncidentCategory string `json:"incidentCategory,omitempty"`
|
|
IncidentLabel string `json:"incidentLabel,omitempty"`
|
|
IncidentPriority int `json:"incidentPriority,omitempty"`
|
|
IncidentImpactSummary string `json:"incidentImpactSummary,omitempty"`
|
|
IncidentUrgency string `json:"incidentUrgency,omitempty"`
|
|
IncidentAction string `json:"incidentAction,omitempty"`
|
|
|
|
// Identity for deduplication
|
|
Identity *ResourceIdentityFrontend `json:"identity,omitempty"`
|
|
|
|
DiscoveryTarget json.RawMessage `json:"discoveryTarget,omitempty"`
|
|
MetricsTarget json.RawMessage `json:"metricsTarget,omitempty"`
|
|
Canonical json.RawMessage `json:"canonicalIdentity,omitempty"`
|
|
Policy json.RawMessage `json:"policy,omitempty"`
|
|
AISafeSummary string `json:"aiSafeSummary,omitempty"`
|
|
Capabilities json.RawMessage `json:"capabilities,omitempty"`
|
|
// CapabilitiesRef references an entry in StateFrontend.CapabilityCatalog.
|
|
// Broadcast payloads dedupe the small set of distinct capability blobs
|
|
// through the catalog instead of inlining ~3KB per resource; consumers
|
|
// resolve the ref at ingestion. Inline Capabilities remains valid for
|
|
// producers that do not carry a catalog.
|
|
CapabilitiesRef string `json:"capabilitiesRef,omitempty"`
|
|
// PolicyRef and AISafeSummaryRef reference entries in
|
|
// StateFrontend.PolicyCatalog / AISafeSummaryCatalog under the same
|
|
// dedupe contract; inline Policy / AISafeSummary remain valid for
|
|
// producers that do not carry catalogs.
|
|
PolicyRef string `json:"policyRef,omitempty"`
|
|
AISafeSummaryRef string `json:"aiSafeSummaryRef,omitempty"`
|
|
Relationships json.RawMessage `json:"relationships,omitempty"`
|
|
RecentChanges json.RawMessage `json:"recentChanges,omitempty"`
|
|
FacetCounts json.RawMessage `json:"facetCounts,omitempty"`
|
|
Incidents json.RawMessage `json:"incidents,omitempty"`
|
|
Proxmox json.RawMessage `json:"proxmox,omitempty"`
|
|
Storage json.RawMessage `json:"storage,omitempty"`
|
|
Agent json.RawMessage `json:"agent,omitempty"`
|
|
Docker json.RawMessage `json:"docker,omitempty"`
|
|
PBS json.RawMessage `json:"pbs,omitempty"`
|
|
PMG json.RawMessage `json:"pmg,omitempty"`
|
|
Kubernetes json.RawMessage `json:"kubernetes,omitempty"`
|
|
PhysicalDisk json.RawMessage `json:"physicalDisk,omitempty"`
|
|
Ceph json.RawMessage `json:"ceph,omitempty"`
|
|
TrueNAS json.RawMessage `json:"truenas,omitempty"`
|
|
VMware json.RawMessage `json:"vmware,omitempty"`
|
|
Availability json.RawMessage `json:"availability,omitempty"`
|
|
AvailabilityChecks json.RawMessage `json:"availabilityChecks,omitempty"`
|
|
|
|
// Platform-specific data (JSON blob)
|
|
PlatformData json.RawMessage `json:"platformData,omitempty"`
|
|
}
|
|
|
|
func (r ResourceFrontend) NormalizeCollections() ResourceFrontend {
|
|
if r.Tags == nil {
|
|
r.Tags = []string{}
|
|
}
|
|
if r.Labels == nil {
|
|
r.Labels = map[string]string{}
|
|
}
|
|
if r.Alerts == nil {
|
|
r.Alerts = []ResourceAlertFrontend{}
|
|
}
|
|
if r.Identity != nil {
|
|
identity := r.Identity.NormalizeCollections()
|
|
r.Identity = &identity
|
|
}
|
|
return r
|
|
}
|
|
|
|
// ResourceMetricFrontend represents a metric value for the frontend.
|
|
type ResourceMetricFrontend struct {
|
|
Current float64 `json:"current"`
|
|
Total *int64 `json:"total,omitempty"`
|
|
Used *int64 `json:"used,omitempty"`
|
|
Free *int64 `json:"free,omitempty"`
|
|
}
|
|
|
|
// ResourceNetworkFrontend represents network metrics for the frontend.
|
|
type ResourceNetworkFrontend struct {
|
|
RXBytes int64 `json:"rxBytes"`
|
|
TXBytes int64 `json:"txBytes"`
|
|
}
|
|
|
|
// ResourceDiskIOFrontend represents aggregate disk I/O rates for the frontend.
|
|
type ResourceDiskIOFrontend struct {
|
|
ReadRate *int64 `json:"readRate,omitempty"`
|
|
WriteRate *int64 `json:"writeRate,omitempty"`
|
|
}
|
|
|
|
// ResourceAlertFrontend represents an alert on a resource.
|
|
type ResourceAlertFrontend struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Level string `json:"level"`
|
|
Message string `json:"message"`
|
|
Value float64 `json:"value"`
|
|
Threshold float64 `json:"threshold"`
|
|
StartTime int64 `json:"startTime"` // Unix milliseconds
|
|
}
|
|
|
|
// ResourceIdentityFrontend contains identity info for deduplication.
|
|
type ResourceIdentityFrontend struct {
|
|
Hostname string `json:"hostname,omitempty"`
|
|
MachineID string `json:"machineId,omitempty"`
|
|
IPs []string `json:"ips"`
|
|
}
|
|
|
|
func (i ResourceIdentityFrontend) NormalizeCollections() ResourceIdentityFrontend {
|
|
if i.IPs == nil {
|
|
i.IPs = []string{}
|
|
}
|
|
return i
|
|
}
|