Show Sparks and Linux in topology
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
/** "macbook pro" | "mac studio" | "mac mini" etc. */
|
||||
/** "macbook pro" | "mac studio" | "mac mini" | "dgx spark" | "linux" etc. */
|
||||
deviceType: string;
|
||||
/** Center X coordinate in SVG space */
|
||||
cx: number;
|
||||
@@ -38,10 +38,50 @@
|
||||
const LOGO_NATIVE_WIDTH = 814;
|
||||
const LOGO_NATIVE_HEIGHT = 1000;
|
||||
|
||||
// NVIDIA logo SVG path
|
||||
const NVIDIA_LOGO_PATH =
|
||||
"M0.81 0.429V0.299c0.013 -0.001 0.026 -0.002 0.038 -0.002 0.355 -0.011 0.588 0.306 0.588 0.306S1.186 0.952 0.916 0.952c-0.036 0 -0.071 -0.006 -0.105 -0.017V0.542c0.138 0.017 0.166 0.078 0.249 0.216l0.185 -0.155s-0.135 -0.177 -0.362 -0.177c-0.024 -0.001 -0.048 0.001 -0.072 0.003m0 -0.429v0.194l0.038 -0.002c0.494 -0.017 0.816 0.405 0.816 0.405s-0.37 0.45 -0.754 0.45c-0.034 0 -0.066 -0.003 -0.099 -0.009v0.12c0.027 0.003 0.055 0.006 0.082 0.006 0.358 0 0.618 -0.183 0.869 -0.399 0.042 0.034 0.212 0.114 0.247 0.15 -0.238 0.2 -0.794 0.361 -1.11 0.361 -0.03 0 -0.059 -0.002 -0.088 -0.005v0.169h1.362V0zm0 0.935v0.102c-0.331 -0.059 -0.423 -0.404 -0.423 -0.404s0.159 -0.176 0.423 -0.205v0.112h-0.001C0.671 0.524 0.562 0.654 0.562 0.654s0.062 0.218 0.248 0.282m-0.588 -0.316s0.196 -0.29 0.589 -0.32V0.194C0.376 0.229 0 0.597 0 0.597s0.213 0.616 0.81 0.672v-0.112c-0.438 -0.054 -0.588 -0.538 -0.588 -0.538";
|
||||
|
||||
// Tux penguin paths (viewBox 0 0 100 100)
|
||||
const TUX_BODY_PATH =
|
||||
"M50 8c-8 0-14 6-14 13 0 4 2 8 5 10-8 4-16 14-16 28v12c0 4 2 7 5 9l-6 4c-2 1-3 3-3 5v3c0 2 2 4 4 4h10l4-4h22l4 4h10c2 0 4-2 4-4v-3c0-2-1-4-3-5l-6-4c3-2 5-5 5-9V59c0-14-8-24-16-28 3-2 5-6 5-10 0-7-6-13-14-13z";
|
||||
const TUX_BELLY_PATH =
|
||||
"M38 52c0-8 5-15 12-15s12 7 12 15v14c0 4-5 7-12 7s-12-3-12-7V52z";
|
||||
|
||||
const wireColor = "rgba(179,179,179,0.8)";
|
||||
const strokeWidth = 1.5;
|
||||
|
||||
const modelLower = $derived(deviceType.toLowerCase());
|
||||
const isSpark = $derived(
|
||||
modelLower.includes("dgx") || modelLower.includes("gx10"),
|
||||
);
|
||||
const isLinux = $derived(!isSpark && modelLower.includes("linux"));
|
||||
|
||||
// ── DGX Spark dimensions ──
|
||||
const dgxW = $derived(size * 1.55);
|
||||
const dgxH = $derived(size * 0.58);
|
||||
const dgxX = $derived(cx - dgxW / 2);
|
||||
const dgxY = $derived(cy - dgxH / 2);
|
||||
const dgxChassisX = $derived(dgxX - dgxW * 0.03);
|
||||
const dgxChassisW = $derived(dgxW * 1.05);
|
||||
const dgxHandleW = $derived(dgxW * 0.27);
|
||||
const dgxHandleGap = $derived(dgxH * 0.05);
|
||||
const dgxHandleH = $derived(dgxH - dgxHandleGap * 2);
|
||||
const dgxHandleY = $derived(dgxY + dgxHandleGap);
|
||||
const dgxInnerHandleW = $derived(dgxW * 0.12);
|
||||
const dgxInnerHandleH = $derived(dgxHandleH - dgxH * 0.06);
|
||||
const dgxLeftHandleX = $derived(dgxX + 4);
|
||||
const dgxRightHandleX = $derived(dgxX + dgxW - dgxHandleW - 4);
|
||||
const dgxClipId = $derived(`di-dgx-${uid}`);
|
||||
const dgxTextureId = $derived(`di-dgx-tex-${uid}`);
|
||||
|
||||
// ── Linux Tux dimensions ──
|
||||
const tuxSize = $derived(Math.min(size * 1.0, size * 1.1));
|
||||
const tuxScale = $derived(tuxSize / 100);
|
||||
const tuxTx = $derived(cx - (100 * tuxScale) / 2);
|
||||
const tuxTy = $derived(cy - (100 * tuxScale) / 2);
|
||||
const tuxEyeR = $derived(2.5 * tuxScale);
|
||||
const tuxBellyClipId = $derived(`di-tux-belly-${uid}`);
|
||||
|
||||
// ── Mac Studio dimensions (same ratios as TopologyGraph) ──
|
||||
const studioW = $derived(size * 1.25);
|
||||
@@ -114,7 +154,230 @@
|
||||
const studioClipId = $derived(`di-studio-${uid}`);
|
||||
</script>
|
||||
|
||||
{#if modelLower === "mac studio" || modelLower === "mac mini"}
|
||||
{#if isSpark}
|
||||
<!-- DGX Spark -->
|
||||
<defs>
|
||||
<clipPath id={dgxClipId}>
|
||||
<rect x={dgxX} y={dgxY} width={dgxW} height={dgxH} rx="3" />
|
||||
</clipPath>
|
||||
<pattern
|
||||
id={dgxTextureId}
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="8"
|
||||
height="8"
|
||||
>
|
||||
<rect width="8" height="8" fill="#6f6248" />
|
||||
<circle cx="2" cy="2" r="1" fill="#5a4f3b" opacity="0.5" />
|
||||
<circle cx="6" cy="6" r="1" fill="#4a4232" opacity="0.45" />
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<!-- Main body -->
|
||||
<rect
|
||||
x={dgxChassisX}
|
||||
y={dgxY}
|
||||
width={dgxChassisW}
|
||||
height={dgxH}
|
||||
rx="3"
|
||||
fill="url(#{dgxTextureId})"
|
||||
stroke={wireColor}
|
||||
stroke-width={strokeWidth}
|
||||
/>
|
||||
|
||||
<!-- Side border accents -->
|
||||
<rect
|
||||
x={dgxChassisX}
|
||||
y={dgxY}
|
||||
width={dgxW * 0.02}
|
||||
height={dgxH}
|
||||
fill="#8a7a56"
|
||||
/>
|
||||
<rect
|
||||
x={dgxChassisX + dgxChassisW - dgxW * 0.02}
|
||||
y={dgxY}
|
||||
width={dgxW * 0.02}
|
||||
height={dgxH}
|
||||
fill="#8a7a56"
|
||||
/>
|
||||
|
||||
<!-- Memory fill -->
|
||||
{#if ramPercent > 0}
|
||||
<rect
|
||||
x={dgxX}
|
||||
y={dgxY + dgxH - (ramPercent / 100) * dgxH}
|
||||
width={dgxW}
|
||||
height={(ramPercent / 100) * dgxH}
|
||||
fill="rgba(255,215,0,0.45)"
|
||||
clip-path="url(#{dgxClipId})"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Left handle -->
|
||||
<rect
|
||||
x={dgxLeftHandleX}
|
||||
y={dgxHandleY}
|
||||
width={dgxHandleW}
|
||||
height={dgxHandleH}
|
||||
rx="2.4"
|
||||
fill="#b3a170"
|
||||
stroke="#403723"
|
||||
stroke-width="0.7"
|
||||
/>
|
||||
<rect
|
||||
x={dgxLeftHandleX + dgxHandleW * 0.06}
|
||||
y={dgxHandleY + dgxH * 0.03}
|
||||
width={dgxInnerHandleW}
|
||||
height={dgxInnerHandleH}
|
||||
rx="1.6"
|
||||
fill="#8a7a56"
|
||||
/>
|
||||
|
||||
<!-- Right handle -->
|
||||
<rect
|
||||
x={dgxRightHandleX}
|
||||
y={dgxHandleY}
|
||||
width={dgxHandleW}
|
||||
height={dgxHandleH}
|
||||
rx="2.4"
|
||||
fill="#b3a170"
|
||||
stroke="#403723"
|
||||
stroke-width="0.7"
|
||||
/>
|
||||
<rect
|
||||
x={dgxRightHandleX + dgxHandleW - dgxInnerHandleW - dgxHandleW * 0.08}
|
||||
y={dgxHandleY + dgxH * 0.03}
|
||||
width={dgxInnerHandleW}
|
||||
height={dgxInnerHandleH}
|
||||
rx="1.6"
|
||||
fill="#8a7a56"
|
||||
/>
|
||||
|
||||
<!-- NVIDIA logo (rotated 90deg on left handle) -->
|
||||
{@const badgeW = dgxW * 0.09}
|
||||
{@const badgeH = dgxHandleH * 0.5}
|
||||
{@const badgeX = dgxLeftHandleX + dgxHandleW - badgeW - dgxHandleW * 0.06}
|
||||
{@const badgeYPos = dgxHandleY + (dgxHandleH - badgeH) / 2}
|
||||
{@const textSz = badgeW * 0.58}
|
||||
{@const logoW = textSz * 1.2}
|
||||
{@const logoH = logoW * (1.438 / 2.174)}
|
||||
{@const ctrX = badgeX + badgeW / 2 - badgeW * 0.03}
|
||||
{@const ctrY = badgeYPos + badgeH / 2}
|
||||
{@const labelGap = badgeW * 0.15}
|
||||
{@const totalW = logoW + labelGap + textSz * 3.6}
|
||||
<g transform="rotate(90 {ctrX} {ctrY})">
|
||||
<svg
|
||||
x={ctrX - totalW / 2}
|
||||
y={ctrY - logoH / 2}
|
||||
width={logoW}
|
||||
height={logoH}
|
||||
viewBox="0 0 2.174 1.438"
|
||||
>
|
||||
<path d={NVIDIA_LOGO_PATH} fill="#76b900" />
|
||||
</svg>
|
||||
<text
|
||||
x={ctrX - totalW / 2 + logoW + labelGap}
|
||||
y={ctrY}
|
||||
text-anchor="start"
|
||||
dominant-baseline="middle"
|
||||
fill="#8a7a56"
|
||||
font-size={textSz}
|
||||
font-family="monospace"
|
||||
font-weight="700">NVIDIA</text
|
||||
>
|
||||
</g>
|
||||
{:else if isLinux}
|
||||
<!-- Linux Tux Penguin -->
|
||||
<defs>
|
||||
<clipPath id={tuxBellyClipId}>
|
||||
<path
|
||||
d={TUX_BELLY_PATH}
|
||||
transform="translate({tuxTx}, {tuxTy}) scale({tuxScale})"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<!-- Body -->
|
||||
<path
|
||||
d={TUX_BODY_PATH}
|
||||
transform="translate({tuxTx}, {tuxTy}) scale({tuxScale})"
|
||||
fill="#1a1a1a"
|
||||
stroke={wireColor}
|
||||
stroke-width={strokeWidth / tuxScale}
|
||||
/>
|
||||
|
||||
<!-- White belly -->
|
||||
<path
|
||||
d={TUX_BELLY_PATH}
|
||||
transform="translate({tuxTx}, {tuxTy}) scale({tuxScale})"
|
||||
fill="rgba(220,220,220,0.85)"
|
||||
/>
|
||||
|
||||
<!-- Memory fill on belly -->
|
||||
{#if ramPercent > 0}
|
||||
{@const bellyTop = tuxTy + 37 * tuxScale}
|
||||
{@const bellyH = 36 * tuxScale}
|
||||
{@const memH = (ramPercent / 100) * bellyH}
|
||||
<rect
|
||||
x={tuxTx + 38 * tuxScale}
|
||||
y={bellyTop + bellyH - memH}
|
||||
width={24 * tuxScale}
|
||||
height={memH}
|
||||
fill="rgba(255,215,0,0.75)"
|
||||
clip-path="url(#{tuxBellyClipId})"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Eyes -->
|
||||
<circle
|
||||
cx={tuxTx + 44 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR}
|
||||
fill="white"
|
||||
/>
|
||||
<circle
|
||||
cx={tuxTx + 56 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR}
|
||||
fill="white"
|
||||
/>
|
||||
<!-- Pupils -->
|
||||
<circle
|
||||
cx={tuxTx + 44 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR * 0.5}
|
||||
fill="#1a1a1a"
|
||||
/>
|
||||
<circle
|
||||
cx={tuxTx + 56 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR * 0.5}
|
||||
fill="#1a1a1a"
|
||||
/>
|
||||
|
||||
<!-- Beak -->
|
||||
<path
|
||||
d="M{tuxTx + 46 * tuxScale} {tuxTy + 22 * tuxScale} L{tuxTx +
|
||||
50 * tuxScale} {tuxTy + 27 * tuxScale} L{tuxTx + 54 * tuxScale} {tuxTy +
|
||||
22 * tuxScale} Z"
|
||||
fill="#E8A317"
|
||||
/>
|
||||
|
||||
<!-- Feet -->
|
||||
<ellipse
|
||||
cx={tuxTx + 42 * tuxScale}
|
||||
cy={tuxTy + 94 * tuxScale}
|
||||
rx={6 * tuxScale}
|
||||
ry={2.5 * tuxScale}
|
||||
fill="#E8A317"
|
||||
/>
|
||||
<ellipse
|
||||
cx={tuxTx + 58 * tuxScale}
|
||||
cy={tuxTy + 94 * tuxScale}
|
||||
rx={6 * tuxScale}
|
||||
ry={2.5 * tuxScale}
|
||||
fill="#E8A317"
|
||||
/>
|
||||
{:else if modelLower === "mac studio" || modelLower === "mac mini"}
|
||||
<!-- Mac Studio / Mac Mini -->
|
||||
<defs>
|
||||
<clipPath id={studioClipId}>
|
||||
|
||||
@@ -169,8 +169,10 @@
|
||||
|
||||
function getDeviceType(
|
||||
name: string,
|
||||
): "macbook" | "studio" | "mini" | "unknown" {
|
||||
): "macbook" | "studio" | "mini" | "dgx" | "linux" | "unknown" {
|
||||
const lower = name.toLowerCase();
|
||||
if (lower.includes("dgx") || lower.includes("gx10")) return "dgx";
|
||||
if (lower.includes("linux")) return "linux";
|
||||
if (lower.includes("macbook")) return "macbook";
|
||||
if (lower.includes("studio")) return "studio";
|
||||
if (lower.includes("mini")) return "mini";
|
||||
|
||||
@@ -117,6 +117,17 @@
|
||||
const LOGO_NATIVE_WIDTH = 814;
|
||||
const LOGO_NATIVE_HEIGHT = 1000;
|
||||
|
||||
// NVIDIA logo SVG path (from exo-nvidia)
|
||||
const NVIDIA_LOGO_PATH =
|
||||
"M0.81 0.429V0.299c0.013 -0.001 0.026 -0.002 0.038 -0.002 0.355 -0.011 0.588 0.306 0.588 0.306S1.186 0.952 0.916 0.952c-0.036 0 -0.071 -0.006 -0.105 -0.017V0.542c0.138 0.017 0.166 0.078 0.249 0.216l0.185 -0.155s-0.135 -0.177 -0.362 -0.177c-0.024 -0.001 -0.048 0.001 -0.072 0.003m0 -0.429v0.194l0.038 -0.002c0.494 -0.017 0.816 0.405 0.816 0.405s-0.37 0.45 -0.754 0.45c-0.034 0 -0.066 -0.003 -0.099 -0.009v0.12c0.027 0.003 0.055 0.006 0.082 0.006 0.358 0 0.618 -0.183 0.869 -0.399 0.042 0.034 0.212 0.114 0.247 0.15 -0.238 0.2 -0.794 0.361 -1.11 0.361 -0.03 0 -0.059 -0.002 -0.088 -0.005v0.169h1.362V0zm0 0.935v0.102c-0.331 -0.059 -0.423 -0.404 -0.423 -0.404s0.159 -0.176 0.423 -0.205v0.112h-0.001C0.671 0.524 0.562 0.654 0.562 0.654s0.062 0.218 0.248 0.282m-0.588 -0.316s0.196 -0.29 0.589 -0.32V0.194C0.376 0.229 0 0.597 0 0.597s0.213 0.616 0.81 0.672v-0.112c-0.438 -0.054 -0.588 -0.538 -0.588 -0.538";
|
||||
|
||||
// Tux penguin SVG path — simplified silhouette for topology view
|
||||
// viewBox: 0 0 100 100
|
||||
const TUX_BODY_PATH =
|
||||
"M50 8c-8 0-14 6-14 13 0 4 2 8 5 10-8 4-16 14-16 28v12c0 4 2 7 5 9l-6 4c-2 1-3 3-3 5v3c0 2 2 4 4 4h10l4-4h22l4 4h10c2 0 4-2 4-4v-3c0-2-1-4-3-5l-6-4c3-2 5-5 5-9V59c0-14-8-24-16-28 3-2 5-6 5-10 0-7-6-13-14-13z";
|
||||
const TUX_BELLY_PATH =
|
||||
"M38 52c0-8 5-15 12-15s12 7 12 15v14c0 4-5 7-12 7s-12-3-12-7V52z";
|
||||
|
||||
function formatBytes(bytes: number, decimals = 1): string {
|
||||
if (!bytes || bytes === 0) return "0B";
|
||||
const k = 1024;
|
||||
@@ -554,6 +565,12 @@
|
||||
const clipPathId = `clip-${nodeInfo.id.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
||||
|
||||
const modelLower = modelId.toLowerCase();
|
||||
const identity = identitiesData[nodeInfo.id];
|
||||
const nameLower = (friendlyName || "").toLowerCase();
|
||||
const isSpark =
|
||||
modelLower.includes("dgx") || modelLower.includes("gx10");
|
||||
const isLinux =
|
||||
!isSpark && (modelLower === "linux" || identity?.osVersion === "Linux");
|
||||
|
||||
// Check node states for styling
|
||||
const isHighlighted = highlightedNodes.has(nodeInfo.id);
|
||||
@@ -623,7 +640,294 @@
|
||||
`${friendlyName}\nID: ${nodeInfo.id.slice(-8)}\nMemory: ${formatBytes(ramUsed)}/${formatBytes(ramTotal)}`,
|
||||
);
|
||||
|
||||
if (modelLower === "mac studio") {
|
||||
if (isSpark) {
|
||||
// NVIDIA DGX Spark — gold chassis with textured front, side handles, and NVIDIA badge
|
||||
iconBaseWidth = nodeRadius * 1.55;
|
||||
iconBaseHeight = nodeRadius * 0.58;
|
||||
const x = nodeInfo.x - iconBaseWidth / 2;
|
||||
const y = nodeInfo.y - iconBaseHeight / 2;
|
||||
const chassisX = x - iconBaseWidth * 0.03;
|
||||
const chassisWidth = iconBaseWidth * 1.05;
|
||||
const cornerRadius = 3;
|
||||
|
||||
const dgxClipId = `dgx-clip-${nodeInfo.id.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
||||
defs
|
||||
.append("clipPath")
|
||||
.attr("id", dgxClipId)
|
||||
.append("rect")
|
||||
.attr("x", x)
|
||||
.attr("y", y)
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", iconBaseHeight)
|
||||
.attr("rx", cornerRadius);
|
||||
|
||||
// Chassis texture pattern
|
||||
const textureId = `chassis-texture-${nodeInfo.id.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
||||
defs
|
||||
.append("pattern")
|
||||
.attr("id", textureId)
|
||||
.attr("patternUnits", "userSpaceOnUse")
|
||||
.attr("width", 8)
|
||||
.attr("height", 8);
|
||||
const texturePattern = defs.select(`#${textureId}`);
|
||||
texturePattern
|
||||
.append("rect")
|
||||
.attr("width", 8)
|
||||
.attr("height", 8)
|
||||
.attr("fill", "#6f6248");
|
||||
texturePattern
|
||||
.append("circle")
|
||||
.attr("cx", 2)
|
||||
.attr("cy", 2)
|
||||
.attr("r", 1)
|
||||
.attr("fill", "#5a4f3b")
|
||||
.attr("opacity", 0.5);
|
||||
texturePattern
|
||||
.append("circle")
|
||||
.attr("cx", 6)
|
||||
.attr("cy", 6)
|
||||
.attr("r", 1)
|
||||
.attr("fill", "#4a4232")
|
||||
.attr("opacity", 0.45);
|
||||
|
||||
// Main body
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("class", "node-outline")
|
||||
.attr("x", chassisX)
|
||||
.attr("y", y)
|
||||
.attr("width", chassisWidth)
|
||||
.attr("height", iconBaseHeight)
|
||||
.attr("rx", cornerRadius)
|
||||
.attr("fill", `url(#${textureId})`)
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", strokeWidth);
|
||||
|
||||
// Side border accents
|
||||
const sideThickness = iconBaseWidth * 0.02;
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", chassisX)
|
||||
.attr("y", y)
|
||||
.attr("width", sideThickness)
|
||||
.attr("height", iconBaseHeight)
|
||||
.attr("fill", "#8a7a56");
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", chassisX + chassisWidth - sideThickness)
|
||||
.attr("y", y)
|
||||
.attr("width", sideThickness)
|
||||
.attr("height", iconBaseHeight)
|
||||
.attr("fill", "#8a7a56");
|
||||
|
||||
// Memory fill (bottom up)
|
||||
if (ramUsagePercent > 0) {
|
||||
const memFillHeight = (ramUsagePercent / 100) * iconBaseHeight;
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", x)
|
||||
.attr("y", y + iconBaseHeight - memFillHeight)
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", memFillHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.45)")
|
||||
.attr("clip-path", `url(#${dgxClipId})`);
|
||||
}
|
||||
|
||||
// Side handles with inner recess
|
||||
const handleWidth = iconBaseWidth * 0.27;
|
||||
const handleGap = iconBaseHeight * 0.05;
|
||||
const handleHeight = iconBaseHeight - handleGap * 2;
|
||||
const handleY = y + handleGap;
|
||||
const innerHandleWidth = iconBaseWidth * 0.12;
|
||||
const innerHandleHeight = handleHeight - iconBaseHeight * 0.06;
|
||||
const leftHandleX = x + 4;
|
||||
const rightHandleX = x + iconBaseWidth - handleWidth - 4;
|
||||
|
||||
// Left handle
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", leftHandleX)
|
||||
.attr("y", handleY)
|
||||
.attr("width", handleWidth)
|
||||
.attr("height", handleHeight)
|
||||
.attr("rx", 2.4)
|
||||
.attr("fill", "#b3a170")
|
||||
.attr("stroke", "#403723")
|
||||
.attr("stroke-width", 0.7);
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", leftHandleX + handleWidth * 0.06)
|
||||
.attr("y", handleY + iconBaseHeight * 0.03)
|
||||
.attr("width", innerHandleWidth)
|
||||
.attr("height", innerHandleHeight)
|
||||
.attr("rx", 1.6)
|
||||
.attr("fill", "#8a7a56");
|
||||
|
||||
// Right handle
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", rightHandleX)
|
||||
.attr("y", handleY)
|
||||
.attr("width", handleWidth)
|
||||
.attr("height", handleHeight)
|
||||
.attr("rx", 2.4)
|
||||
.attr("fill", "#b3a170")
|
||||
.attr("stroke", "#403723")
|
||||
.attr("stroke-width", 0.7);
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr(
|
||||
"x",
|
||||
rightHandleX + handleWidth - innerHandleWidth - handleWidth * 0.08,
|
||||
)
|
||||
.attr("y", handleY + iconBaseHeight * 0.03)
|
||||
.attr("width", innerHandleWidth)
|
||||
.attr("height", innerHandleHeight)
|
||||
.attr("rx", 1.6)
|
||||
.attr("fill", "#8a7a56");
|
||||
|
||||
// NVIDIA logo + text label (rotated 90 deg on left handle)
|
||||
const badgeWidth = iconBaseWidth * 0.09;
|
||||
const badgeHeight = handleHeight * 0.5;
|
||||
const badgeX =
|
||||
leftHandleX + handleWidth - badgeWidth - handleWidth * 0.06;
|
||||
const badgeY = handleY + (handleHeight - badgeHeight) / 2;
|
||||
const textSize = badgeWidth * 0.58;
|
||||
const logoWidth = textSize * 1.2;
|
||||
const logoHeight = logoWidth * (1.438 / 2.174);
|
||||
const centerX = badgeX + badgeWidth / 2 - badgeWidth * 0.03;
|
||||
const centerY = badgeY + badgeHeight / 2;
|
||||
const gap = badgeWidth * 0.15;
|
||||
const totalWidth = logoWidth + gap + textSize * 3.6;
|
||||
|
||||
const labelGroup = nodeG
|
||||
.append("g")
|
||||
.attr("transform", `rotate(90 ${centerX} ${centerY})`);
|
||||
|
||||
labelGroup
|
||||
.append("svg")
|
||||
.attr("x", centerX - totalWidth / 2)
|
||||
.attr("y", centerY - logoHeight / 2)
|
||||
.attr("width", logoWidth)
|
||||
.attr("height", logoHeight)
|
||||
.attr("viewBox", "0 0 2.174 1.438")
|
||||
.append("path")
|
||||
.attr("d", NVIDIA_LOGO_PATH)
|
||||
.attr("fill", "#76b900");
|
||||
|
||||
labelGroup
|
||||
.append("text")
|
||||
.attr("x", centerX - totalWidth / 2 + logoWidth + gap)
|
||||
.attr("y", centerY)
|
||||
.attr("text-anchor", "start")
|
||||
.attr("dominant-baseline", "middle")
|
||||
.attr("fill", "#8a7a56")
|
||||
.attr("font-size", textSize)
|
||||
.attr("font-family", "monospace")
|
||||
.attr("font-weight", "700")
|
||||
.text("NVIDIA");
|
||||
} else if (isLinux) {
|
||||
// Linux — Tux penguin
|
||||
iconBaseWidth = nodeRadius * 1.0;
|
||||
iconBaseHeight = nodeRadius * 1.1;
|
||||
const tuxSize = Math.min(iconBaseWidth, iconBaseHeight);
|
||||
const scale = tuxSize / 100;
|
||||
const tx = nodeInfo.x - (100 * scale) / 2;
|
||||
const ty = nodeInfo.y - (100 * scale) / 2;
|
||||
|
||||
// Body (dark outline)
|
||||
nodeG
|
||||
.append("path")
|
||||
.attr("class", "node-outline")
|
||||
.attr("d", TUX_BODY_PATH)
|
||||
.attr("transform", `translate(${tx}, ${ty}) scale(${scale})`)
|
||||
.attr("fill", "#1a1a1a")
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", strokeWidth / scale);
|
||||
|
||||
// White belly
|
||||
nodeG
|
||||
.append("path")
|
||||
.attr("d", TUX_BELLY_PATH)
|
||||
.attr("transform", `translate(${tx}, ${ty}) scale(${scale})`)
|
||||
.attr("fill", "rgba(220,220,220,0.85)");
|
||||
|
||||
// Memory fill on belly (from bottom up)
|
||||
if (ramUsagePercent > 0) {
|
||||
const bellyClipId = `tux-belly-${nodeInfo.id.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
||||
defs
|
||||
.append("clipPath")
|
||||
.attr("id", bellyClipId)
|
||||
.append("path")
|
||||
.attr("d", TUX_BELLY_PATH)
|
||||
.attr("transform", `translate(${tx}, ${ty}) scale(${scale})`);
|
||||
|
||||
const bellyTop = ty + 37 * scale;
|
||||
const bellyHeight = 36 * scale;
|
||||
const memHeight = (ramUsagePercent / 100) * bellyHeight;
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", tx + 38 * scale)
|
||||
.attr("y", bellyTop + bellyHeight - memHeight)
|
||||
.attr("width", 24 * scale)
|
||||
.attr("height", memHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.75)")
|
||||
.attr("clip-path", `url(#${bellyClipId})`);
|
||||
}
|
||||
|
||||
// Eyes
|
||||
const eyeRadius = 2.5 * scale;
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 44 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius)
|
||||
.attr("fill", "white");
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 56 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius)
|
||||
.attr("fill", "white");
|
||||
// Pupils
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 44 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius * 0.5)
|
||||
.attr("fill", "#1a1a1a");
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 56 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius * 0.5)
|
||||
.attr("fill", "#1a1a1a");
|
||||
|
||||
// Beak
|
||||
nodeG
|
||||
.append("path")
|
||||
.attr(
|
||||
"d",
|
||||
`M${tx + 46 * scale} ${ty + 22 * scale} L${tx + 50 * scale} ${ty + 27 * scale} L${tx + 54 * scale} ${ty + 22 * scale} Z`,
|
||||
)
|
||||
.attr("fill", "#E8A317");
|
||||
|
||||
// Feet
|
||||
nodeG
|
||||
.append("ellipse")
|
||||
.attr("cx", tx + 42 * scale)
|
||||
.attr("cy", ty + 94 * scale)
|
||||
.attr("rx", 6 * scale)
|
||||
.attr("ry", 2.5 * scale)
|
||||
.attr("fill", "#E8A317");
|
||||
nodeG
|
||||
.append("ellipse")
|
||||
.attr("cx", tx + 58 * scale)
|
||||
.attr("cy", ty + 94 * scale)
|
||||
.attr("rx", 6 * scale)
|
||||
.attr("ry", 2.5 * scale)
|
||||
.attr("fill", "#E8A317");
|
||||
} else if (modelLower === "mac studio") {
|
||||
// Mac Studio - classic cube with memory fill
|
||||
iconBaseWidth = nodeRadius * 1.25;
|
||||
iconBaseHeight = nodeRadius * 0.85;
|
||||
@@ -1182,8 +1486,12 @@
|
||||
debugLabelY += debugLineHeight;
|
||||
}
|
||||
|
||||
const identity = identitiesData[nodeInfo.id];
|
||||
if (identity?.osVersion) {
|
||||
const dbgIdentity = identitiesData[nodeInfo.id];
|
||||
if (dbgIdentity?.osVersion) {
|
||||
const osLabel =
|
||||
dbgIdentity.osVersion === "Linux"
|
||||
? "Linux"
|
||||
: `macOS ${dbgIdentity.osVersion}${dbgIdentity.osBuildVersion ? ` (${dbgIdentity.osBuildVersion})` : ""}`;
|
||||
nodeG
|
||||
.append("text")
|
||||
.attr("x", nodeInfo.x)
|
||||
@@ -1192,9 +1500,7 @@
|
||||
.attr("fill", "rgba(179,179,179,0.7)")
|
||||
.attr("font-size", debugFontSize)
|
||||
.attr("font-family", "SF Mono, Monaco, monospace")
|
||||
.text(
|
||||
`macOS ${identity.osVersion}${identity.osBuildVersion ? ` (${identity.osBuildVersion})` : ""}`,
|
||||
);
|
||||
.text(osLabel);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import platform
|
||||
import socket
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from subprocess import CalledProcessError
|
||||
|
||||
import psutil
|
||||
@@ -117,12 +118,84 @@ async def get_network_interfaces() -> list[NetworkInterfaceInfo]:
|
||||
return interfaces_info
|
||||
|
||||
|
||||
def _read_dmi_field(name: str) -> str | None:
|
||||
"""Read a single DMI sysfs field, returning ``None`` on failure."""
|
||||
try:
|
||||
path = Path(f"/sys/class/dmi/id/{name}")
|
||||
if path.exists():
|
||||
return path.read_text().strip()
|
||||
except (OSError, PermissionError):
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
async def _get_linux_model_and_chip() -> tuple[str, str]:
|
||||
"""Get Linux system information using DMI and /proc/cpuinfo.
|
||||
|
||||
Detects NVIDIA DGX Spark (DMI product_name ``"DGX_Spark"``) and other
|
||||
NVIDIA systems via the ``sys_vendor`` DMI field, falling back to generic
|
||||
Linux identification.
|
||||
"""
|
||||
model = "Linux"
|
||||
chip = "Unknown Chip"
|
||||
|
||||
product_name = _read_dmi_field("product_name")
|
||||
sys_vendor = _read_dmi_field("sys_vendor")
|
||||
|
||||
# DGX Spark: DMI product_name may be "DGX_Spark" or "gx10" variant
|
||||
product_lower = (product_name or "").lower()
|
||||
if product_name and ("dgx" in product_lower or "gx10" in product_lower):
|
||||
model = "DGX Spark"
|
||||
try:
|
||||
process = await run_process(
|
||||
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"]
|
||||
)
|
||||
gpu_name = process.stdout.decode().strip().split("\n")[0]
|
||||
chip = gpu_name if gpu_name and gpu_name != "[N/A]" else "NVIDIA GB10"
|
||||
except (CalledProcessError, FileNotFoundError):
|
||||
chip = "NVIDIA GB10"
|
||||
return (model, chip)
|
||||
|
||||
# Other NVIDIA systems (sys_vendor contains "NVIDIA")
|
||||
if sys_vendor and "NVIDIA" in sys_vendor:
|
||||
model = product_name.replace("_", " ") if product_name else "NVIDIA System"
|
||||
try:
|
||||
process = await run_process(
|
||||
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"]
|
||||
)
|
||||
gpu_name = process.stdout.decode().strip().split("\n")[0]
|
||||
if gpu_name and gpu_name != "[N/A]":
|
||||
chip = gpu_name
|
||||
except (CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
return (model, chip)
|
||||
|
||||
# Generic Linux — use /proc/cpuinfo for chip
|
||||
cpuinfo_path = Path("/proc/cpuinfo")
|
||||
if cpuinfo_path.exists():
|
||||
try:
|
||||
for line in cpuinfo_path.read_text().splitlines():
|
||||
if line.startswith("model name"):
|
||||
chip = line.split(":", 1)[1].strip()
|
||||
break
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return (model, chip)
|
||||
|
||||
|
||||
async def get_model_and_chip() -> tuple[str, str]:
|
||||
"""Get Mac system information using system_profiler."""
|
||||
"""Get system model and chip information.
|
||||
|
||||
On macOS, uses ``system_profiler``. On Linux, reads DMI data from
|
||||
sysfs and CPU info from ``/proc/cpuinfo``.
|
||||
"""
|
||||
model = "Unknown Model"
|
||||
chip = "Unknown Chip"
|
||||
|
||||
# TODO: better non mac support
|
||||
if sys.platform == "linux":
|
||||
return await _get_linux_model_and_chip()
|
||||
|
||||
if sys.platform != "darwin":
|
||||
return (model, chip)
|
||||
|
||||
|
||||
@@ -14,11 +14,14 @@ DSC="${SRCNAME}_${SRCVER}.dsc"
|
||||
KBUILD="/lib/modules/${KVER}/build"
|
||||
|
||||
msg() { printf '\n==> %s\n' "$*"; }
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
need_cmd() { command -v "$1" >/dev/null 2>&1 || die "Missing command: $1"; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "Run with sudo"
|
||||
[[ -d "$KBUILD" ]] || die "Missing kernel headers/build dir: $KBUILD"
|
||||
[[ -d $KBUILD ]] || die "Missing kernel headers/build dir: $KBUILD"
|
||||
|
||||
need_cmd curl
|
||||
need_cmd dpkg-source
|
||||
@@ -52,13 +55,13 @@ cd "$WORKDIR"
|
||||
msg "Downloading exact source package for ${SRCNAME} ${SRCVER}"
|
||||
curl -fL "${BASE_URL}/${ORIG}" -o "${ORIG}"
|
||||
curl -fL "${BASE_URL}/${DIFF}" -o "${DIFF}"
|
||||
curl -fL "${BASE_URL}/${DSC}" -o "${DSC}"
|
||||
curl -fL "${BASE_URL}/${DSC}" -o "${DSC}"
|
||||
|
||||
msg "Extracting source"
|
||||
dpkg-source -x "${DSC}"
|
||||
|
||||
SRCDIR="$(find . -maxdepth 1 -mindepth 1 -type d -name "${SRCNAME}-*" | head -n1)"
|
||||
[[ -n "${SRCDIR}" ]] || die "Could not find extracted source directory"
|
||||
[[ -n ${SRCDIR} ]] || die "Could not find extracted source directory"
|
||||
cd "${SRCDIR}"
|
||||
|
||||
[[ -f drivers/net/usb/cdc_ncm.c ]] || die "cdc_ncm.c not found in source tree"
|
||||
@@ -127,7 +130,7 @@ msg "Preparing out-of-tree build dir"
|
||||
mkdir -p buildmod
|
||||
cp drivers/net/usb/cdc_ncm.c buildmod/
|
||||
|
||||
cat > buildmod/Makefile <<'EOF'
|
||||
cat >buildmod/Makefile <<'EOF'
|
||||
obj-m += cdc_ncm.o
|
||||
ccflags-y += -Wno-error
|
||||
|
||||
@@ -142,7 +145,7 @@ msg "Building patched module"
|
||||
cd buildmod
|
||||
make -C "$KBUILD" M="$PWD" modules
|
||||
PATCHED_KO="$PWD/cdc_ncm.ko"
|
||||
[[ -f "$PATCHED_KO" ]] || die "Patched cdc_ncm.ko was not built"
|
||||
[[ -f $PATCHED_KO ]] || die "Patched cdc_ncm.ko was not built"
|
||||
|
||||
msg "Patched module info"
|
||||
modinfo "$PATCHED_KO" | sed -n '1,20p'
|
||||
|
||||
Reference in New Issue
Block a user