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:
parent
41acb4f2ce
commit
c9915ead8b
6 changed files with 87 additions and 2 deletions
|
|
@ -206,17 +206,20 @@ function NetworkInfoCell(props: { ipAddresses: string[]; networkInterfaces: Netw
|
||||||
}
|
}
|
||||||
|
|
||||||
// OS detection helper - returns icon type and color based on OS name
|
// 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 {
|
function detectOSType(osName: string): OSType {
|
||||||
const lower = osName.toLowerCase();
|
const lower = osName.toLowerCase();
|
||||||
if (lower.includes('windows')) return 'windows';
|
if (lower.includes('windows')) return 'windows';
|
||||||
if (lower.includes('ubuntu')) return 'ubuntu';
|
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('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('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('fedora')) return 'fedora';
|
||||||
if (lower.includes('arch')) return 'arch';
|
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('freebsd') || lower.includes('openbsd') || lower.includes('netbsd')) return 'freebsd';
|
||||||
if (lower.includes('linux') || lower.includes('gnu')) return 'linux';
|
if (lower.includes('linux') || lower.includes('gnu')) return 'linux';
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
|
|
@ -230,6 +233,9 @@ const OS_COLORS: Record<OSType, string> = {
|
||||||
centos: 'text-purple-500',
|
centos: 'text-purple-500',
|
||||||
fedora: 'text-blue-600',
|
fedora: 'text-blue-600',
|
||||||
arch: 'text-cyan-500',
|
arch: 'text-cyan-500',
|
||||||
|
nixos: 'text-sky-400',
|
||||||
|
opensuse: 'text-green-500',
|
||||||
|
gentoo: 'text-violet-400',
|
||||||
linux: 'text-yellow-500',
|
linux: 'text-yellow-500',
|
||||||
freebsd: 'text-red-600',
|
freebsd: 'text-red-600',
|
||||||
unknown: 'text-gray-400',
|
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"/>
|
<path d="M12 2l-9 18h4l5-10 5 10h4L12 2z"/>
|
||||||
</svg>
|
</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':
|
case 'freebsd':
|
||||||
return (
|
return (
|
||||||
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
|
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,10 @@ func (c Container) ToFrontend() ContainerFrontend {
|
||||||
copy(ct.NetworkInterfaces, c.NetworkInterfaces)
|
copy(ct.NetworkInterfaces, c.NetworkInterfaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.OSName != "" {
|
||||||
|
ct.OSName = c.OSName
|
||||||
|
}
|
||||||
|
|
||||||
return ct
|
return ct
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ type Container struct {
|
||||||
LastSeen time.Time `json:"lastSeen"`
|
LastSeen time.Time `json:"lastSeen"`
|
||||||
IPAddresses []string `json:"ipAddresses,omitempty"`
|
IPAddresses []string `json:"ipAddresses,omitempty"`
|
||||||
NetworkInterfaces []GuestNetworkInterface `json:"networkInterfaces,omitempty"`
|
NetworkInterfaces []GuestNetworkInterface `json:"networkInterfaces,omitempty"`
|
||||||
|
OSName string `json:"osName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host represents a generic infrastructure host reporting via external agents.
|
// Host represents a generic infrastructure host reporting via external agents.
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ type ContainerFrontend struct {
|
||||||
Tags string `json:"tags,omitempty"` // Joined string
|
Tags string `json:"tags,omitempty"` // Joined string
|
||||||
Lock string `json:"lock,omitempty"`
|
Lock string `json:"lock,omitempty"`
|
||||||
LastSeen int64 `json:"lastSeen"` // Unix timestamp
|
LastSeen int64 `json:"lastSeen"` // Unix timestamp
|
||||||
|
OSName string `json:"osName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockerHostFrontend represents a Docker host with frontend-friendly fields
|
// DockerHostFrontend represents a Docker host with frontend-friendly fields
|
||||||
|
|
|
||||||
|
|
@ -445,3 +445,48 @@ func extractContainerRootDeviceFromConfig(config map[string]interface{}) string
|
||||||
device := strings.TrimSpace(parts[0])
|
device := strings.TrimSpace(parts[0])
|
||||||
return device
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2329,6 +2329,10 @@ func (m *Monitor) enrichContainerMetadata(ctx context.Context, client PVEClientI
|
||||||
}
|
}
|
||||||
mergeContainerNetworkInterface(&networkIfaces, detail)
|
mergeContainerNetworkInterface(&networkIfaces, detail)
|
||||||
}
|
}
|
||||||
|
// Extract OS type from container config
|
||||||
|
if osName := extractContainerOSType(configData); osName != "" {
|
||||||
|
container.OSName = osName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(addressOrder) == 0 {
|
if len(addressOrder) == 0 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue