feat: add Pokemon sprites to Proxmox nodes based on IP last octet
Each Proxmox server and VM/CT now shows a Pokemon sprite matching its IP's last number (e.g. .3 = Venusaur, .185 = Sudowoodo). VMs also display their IP suffix for quick identification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,17 +11,38 @@ const ICON_MAP: Record<string, string> = {
|
|||||||
phone: "📞", camera: "📷", device: "📱",
|
phone: "📞", camera: "📷", device: "📱",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function pokemonId(ip: string): number {
|
||||||
|
const last = parseInt(ip.split(".").pop() || "0", 10);
|
||||||
|
return last > 0 ? last : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PokemonSprite({ ip, size = 32 }: { ip: string; size?: number }) {
|
||||||
|
const id = pokemonId(ip);
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png`}
|
||||||
|
alt={`#${id}`}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
className="pixelated"
|
||||||
|
style={{ imageRendering: "pixelated" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function VmPill({ node }: { node: NetworkNode }) {
|
function VmPill({ node }: { node: NetworkNode }) {
|
||||||
const dot = node.status === "up" ? "bg-success" : "bg-danger";
|
const dot = node.status === "up" ? "bg-success" : "bg-danger";
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 bg-bg-card/80 border border-border rounded px-1.5 py-0.5">
|
<div className="flex items-center gap-1 bg-bg-card/80 border border-border rounded px-1.5 py-0.5">
|
||||||
|
<PokemonSprite ip={node.ip} size={20} />
|
||||||
<span className={`w-1.5 h-1.5 rounded-full ${dot} shrink-0`} />
|
<span className={`w-1.5 h-1.5 rounded-full ${dot} shrink-0`} />
|
||||||
<span className="text-xs text-text-primary truncate">{node.name}</span>
|
<span className="text-xs text-text-primary truncate">{node.name}</span>
|
||||||
|
<span className="text-xs font-mono text-text-muted ml-auto shrink-0">.{node.ip.split(".").pop()}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InfraCard({ node }: { node: NetworkNode }) {
|
function InfraCard({ node, showPokemon }: { node: NetworkNode; showPokemon?: boolean }) {
|
||||||
const [showPass, setShowPass] = useState(false);
|
const [showPass, setShowPass] = useState(false);
|
||||||
const isUp = node.status === "up";
|
const isUp = node.status === "up";
|
||||||
const border = isUp ? "border-success/30" : "border-danger/30";
|
const border = isUp ? "border-success/30" : "border-danger/30";
|
||||||
@@ -34,7 +55,11 @@ function InfraCard({ node }: { node: NetworkNode }) {
|
|||||||
onClick={() => setShowPass((p) => !p)}
|
onClick={() => setShowPass((p) => !p)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-lg">{ICON_MAP[node.icon] || "📦"}</span>
|
{showPokemon ? (
|
||||||
|
<PokemonSprite ip={node.ip} size={36} />
|
||||||
|
) : (
|
||||||
|
<span className="text-lg">{ICON_MAP[node.icon] || "📦"}</span>
|
||||||
|
)}
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-bold text-text-primary leading-none">{node.name}</p>
|
<p className="text-sm font-bold text-text-primary leading-none">{node.name}</p>
|
||||||
<p className="text-xs font-mono text-text-secondary leading-none mt-0.5">{node.ip}</p>
|
<p className="text-xs font-mono text-text-secondary leading-none mt-0.5">{node.ip}</p>
|
||||||
@@ -59,7 +84,7 @@ function ProxmoxCol({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center flex-1">
|
<div className="flex flex-col items-center flex-1">
|
||||||
<div className="w-px h-3 bg-border-light" />
|
<div className="w-px h-3 bg-border-light" />
|
||||||
<InfraCard node={server} />
|
<InfraCard node={server} showPokemon />
|
||||||
<div className="w-px h-2 bg-border-light" />
|
<div className="w-px h-2 bg-border-light" />
|
||||||
<span className="text-xs text-text-muted mb-1">{up}/{vms.length}</span>
|
<span className="text-xs text-text-muted mb-1">{up}/{vms.length}</span>
|
||||||
<div className="grid grid-cols-3 gap-1 w-full">
|
<div className="grid grid-cols-3 gap-1 w-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user