diff --git a/README.md b/README.md index 43906fa..b41cd1f 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,8 @@ The rack front, drawn from the rack units set on cutsheet items, top first. - **Drawn fronts**: gear without a live faceplate can pick a **Front drawing** on its cutsheet item: UniFi NVR Pro (a full faceplate, `UNVRPRO` in `static/faceplates.js`, laid out from the CC0 NetBox device library image), Sliger CX4712 4U (10 numbered bays), Rosewill RSV-L4500U 4U (perforated fan - door), or a generic 1U/2U/4U server. The power light shows the linked device's live status. Case + door), the UniFi blank 24-port keystone patch panel (UACC-Rack-Panel-Patch-Blank-24: keystone jacks + appear where cables are documented), or a generic 1U/2U/4U server. The power light shows the linked device's live status. Case drawings live in `static/fronts.js`. - **Shelves**: an item can sit on a shelf item instead of having a rack unit. The shelf draws what is on it side by side (mini PCs, desk switches), each with its status and a link to its page. diff --git a/cutsheet.py b/cutsheet.py index 645b121..cd9be22 100644 --- a/cutsheet.py +++ b/cutsheet.py @@ -29,6 +29,7 @@ RACK_UNITS_MAX = 60 # Drawn fronts for gear without a live faceplate (keys of RACK_FRONTS in static/fronts.js) CHASSIS = { 'unvr-pro': 'UniFi NVR Pro (UNVR Pro)', + 'uacc-patch-24': 'UniFi Patch Panel Blank 24 (UACC-Rack-Panel-Patch-Blank-24)', '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/rack.js b/static/rack.js index d9b0130..d42e686 100644 --- a/static/rack.js +++ b/static/rack.js @@ -105,6 +105,48 @@ function shelfHtml(item) { `; } +// Ubiquiti UACC-Rack-Panel-Patch-Blank-24, laid out from the CC0 NetBox devicetype-library image +// (elevation-images/Ubiquiti/ubiquiti-uacc-rack-panel-patch-blank-24.front.png): a grey steel face, +// darker ears with oval holes, 24 square keystone openings in one row with the cable-support bar +// behind them, and a narrow cable slot at each end. A port with a documented cable gets a keystone jack. +function uaccPanelSvg(item) { + const W = 1178, H = 108, x0 = 107, pitch = 40.5, ow = 34, oy = 36, oh = 40; + const count = Math.min(item.port_count || 24, 24); + const ports = []; + for (let n = 1; n <= count; n++) { + const x = x0 + (n - 1) * pitch; + const m = item.ports[String(n)]; + const label = item.labels[String(n)] || ''; + const title = [`Port ${n}`, label && `"${label}"`, m ? m.text : 'not on the cutsheet'].filter(Boolean).join(' · '); + const attrs = m ? ` data-run="${m.run.join(' ')}" data-cable="${m.cable_id}" tabindex="0" role="link"` : ''; + const jack = m ? ` + ` : ''; + ports.push(` + ${esc(title)} + + + ${jack} + + ${n} + ${label ? `${esc(label.length > 7 ? `${label.slice(0, 6)}…` : label)}` : ''} + + `); + } + const hole = (x, y) => ``; + return ` + ${esc(item.name)} + + + ${hole(8, 12)}${hole(8, 83)}${hole(W - 30, 12)}${hole(W - 30, 83)} + + + + + ${ports.join('')} + `; +} + function plateHtml(item) { const unlinked = item.kind === 'device' && !item.linked; const sub = [item.device !== item.name ? item.device : '', KIND_TEXT[item.kind] || item.kind, item.location, @@ -129,6 +171,7 @@ function itemHtml(item) { else if (item.switch && isRackFaceplate(item.switch.model)) body = switchHtml(item); else if (CHASSIS_FACEPLATES[item.chassis]) body = switchHtml(item, CHASSIS_FACEPLATES[item.chassis]); else if (item.chassis && RACK_FRONTS[item.chassis]) body = rackFrontSvg(item.chassis, item); + else if (item.kind === 'patch_panel' && item.chassis === 'uacc-patch-24') body = uaccPanelSvg(item); else if (item.kind === 'patch_panel') body = panelSvg(item); else if (item.kind === 'shelf') body = shelfHtml(item); else body = plateHtml(item); diff --git a/static/style.css b/static/style.css index 75eca17..d3362af 100644 --- a/static/style.css +++ b/static/style.css @@ -1567,3 +1567,20 @@ body.fp-marks-off .fp-svg .fp-mark { display: none; } .rf-unvr-dot { fill: #ffffff; stroke: #c4c6ca; } .rf-screen { fill: #0f1726; } .rf-screen-ring { fill: none; stroke-width: 3; } + +/* Ubiquiti UACC-Rack-Panel-Patch-Blank-24 (static/rack.js uaccPanelSvg) */ +.ua-ear { fill: #6f7277; } +.ua-hole { fill: #15181c; } +.ua-face { fill: #8e9095; stroke: #6c6f74; } +.ua-lip { fill: #a6a8ad; } +.ua-open { fill: #17191d; } +.ua-bar { fill: #9a9ca1; } +.ua-keystone { fill: #101113; stroke: #2b2d31; } +.ua-keystone-hole { fill: #2f3236; } +.ua-num { fill: #2b2d31; font-size: 9px; font-family: Arial, Helvetica, sans-serif; } +.ua-label { fill: #1f2124; font-size: 8.5px; font-weight: 700; font-family: Arial, Helvetica, sans-serif; } +.ua-mark { fill: none; stroke: transparent; } +.rack-pp-ok .ua-mark { stroke: #00b860; stroke-width: 2.5; } +.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; } diff --git a/tests/test_cutsheet.py b/tests/test_cutsheet.py index ec16f20..0023c38 100644 --- a/tests/test_cutsheet.py +++ b/tests/test_cutsheet.py @@ -367,4 +367,5 @@ class TestChassis: cs.validate_item({'name': 'x', 'chassis': 'tardis'}) def test_every_drawing_has_a_label(self): - assert all(cs.CHASSIS.values()) and 'sliger-cx4712' in cs.CHASSIS and 'rosewill-rsv-l4500u' in cs.CHASSIS + assert all(cs.CHASSIS.values()) + assert {'sliger-cx4712', 'rosewill-rsv-l4500u', 'unvr-pro', 'uacc-patch-24'} <= set(cs.CHASSIS)