feat: Replace OS column text with icons and rich tooltips
- Add OSInfoCell component with OS-specific icons (Windows, Ubuntu, Debian, Alpine, CentOS/RHEL, Fedora, Arch, FreeBSD, generic Linux) - Each OS type has a distinct color for quick visual identification - Portal tooltip shows full OS name, version, and guest agent version - Much more compact than text strings like "Microsoft Windows Server 2022"
This commit is contained in:
parent
c169ead4d2
commit
41acb4f2ce
1 changed files with 178 additions and 6 deletions
|
|
@ -205,6 +205,179 @@ 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';
|
||||||
|
|
||||||
|
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('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('freebsd') || lower.includes('openbsd') || lower.includes('netbsd')) return 'freebsd';
|
||||||
|
if (lower.includes('linux') || lower.includes('gnu')) return 'linux';
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
const OS_COLORS: Record<OSType, string> = {
|
||||||
|
windows: 'text-blue-500',
|
||||||
|
ubuntu: 'text-orange-500',
|
||||||
|
debian: 'text-red-500',
|
||||||
|
alpine: 'text-blue-400',
|
||||||
|
centos: 'text-purple-500',
|
||||||
|
fedora: 'text-blue-600',
|
||||||
|
arch: 'text-cyan-500',
|
||||||
|
linux: 'text-yellow-500',
|
||||||
|
freebsd: 'text-red-600',
|
||||||
|
unknown: 'text-gray-400',
|
||||||
|
};
|
||||||
|
|
||||||
|
// OS info cell with icon and Portal tooltip
|
||||||
|
function OSInfoCell(props: { osName: string; osVersion: string; agentVersion: string }) {
|
||||||
|
const [showTooltip, setShowTooltip] = createSignal(false);
|
||||||
|
const [tooltipPos, setTooltipPos] = createSignal({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const osType = createMemo(() => detectOSType(props.osName));
|
||||||
|
const colorClass = createMemo(() => OS_COLORS[osType()]);
|
||||||
|
const displayName = createMemo(() => {
|
||||||
|
const name = props.osName;
|
||||||
|
// Shorten common long names
|
||||||
|
if (name.toLowerCase().includes('microsoft windows')) {
|
||||||
|
return name.replace(/Microsoft Windows/i, 'Win').replace('Server ', 'Srv ');
|
||||||
|
}
|
||||||
|
if (name.toLowerCase().includes('gnu/linux')) {
|
||||||
|
return name.replace(/GNU\/Linux/i, '');
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMouseEnter = (e: MouseEvent) => {
|
||||||
|
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||||
|
setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top });
|
||||||
|
setShowTooltip(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setShowTooltip(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// SVG icons for different OS types
|
||||||
|
const OSIcon = () => {
|
||||||
|
const type = osType();
|
||||||
|
const iconClass = `w-3.5 h-3.5 ${colorClass()}`;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'windows':
|
||||||
|
return (
|
||||||
|
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M3 5.5l7.038-1v6.5H3v-5.5zm0 13l7.038 1V13H3v5.5zm8.038 1.118L21 21V13h-9.962v6.618zM11.038 4.382L21 3v8h-9.962V4.382z"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'ubuntu':
|
||||||
|
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"/>
|
||||||
|
<circle cx="12" cy="5" r="2" fill="currentColor"/>
|
||||||
|
<circle cx="6" cy="15.5" r="2" fill="currentColor"/>
|
||||||
|
<circle cx="18" cy="15.5" r="2" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'debian':
|
||||||
|
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 2zm0 3c.83 0 1.5.67 1.5 1.5S12.83 8 12 8s-1.5-.67-1.5-1.5S11.17 5 12 5zm-4 9.5c0-2.21 1.79-4 4-4s4 1.79 4 4h-2c0-1.1-.9-2-2-2s-2 .9-2 2H8z"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'alpine':
|
||||||
|
return (
|
||||||
|
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M12 2L2 19h20L12 2zm0 4l6 10H6l6-10z"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'centos':
|
||||||
|
case 'fedora':
|
||||||
|
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 2zm0 2v8H4c0-4.42 3.58-8 8-8zm0 16c-4.42 0-8-3.58-8-8h8v8zm2-8h8c0 4.42-3.58 8-8 8v-8zm0-8c4.42 0 8 3.58 8 8h-8V4z"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'arch':
|
||||||
|
return (
|
||||||
|
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M12 2l-9 18h4l5-10 5 10h4L12 2z"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'freebsd':
|
||||||
|
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 10c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2v4c0 1.1-.9 2-2 2h-4c-1.1 0-2-.9-2-2v-4z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
case 'linux':
|
||||||
|
return (
|
||||||
|
<svg class={iconClass} viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M12 2C8.13 2 5 5.13 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.87-3.13-7-7-7zm-2 15v-2h4v2h-4zm3-4h-2V9h2v4z"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
// Generic server/computer icon
|
||||||
|
return (
|
||||||
|
<svg class={iconClass} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||||||
|
<line x1="8" y1="21" x2="16" y2="21" />
|
||||||
|
<line x1="12" y1="17" x2="12" y2="21" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center gap-1 cursor-help"
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<OSIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Show when={showTooltip()}>
|
||||||
|
<Portal mount={document.body}>
|
||||||
|
<div
|
||||||
|
class="fixed z-[9999] pointer-events-none"
|
||||||
|
style={{
|
||||||
|
left: `${tooltipPos().x}px`,
|
||||||
|
top: `${tooltipPos().y - 8}px`,
|
||||||
|
transform: 'translate(-50%, -100%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="bg-gray-900 dark:bg-gray-800 text-white text-[10px] rounded-md shadow-lg px-2 py-1.5 min-w-[120px] max-w-[220px] border border-gray-700">
|
||||||
|
<div class="font-medium mb-1 text-gray-300 border-b border-gray-700 pb-1">
|
||||||
|
Operating System
|
||||||
|
</div>
|
||||||
|
<div class="py-0.5">
|
||||||
|
<div class="text-gray-200 font-medium">{props.osName}</div>
|
||||||
|
<Show when={props.osVersion}>
|
||||||
|
<div class="text-gray-400">Version: {props.osVersion}</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={props.agentVersion}>
|
||||||
|
<div class="text-gray-500 text-[9px] mt-1 pt-1 border-t border-gray-700/50">
|
||||||
|
Agent: {props.agentVersion}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
</Show>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Backup status cell with Portal tooltip
|
// Backup status cell with Portal tooltip
|
||||||
function BackupStatusCell(props: { lastBackup: string | number | null | undefined }) {
|
function BackupStatusCell(props: { lastBackup: string | number | null | undefined }) {
|
||||||
const [showTooltip, setShowTooltip] = createSignal(false);
|
const [showTooltip, setShowTooltip] = createSignal(false);
|
||||||
|
|
@ -997,12 +1170,11 @@ export function GuestRow(props: GuestRowProps) {
|
||||||
<td class="px-2 py-1 align-middle">
|
<td class="px-2 py-1 align-middle">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<Show when={hasOsInfo()} fallback={<span class="text-xs text-gray-400">-</span>}>
|
<Show when={hasOsInfo()} fallback={<span class="text-xs text-gray-400">-</span>}>
|
||||||
<span
|
<OSInfoCell
|
||||||
class="text-xs text-gray-600 dark:text-gray-400 truncate max-w-[100px]"
|
osName={osName()}
|
||||||
title={`${osName()} ${osVersion()}`}
|
osVersion={osVersion()}
|
||||||
>
|
agentVersion={agentVersion()}
|
||||||
{osName() || osVersion()}
|
/>
|
||||||
</span>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue