From c9915ead8b8c90281d385c842398182159ee18c2 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 5 Dec 2025 12:43:32 +0000 Subject: [PATCH] feat: Add OS type display for LXC containers - Extract ostype from LXC container config (debian, ubuntu, alpine, etc.) - Map ostype values to human-readable names (e.g., "debian" -> "Debian") - Add OSName field to Container model and ContainerFrontend - Add icons for NixOS, openSUSE, and Gentoo in frontend - LXC containers now show OS icons alongside VMs in the dashboard Supported LXC OS types: alpine, archlinux, centos, debian, devuan, fedora, gentoo, nixos, opensuse, ubuntu, unmanaged --- .../src/components/Dashboard/GuestRow.tsx | 34 +++++++++++++- internal/models/converters.go | 4 ++ internal/models/models.go | 1 + internal/models/models_frontend.go | 1 + internal/monitoring/container_parsing.go | 45 +++++++++++++++++++ internal/monitoring/monitor.go | 4 ++ 6 files changed, 87 insertions(+), 2 deletions(-) diff --git a/frontend-modern/src/components/Dashboard/GuestRow.tsx b/frontend-modern/src/components/Dashboard/GuestRow.tsx index fb66b87..44e17b4 100644 --- a/frontend-modern/src/components/Dashboard/GuestRow.tsx +++ b/frontend-modern/src/components/Dashboard/GuestRow.tsx @@ -206,17 +206,20 @@ function NetworkInfoCell(props: { ipAddresses: string[]; networkInterfaces: Netw } // OS detection helper - returns icon type and color based on OS name -type OSType = 'windows' | 'ubuntu' | 'debian' | 'alpine' | 'centos' | 'fedora' | 'arch' | 'linux' | 'freebsd' | 'unknown'; +type OSType = 'windows' | 'ubuntu' | 'debian' | 'alpine' | 'centos' | 'fedora' | 'arch' | 'nixos' | 'opensuse' | 'gentoo' | 'linux' | 'freebsd' | 'unknown'; function detectOSType(osName: string): OSType { const lower = osName.toLowerCase(); if (lower.includes('windows')) return 'windows'; if (lower.includes('ubuntu')) return 'ubuntu'; - if (lower.includes('debian')) return 'debian'; + if (lower.includes('debian') || lower.includes('devuan')) return 'debian'; if (lower.includes('alpine')) return 'alpine'; if (lower.includes('centos') || lower.includes('rocky') || lower.includes('alma') || lower.includes('rhel') || lower.includes('red hat')) return 'centos'; if (lower.includes('fedora')) return 'fedora'; if (lower.includes('arch')) return 'arch'; + if (lower.includes('nixos')) return 'nixos'; + if (lower.includes('opensuse') || lower.includes('suse')) return 'opensuse'; + if (lower.includes('gentoo')) return 'gentoo'; if (lower.includes('freebsd') || lower.includes('openbsd') || lower.includes('netbsd')) return 'freebsd'; if (lower.includes('linux') || lower.includes('gnu')) return 'linux'; return 'unknown'; @@ -230,6 +233,9 @@ const OS_COLORS: Record = { centos: 'text-purple-500', fedora: 'text-blue-600', arch: 'text-cyan-500', + nixos: 'text-sky-400', + opensuse: 'text-green-500', + gentoo: 'text-violet-400', linux: 'text-yellow-500', freebsd: 'text-red-600', unknown: 'text-gray-400', @@ -310,6 +316,30 @@ function OSInfoCell(props: { osName: string; osVersion: string; agentVersion: st ); + case 'nixos': + // Snowflake-like icon for NixOS + return ( + + + + + ); + case 'opensuse': + // Chameleon-inspired icon for openSUSE + return ( + + + + + + ); + case 'gentoo': + // G-like icon for Gentoo + return ( + + + + ); case 'freebsd': return ( diff --git a/internal/models/converters.go b/internal/models/converters.go index 5001378..5bf5334 100644 --- a/internal/models/converters.go +++ b/internal/models/converters.go @@ -191,6 +191,10 @@ func (c Container) ToFrontend() ContainerFrontend { copy(ct.NetworkInterfaces, c.NetworkInterfaces) } + if c.OSName != "" { + ct.OSName = c.OSName + } + return ct } diff --git a/internal/models/models.go b/internal/models/models.go index cb13780..2776982 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -145,6 +145,7 @@ type Container struct { LastSeen time.Time `json:"lastSeen"` IPAddresses []string `json:"ipAddresses,omitempty"` NetworkInterfaces []GuestNetworkInterface `json:"networkInterfaces,omitempty"` + OSName string `json:"osName,omitempty"` } // Host represents a generic infrastructure host reporting via external agents. diff --git a/internal/models/models_frontend.go b/internal/models/models_frontend.go index 0384845..9c6b5ed 100644 --- a/internal/models/models_frontend.go +++ b/internal/models/models_frontend.go @@ -94,6 +94,7 @@ type ContainerFrontend struct { Tags string `json:"tags,omitempty"` // Joined string Lock string `json:"lock,omitempty"` LastSeen int64 `json:"lastSeen"` // Unix timestamp + OSName string `json:"osName,omitempty"` } // DockerHostFrontend represents a Docker host with frontend-friendly fields diff --git a/internal/monitoring/container_parsing.go b/internal/monitoring/container_parsing.go index 82d15a1..c977de7 100644 --- a/internal/monitoring/container_parsing.go +++ b/internal/monitoring/container_parsing.go @@ -445,3 +445,48 @@ func extractContainerRootDeviceFromConfig(config map[string]interface{}) string device := strings.TrimSpace(parts[0]) return device } + +// lxcOSTypeDisplayNames maps Proxmox LXC ostype values to human-readable OS names. +// See: https://pve.proxmox.com/wiki/Manual:_pct.conf +var lxcOSTypeDisplayNames = map[string]string{ + "alpine": "Alpine Linux", + "archlinux": "Arch Linux", + "centos": "CentOS", + "debian": "Debian", + "devuan": "Devuan", + "fedora": "Fedora", + "gentoo": "Gentoo", + "nixos": "NixOS", + "opensuse": "openSUSE", + "ubuntu": "Ubuntu", + "unmanaged": "Unmanaged", +} + +// extractContainerOSType extracts and normalizes the ostype from container config. +// Returns a human-readable OS name (e.g., "Ubuntu", "Debian", "Alpine Linux"). +func extractContainerOSType(config map[string]interface{}) string { + if len(config) == 0 { + return "" + } + + raw, ok := config["ostype"] + if !ok { + return "" + } + + ostype := strings.TrimSpace(strings.ToLower(fmt.Sprint(raw))) + if ostype == "" { + return "" + } + + // Return display name if known, otherwise capitalize the ostype + if displayName, found := lxcOSTypeDisplayNames[ostype]; found { + return displayName + } + + // Fallback: capitalize first letter + if len(ostype) > 0 { + return strings.ToUpper(ostype[:1]) + ostype[1:] + } + return ostype +} diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index e8f992f..1f7ef48 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -2329,6 +2329,10 @@ func (m *Monitor) enrichContainerMetadata(ctx context.Context, client PVEClientI } mergeContainerNetworkInterface(&networkIfaces, detail) } + // Extract OS type from container config + if osName := extractContainerOSType(configData); osName != "" { + container.OSName = osName + } } if len(addressOrder) == 0 {