Faceplates: model labels as printed on the units
Lint / JS (eslint) (push) Successful in 16s
Lint / Python (flake8) (push) Successful in 1m6s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / Python (flake8) (pull_request) Successful in 52s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 53s
Security / Python Security (bandit) (push) Successful in 54s
Test / Python Tests (pytest) (push) Successful in 2m11s
Test / Python Tests (pytest) (pull_request) Successful in 2m16s

The label under each UniFi screen was one weight, left-aligned, and on the
UDM read "Dream Machine Pro". Checked against the NetBox reference photos
(and the UDM against the real unit), the labels are now centred under the
screen with the first word bold and the rest light: "UDM Pro",
"USW Pro" with its PoE mark, "USW", "NVR Pro", "USP PDU Pro". One helper
(fpModel) draws them all, so the NVR no longer looks bolder than the
switches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-10 23:36:23 -05:00
co-authored by Claude Opus 5.5
parent 565fdd81de
commit 3c9e5fd593
2 changed files with 24 additions and 7 deletions
+20 -6
View File
@@ -225,7 +225,21 @@ function fpQuadScreen(x, y, s) {
</g>`;
}
const fpBrand = (x, y, text) => `<text class="fp-brand" x="${x}" y="${y}">${text}</text>`;
// The model name printed under a UniFi screen: centred on the screen, first word bold and
// second word light ("USW Pro", "NVR Pro"), as on the real units. `bolt` adds the PoE mark.
function fpModel(cx, y, bold, light = '', { size = 7.5, bolt = false } = {}) {
const chars = bold.length + (light ? light.length + 1 : 0);
const boltW = bolt ? size * 1.3 : 0;
const textW = chars * size * 0.6;
const x = cx - boltW / 2;
const mark = bolt
? `<g transform="translate(${x + textW / 2 + size * 0.75} ${y - size * 0.4})">
<circle class="fp-brand-bolt-ring" r="${size * 0.62}"/>
<path class="fp-brand-bolt" transform="scale(${size / 11})" d="M1 -5 L-3 1 H0 L-1 5 L3 -1 H0 Z"/></g>`
: '';
return `<text class="fp-brand" x="${x}" y="${y}" text-anchor="middle" style="font-size:${size}px">${bold}${light
? `<tspan class="fp-brand-light" dx="${size * 0.25}">${light}</tspan>` : ''}</text>${mark}`;
}
// Two rows of jacks, odd numbers on top: how UniFi rack switches number ports
function rj45Pairs(columns, rows) {
@@ -270,7 +284,7 @@ const FACEPLATES = {
<rect class="fp-screen" x="15" y="25" width="56" height="56" rx="3"/>
<circle class="fp-screen-ring" cx="43" cy="51" r="13"/>
<circle class="fp-screen-dot" cx="43" cy="51" r="4.5"/>
${fpBrand(16, 94, 'Dream Machine Pro')}
${fpModel(43, 94, 'UDM', 'Pro')}
<rect class="fp-bay" x="447" y="19" width="259" height="66" rx="1.5"/>
<rect class="fp-bay-edge" x="447.8" y="19.8" width="3.5" height="64.4"/>
<circle class="fp-bay-led" cx="681" cy="52" r="1.6"/>
@@ -305,7 +319,7 @@ const FACEPLATES = {
<circle class="fp-bay-led" cx="${x + 255}" cy="${y + 38}" r="2.2"/>
<text class="fp-label" x="${x + 141}" y="${y + 89}" text-anchor="middle">${n}</text>`;
return `${fpVents(spans, 8, 4)}${fpVents(spans, 216, 4)}
${fpQuadScreen(14, 33, 62)}${fpBrand(26, 112, 'NVR Pro')}
${fpQuadScreen(14, 33, 62)}${fpModel(45, 111, 'NVR', 'Pro')}
${[97, 384, 669].map((x, i) => bay(x, 27, i + 1)).join('')}
${[12, 299, 585, 871].map((x, i) => bay(x, 125, i + 4)).join('')}
<rect class="fp-block" x="1014" y="52" width="104" height="44" rx="1"/>
@@ -328,7 +342,7 @@ const FACEPLATES = {
],
decor() {
return `${fpVents([[98, 298], [304, 504], [512, 710], [717, 917], [923, 1122]], 3)}
${fpQuadScreen(15, 23, 62)}${fpBrand(17, 99, 'USW Pro')}
${fpQuadScreen(15, 23, 62)}${fpModel(46, 99, 'USW', 'Pro', { bolt: true })}
<rect class="fp-block" x="563" y="19" width="234" height="69" rx="1"/>
<rect class="fp-block" x="797.5" y="19" width="233" height="69" rx="1"/>
<rect class="fp-divider" x="1083" y="45" width="39" height="16"/>
@@ -350,7 +364,7 @@ const FACEPLATES = {
const dividers = [876.4, 925.6, 975.6, 1025]
.map(x => `<rect class="fp-divider" x="${x}" y="46.6" width="36" height="15"/>`).join('');
return `${fpVents([[91, 280], [287, 476], [482, 671], [678, 867], [874, 1062]], 7)}
${fpQuadScreen(12, 25, 58)}${fpBrand(28, 96, 'USW')}
${fpQuadScreen(12, 25, 58)}${fpModel(41, 96, 'USW')}
${dividers}
<rect class="fp-speed-bar" x="874" y="99" width="188" height="4" rx="1"/>
${fpReset(1078.6, 95)}`;
@@ -425,7 +439,7 @@ const FACEPLATES = {
<rect class="fp-ear-hole" x="${x + 13}" y="189" width="27" height="17" rx="8.5"/>`;
return `${ear(0)}${ear(1126)}
<rect class="fp-body-metal" x="48" y="1" width="1078" height="213" rx="1"/>
${fpQuadScreen(61, 132, 56)}${fpBrand(68, 199, 'USP PDU Pro')}
${fpQuadScreen(61, 132, 56)}${fpModel(89, 199, 'USP', 'PDU Pro')}
<circle class="fp-button" cx="1053" cy="53" r="4.5"/>
<rect class="fp-block" x="977" y="160" width="39" height="32.5" rx="1"/>
<rect class="fp-block" x="1032" y="130" width="41" height="62.5" rx="1"/>
+4 -1
View File
@@ -1190,7 +1190,10 @@ tr.device-row.is-selected { background: var(--bg-tertiary); box-shadow: inset 3p
.fp-screen { fill: #0c1020; stroke: #2a2c34; stroke-width: 1; }
.fp-screen-ring{ fill: none; stroke: #3f7bd9; stroke-width: 2.2; }
.fp-screen-dot { fill: #dfe8f8; }
.fp-brand { fill: #55565c; font: 600 7.5px Arial, Helvetica, sans-serif; }
.fp-brand { fill: #2e2f33; font: 700 7.5px Arial, Helvetica, sans-serif; }
.fp-brand-light{ font-weight: 400; fill: #55565c; }
.fp-brand-bolt-ring { fill: #2e2f33; }
.fp-brand-bolt { fill: #e4e4e6; }
.fp-bay { fill: url(#fp-bay-face); stroke: #54555a; stroke-width: 1; }
.fp-bay-edge { fill: #bdbdc1; }
.fp-bay-led { fill: #3a3a3e; }