Rack: Sliger fronts in red, white and blue, and the UniFi brush panel
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 53s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m0s
Lint / Python (flake8) (pull_request) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 1m7s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m52s
Test / Python Tests (pytest) (pull_request) Successful in 1m51s

From the rack photo on the wiki, the three Sligers have their coloured
perforated fronts fitted, not the faceplate-off view: Front drawings
"Sliger CX4712 4U, red/white/blue front" draw the hex-perforated panel
(about 20 rows of holes, the drive cages faint behind it), black rack ears,
the power button with Gandalf's status ring, and the USB recess.

UniFi UACC-Rack-Panel-Brush-1U is drawn from Ubiquiti's product images:
silver face, ear plates with rounded slots, a recessed chamfered frame and
two rows of bristles meeting at a seam.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-11 01:51:29 -05:00
co-authored by Claude Opus 5.5
parent 64ef4222d0
commit 7408b1feca
5 changed files with 98 additions and 2 deletions
+3
View File
@@ -156,6 +156,9 @@ The rack front, drawn from the rack units set on cutsheet items, top first.
with a DAC attached; its round dot is Gandalf's link mark for enp1s0 (the slot is PCI bus 1), not an
LED on the card. UniFi desk switches (Flex Mini, Flex) on a shelf are drawn with their live faceplate
at their real width. Anything else is a labelled box.
- **More drawn fronts**: the Sliger CX4712 with its red, white or blue perforated front fitted (as in
the rack photo, drive cages faint behind the mesh), and the UniFi brush panel
(UACC-Rack-Panel-Brush-1U, from Ubiquiti's product images).
- **Gear resting on other gear**: a shelf item with Front drawing "No shelf: gear resting on what is
below" draws no tray, so a switch lying on top of the NVR is a "shelf" in the unit above the NVR whose
contents sit directly on it.
+5 -1
View File
@@ -37,7 +37,11 @@ CHASSIS = {
'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)',
'sliger-cx4712': 'Sliger CX4712 4U (10-bay, faceplate off)',
'sliger-cx4712-red': 'Sliger CX4712 4U, red front',
'sliger-cx4712-white': 'Sliger CX4712 4U, white front',
'sliger-cx4712-blue': 'Sliger CX4712 4U, blue front',
'uacc-brush-1u': 'UniFi Rack Panel Brush 1U (UACC-Rack-Panel-Brush-1U)',
'rosewill-rsv-l4500u': 'Rosewill RSV-L4500U 4U (15-bay)',
'zimaboard-832': 'IceWhale ZimaBoard 832 (shelf gear)',
'zimaboard-832-cx3': 'IceWhale ZimaBoard 832 + ConnectX-3 SFP+ in PCIe (shelf gear)',
+74
View File
@@ -106,12 +106,86 @@ const RACK_FRONTS = {
},
},
// Sliger CX4712 with its coloured front fitted, as in the rack photo (wiki: rack-2026.jpg):
// a hex-perforated panel over most of the face, the drive cages faintly visible through it,
// black rack ears, and a power button over two USB ports at the top right.
'sliger-cx4712-red': sligerFront('#c8161d', '#e9e5e0'),
'sliger-cx4712-white': sligerFront('#eceae6', '#9aa0a6'),
'sliger-cx4712-blue': sligerFront('#1f5fae', '#e9e5e0'),
// Ubiquiti UACC-Rack-Panel-Brush-1U, from Ubiquiti's product images (store.ui.com, UACC-Rack-Panel
// variant Brush 1U): a light silver face with separate ear plates (two rounded slots each), a
// recessed chamfered frame around the opening (shadowed along the top, bright bevel along the
// bottom), and two rows of black bristles meeting at a seam along the middle.
'uacc-brush-1u': {
label: 'UniFi Rack Panel Brush 1U (UACC-Rack-Panel-Brush-1U)', units: 1, width: 1100, height: 105,
draw() {
// Bristle tufts: thin strands of varying shade, deterministic so the drawing never flickers
const strands = (y0, y1, seed) => Array.from({ length: 300 }, (_, i) => {
const n = (i * 7919 + seed * 104729) % 97;
return `<rect x="${94 + i * 3.04}" y="${y0 + (n % 3)}" width="${1.2 + (n % 4) * 0.35}" height="${y1 - y0 - (n % 3)}"
style="fill:${['#1b1d20', '#23262a', '#2c2f33', '#151719'][n % 4]}"/>`;
}).join('');
const slot = x => `<rect class="ua-ear-slot" x="${x - 7}" y="22" width="14" height="10" rx="5"/>
<rect class="ua-ear-slot" x="${x - 7}" y="73" width="14" height="10" rx="5"/>`;
return `<defs><linearGradient id="fp-metal" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#e4e4e6"/><stop offset=".08" stop-color="#d6d6d8"/>
<stop offset=".92" stop-color="#cfcfd2"/><stop offset="1" stop-color="#a9a9ad"/>
</linearGradient></defs>
<rect class="fp-body" width="1100" height="105" rx="3"/>
<rect class="ua-ear-plate" x="0" y="1" width="32" height="103" rx="3"/>
<rect class="ua-ear-plate" x="1068" y="1" width="32" height="103" rx="3"/>
<rect class="ua-ear-seam" x="31" y="1" width="1.5" height="103"/>
<rect class="ua-ear-seam" x="1067.5" y="1" width="1.5" height="103"/>
${slot(16)}${slot(1084)}
<rect class="ub-frame" x="80" y="20" width="940" height="66" rx="7"/>
<rect class="ub-frame-shadow" x="84" y="24" width="932" height="7" rx="3"/>
<rect class="ub-frame-bevel" x="84" y="77" width="932" height="5" rx="2.5"/>
<rect class="ub-opening" x="92" y="30" width="916" height="47" rx="2"/>
${strands(31, 53, 1)}${strands(54, 76, 2)}
<rect class="ub-seam" x="92" y="53" width="916" height="1.3"/>`;
},
},
// Generic servers, for gear without a specific drawing
'server-1u': genericServer(1, 4),
'server-2u': genericServer(2, 12),
'server-4u': genericServer(4, 24),
};
function sligerFront(colour, hole) {
const W = 1100, H = 420;
// Hex perforations: a pattern of hexagons cut out of the panel through a mask
const hex = (cx, cy, r) => Array.from({ length: 6 }, (_, i) => {
const a = Math.PI / 6 + i * Math.PI / 3;
return `${(cx + r * Math.cos(a)).toFixed(1)},${(cy + r * Math.sin(a)).toFixed(1)}`;
}).join(' ');
// Hole size and pitch from the rack photo: about 20 rows of holes over the panel's height
const r = 6.9, dx = 18, dy = 15.6;
const holes = [];
for (let row = 0, y = 46; y < H - 34; row++, y += dy) {
for (let x = 82 + (row % 2 ? dx / 2 : 0); x < 925; x += dx) holes.push(`<polygon points="${hex(x, y, r)}"/>`);
}
const bays = Array.from({ length: 10 }, (_, i) => `<rect class="rf-sl-bay" x="${140 + i * 74}" y="120" width="44" height="250" rx="3" style="fill:${hole}"/>`).join('');
// The cages show only faintly through the mesh
return {
label: 'Sliger CX4712 4U (10-bay)', units: 4, width: W, height: H,
draw({ status }) {
const id = `sl-${colour.slice(1)}`;
return `<defs><mask id="${id}"><rect width="${W}" height="${H}" fill="#fff"/><g fill="#000">${holes.join('')}</g></mask></defs>
<rect class="rf-sl-inside" x="70" y="30" width="870" height="${H - 60}"/>${bays}
<rect class="rf-sl-ear" x="0" y="0" width="70" height="${H}" rx="4"/>
<rect class="rf-sl-ear" x="${W - 70}" y="0" width="70" height="${H}" rx="4"/>
<rect x="52" y="10" width="${W - 104}" height="${H - 20}" rx="8" style="fill:${colour}" mask="url(#${id})"/>
<rect class="rf-sl-edge" x="52" y="10" width="${W - 104}" height="${H - 20}" rx="8"/>
<circle class="rf-sl-button" cx="988" cy="88" r="17"/>
<circle class="rf-power-ring" cx="988" cy="88" r="12" style="stroke:${frontLed(status)}"/>
<rect class="rf-sl-io" x="966" y="138" width="44" height="74" rx="4"/>
${usbA(977, 146)}${usbA(977, 168)}${usbC(980, 192)}`;
},
};
}
function genericServer(units, bays) {
const H = 105 * units;
return {
+14
View File
@@ -1630,3 +1630,17 @@ body.fp-marks-off .fp-svg .fp-mark { display: none; }
.cx-tab { fill: none; stroke: #c9ccd0; stroke-width: 9; stroke-linejoin: round; }
.cx-link { stroke: #101418; stroke-width: 3; }
.fp-speedbar { fill: #b3b5ba; }
.rf-sl-inside { fill: #141619; }
.rf-sl-ear { fill: #17181a; }
.rf-sl-edge { fill: none; stroke: rgba(0,0,0,.35); stroke-width: 2; }
.rf-sl-button { fill: #c9ccd0; stroke: #8a8e93; stroke-width: 2; }
.rf-sl-bay { opacity: .45; }
.rf-sl-io { fill: #0e0f11; }
.ua-ear-plate { fill: #d3d5d8; stroke: #b2b4b8; stroke-width: 1; }
.ua-ear-seam { fill: #9fa1a6; }
.ua-ear-slot { fill: #2a2c30; }
.ub-frame { fill: #bfc1c5; stroke: #a5a7ac; stroke-width: 1; }
.ub-frame-shadow { fill: #8c8e93; }
.ub-frame-bevel { fill: #f1f2f3; }
.ub-opening { fill: #0d0e10; }
.ub-seam { fill: #050506; }
+2 -1
View File
@@ -370,7 +370,8 @@ class TestChassis:
assert all(cs.CHASSIS.values())
assert {'sliger-cx4712', 'rosewill-rsv-l4500u', 'unvr-pro', 'uacc-patch-24',
'usw-pro-max-24-poe', 'usw-pro-max-48-poe', 'zimaboard-832', 'zimaboard-832-cx3',
'resting'} <= set(cs.CHASSIS)
'resting', 'uacc-brush-1u', 'sliger-cx4712-red', 'sliger-cx4712-white',
'sliger-cx4712-blue'} <= set(cs.CHASSIS)
class TestFacingAndSlots: