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
This commit is contained in:
rcourtman 2025-12-05 12:43:32 +00:00
parent 41acb4f2ce
commit c9915ead8b
6 changed files with 87 additions and 2 deletions

View file

@ -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<OSType, string> = {
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
<path d="M12 2l-9 18h4l5-10 5 10h4L12 2z"/>
</svg>
);
case 'nixos':
// Snowflake-like icon for NixOS
return (
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2v4m0 12v4M2 12h4m12 0h4M5.64 5.64l2.83 2.83m7.07 7.07l2.82 2.82M5.64 18.36l2.83-2.83m7.07-7.07l2.82-2.82" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<circle cx="12" cy="12" r="3" fill="currentColor"/>
</svg>
);
case 'opensuse':
// Chameleon-inspired icon for openSUSE
return (
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2"/>
<path d="M8 12c0-2.21 1.79-4 4-4s4 1.79 4 4-1.79 4-4 4-4-1.79-4-4z" fill="currentColor"/>
<circle cx="10" cy="11" r="1" fill="white"/>
</svg>
);
case 'gentoo':
// G-like icon for Gentoo
return (
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4 11h-4v3c0 .55-.45 1-1 1s-1-.45-1-1v-4c0-.55.45-1 1-1h5c.55 0 1 .45 1 1s-.45 1-1 1z"/>
</svg>
);
case 'freebsd':
return (
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">

View file

@ -191,6 +191,10 @@ func (c Container) ToFrontend() ContainerFrontend {
copy(ct.NetworkInterfaces, c.NetworkInterfaces)
}
if c.OSName != "" {
ct.OSName = c.OSName
}
return ct
}

View file

@ -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.

View file

@ -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

View file

@ -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
}

View file

@ -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 {