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)