From ce12dede5fbc97edc72b00300145e21525f31e8c Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Wed, 11 Nov 2026 00:01:12 -0500 Subject: [PATCH] Faceplates: USW Pro Max 24 PoE and 48 PoE Both laid out from the CC0 NetBox devicetype-library images, with jack centres measured from them: the 24 has one row of 24 RJ45 in groups of 8 (17-24 at 2.5G) and 2 SFP+; the 48 has two rows, odd ports on top, in groups of 16 (33-48 at 2.5G) and 4 SFP+ (49-52). The "2.5G" and "10G" bars under the faster ports are drawn too (fpSpeedBar). Registered as USPM24P/USPM48P with USPM24/USPM48 aliases, and as Front drawing options so the new switch draws on the rack before it is adopted. Co-Authored-By: Claude Opus 5.5 --- README.md | 10 ++++++++ cutsheet.py | 2 ++ static/faceplates.js | 58 ++++++++++++++++++++++++++++++++++++++++++ static/fronts.js | 7 ++++- static/style.css | 2 ++ tests/test_cutsheet.py | 3 ++- 6 files changed, 80 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b41cd1f..4d9ee87 100644 --- a/README.md +++ b/README.md @@ -704,6 +704,16 @@ gandalf/ 4. Restart monitor: `systemctl restart gandalf-monitor` 5. For SFP DOM / ethtool: ensure the host is SSH-accessible from the Pulse worker +### Faceplates included + +UniFi gear drawn with live ports: UDM Pro (`UDMPRO`), USW Pro 24 PoE (`US24PRO`), USW Aggregation +(`USL8A`), USW Flex (`USF5P`), USW Flex Mini (`USMINI`), USP PDU Pro (`USPPDUP`), UPS 2U Pro (`USPDA2B`), +USW Pro Max 24 PoE (`USPM24P`, also `USPM24`) and USW Pro Max 48 PoE (`USPM48P`, also `USPM48`), plus the +UniFi NVR Pro (`UNVRPRO`), which UniFi Network doesn't report and so is picked by Front drawing. The Pro +Max switches also have Front drawing options, so a new switch draws correctly on the rack before it is +adopted. If UniFi reports a Pro Max under a different model code, add it as another alias at the end of +`static/faceplates.js`. + ### Adding a New Device Faceplate Find the UniFi model code on the device page, then add an entry to `FACEPLATES` in `static/faceplates.js`. diff --git a/cutsheet.py b/cutsheet.py index cd9be22..e657633 100644 --- a/cutsheet.py +++ b/cutsheet.py @@ -30,6 +30,8 @@ RACK_UNITS_MAX = 60 CHASSIS = { 'unvr-pro': 'UniFi NVR Pro (UNVR Pro)', 'uacc-patch-24': 'UniFi Patch Panel Blank 24 (UACC-Rack-Panel-Patch-Blank-24)', + 'usw-pro-max-24-poe': 'UniFi Switch Pro Max 24 PoE (until UniFi reports it)', + 'usw-pro-max-48-poe': 'UniFi Switch Pro Max 48 PoE (until UniFi reports it)', 'sliger-cx4712': 'Sliger CX4712 4U (10-bay)', 'rosewill-rsv-l4500u': 'Rosewill RSV-L4500U 4U (15-bay)', 'server-1u': 'Generic 1U server', diff --git a/static/faceplates.js b/static/faceplates.js index f2cab7c..fc33088 100644 --- a/static/faceplates.js +++ b/static/faceplates.js @@ -225,6 +225,12 @@ function fpQuadScreen(x, y, s) { `; } +// The bar UniFi prints under a group of faster ports, with its speed ("2.5G", "10G") +function fpSpeedBar(x0, x1, y, text) { + return ` + ${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 } = {}) { @@ -350,6 +356,53 @@ const FACEPLATES = { }, }, + // USW Pro Max 24 PoE, laid out from the CC0 NetBox devicetype-library image + // (ubiquiti-unifi-switch-pro-max-24-poe.front.png; jack centres measured from it): + // one row of 24 RJ45 in three groups of 8 (17-24 are 2.5G), then 2 SFP+. + USPM24P: { + width: 1167, height: 117, frameClass: 'fp-scroll', scrollX: 560, + label: 'USW Pro Max 24 PoE', + legend: [...LEGEND_RJ45, ['white', '10G'], ['poe', 'PoE in use']], + roles: { ...roleRange(1, 16, 'RJ45 1G'), ...roleRange(17, 24, 'RJ45 2.5G'), 25: 'SFP+', 26: 'SFP+' }, + ports: [ + ...[118.5, 427, 734.5].flatMap((x0, g) => Array.from({ length: 8 }, (_, i) => { + const x = x0 + i * 37.35; + return { idx: g * 8 + i + 1, type: 'rj45', x, y: 74, scale: 1.1, label: [x, 100] }; + })), + ...[1030, 1071].map((x, i) => ({ idx: 25 + i, type: 'sfp', x, y: 62, w: 38, h: 27, + led: [x + 19, 54], label: [x + 19, 100] })), + ], + decor() { + return `${fpVents([[97, 293], [301, 498], [506, 703], [710, 906], [914, 1110]], 9, 3)} + ${fpQuadScreen(14, 27, 60)}${fpModel(44, 103, 'USW', 'Pro Max', { bolt: true })} + ${[98, 407, 714].map(x => ``).join('')} + ${fpSpeedBar(714, 1017, 107, '2.5G')}${fpSpeedBar(1030, 1109, 107, '10G')} + ${fpReset(1128, 101)}`; + }, + }, + + // USW Pro Max 48 PoE, from ubiquiti-unifi-switch-pro-max-48-poe.front.png (CC0, NetBox): + // two rows, odd ports on top, three groups of 16 (33-48 are 2.5G), then 4 SFP+ (49-52). + USPM48P: { + width: 1169, height: 114, frameClass: 'fp-scroll', scrollX: 560, + label: 'USW Pro Max 48 PoE', + legend: [...LEGEND_RJ45, ['white', '10G'], ['poe', 'PoE in use']], + roles: { ...roleRange(1, 32, 'RJ45 1G'), ...roleRange(33, 48, 'RJ45 2.5G'), ...roleRange(49, 52, 'SFP+') }, + ports: [ + ...rj45Pairs([122, 425.5, 731].flatMap(x0 => Array.from({ length: 8 }, (_, i) => x0 + i * 36.8)), + { top: 38, bottom: 68, topLabel: 15, bottomLabel: 97 }), + ...sfpPairs([1033, 1075], { first: 49, top: 23, bottom: 60, w: 37, h: 27, ledY: 56, ledGap: 9, + topLabel: 15, bottomLabel: 97 }), + ], + decor() { + return `${fpVents([[97, 293], [301, 498], [506, 703], [710, 906], [914, 1110]], 7, 3)} + ${fpQuadScreen(14, 25, 60)}${fpModel(44, 101, 'USW', 'Pro Max', { bolt: true })} + ${[101, 405, 711].map(x => ``).join('')} + ${fpSpeedBar(711, 1009, 104, '2.5G')}${fpSpeedBar(1033, 1112, 104, '10G')} + ${fpReset(1130, 99)}`; + }, + }, + // No quick start guide was available: layout from a reference image. LED colors // are taken from the SFP+ ports of the USW Pro 24 guide. USL8A: { @@ -585,3 +638,8 @@ function faceplateScrollLeft(model, frame) { const spec = FACEPLATES[model]; return spec.scrollX ? (frame.scrollWidth / spec.width) * spec.scrollX : 0; } + +// Same fronts under the other model codes UniFi uses for these switches (non-PoE variants +// draw the same; PoE marks simply never show) +FACEPLATES.USPM24 = FACEPLATES.USPM24P; +FACEPLATES.USPM48 = FACEPLATES.USPM48P; diff --git a/static/fronts.js b/static/fronts.js index e8af622..b4aaa4d 100644 --- a/static/fronts.js +++ b/static/fronts.js @@ -134,7 +134,12 @@ function genericServer(units, bays) { } // Front drawings that are full faceplates in faceplates.js (same parts as the UniFi gear) -const CHASSIS_FACEPLATES = { 'unvr-pro': 'UNVRPRO' }; +const CHASSIS_FACEPLATES = { + 'unvr-pro': 'UNVRPRO', + // For a new switch before UniFi reports it (or under a model code not mapped yet) + 'usw-pro-max-24-poe': 'USPM24P', + 'usw-pro-max-48-poe': 'USPM48P', +}; // The markup for one item's drawn front, or '' when the key is unknown function rackFrontSvg(key, ctx) { diff --git a/static/style.css b/static/style.css index d3362af..59a1aa5 100644 --- a/static/style.css +++ b/static/style.css @@ -1584,3 +1584,5 @@ body.fp-marks-off .fp-svg .fp-mark { display: none; } .rack-pp-bad .ua-mark { stroke: #e0312b; stroke-width: 3; } .rack-pp-doc .ua-mark { stroke: #4c5662; stroke-width: 2; } .rack-pp-port.rack-hl .ua-mark { fill: rgba(0,212,255,.25); stroke: #00b8e6; stroke-width: 3.5; } + +.fp-speedbar { fill: #b3b5ba; } diff --git a/tests/test_cutsheet.py b/tests/test_cutsheet.py index 0023c38..ee0e3cc 100644 --- a/tests/test_cutsheet.py +++ b/tests/test_cutsheet.py @@ -368,4 +368,5 @@ class TestChassis: def test_every_drawing_has_a_label(self): assert all(cs.CHASSIS.values()) - assert {'sliger-cx4712', 'rosewill-rsv-l4500u', 'unvr-pro', 'uacc-patch-24'} <= set(cs.CHASSIS) + assert {'sliger-cx4712', 'rosewill-rsv-l4500u', 'unvr-pro', 'uacc-patch-24', + 'usw-pro-max-24-poe', 'usw-pro-max-48-poe'} <= set(cs.CHASSIS)