3D rooms: cabinets apart from servers, cables you can see
The cabinet and the servers were both dark grey, and the cables behind an opaque cabinet could only be seen from the back. Cabinets are now light steel and see-through like a perforated door; every server has a solid dark chassis between its front and its back, so it stands out from the cabinet. Cables are thicker and lit, each switch's cables run up their own lane beside the rack instead of on top of one another, and the switch colours start away from the selection's blue. The legend says the cables are at the back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
44ac453bde
commit
dc1ca9f2c6
2 files changed
+14
-8
No files matched your search
@@ -25,7 +25,7 @@ const cablesView = {
|
||||
get() { try { return localStorage.getItem('rollcall.cables') !== 'off'; } catch (e) { return true; } },
|
||||
set(v) { try { localStorage.setItem('rollcall.cables', v ? 'on' : 'off'); } catch (e) { /* the choice is not kept */ } },
|
||||
};
|
||||
const SWITCH_COLORS = ['#4f8cff', '#f2a33a', '#3fbf8f', '#c470e0', '#e05d5d', '#46b8d1', '#b5c43a', '#e07fb0'];
|
||||
const SWITCH_COLORS = ['#f2a33a', '#3fbf8f', '#c470e0', '#e05d5d', '#46b8d1', '#b5c43a', '#e07fb0', '#4f8cff']; // the selection's blue last
|
||||
|
||||
function loadThree() {
|
||||
if (!loadThree.p) loadThree.p = import('./vendor/three/three.module.min.js').catch(e => { loadThree.p = null; throw e; });
|
||||
@@ -121,13 +121,14 @@ function build(site, room, racks, blocks) {
|
||||
g.position.set((r.x + f.w / 2) * TILE, 0, (r.y + f.h / 2) * TILE);
|
||||
g.rotation.y = { N: 0, S: Math.PI, E: -Math.PI / 2, W: Math.PI / 2 }[r.facing]; // the front is local -z
|
||||
const st = rackState(site, r);
|
||||
const body = new T.Mesh(box(TILE - 0.02, h, 2 * TILE - 0.02), mat('rack' + r.auto, () => new T.MeshStandardMaterial({ color: 0x2a2f36, roughness: 0.8, metalness: 0.2, transparent: r.auto, opacity: r.auto ? 0.5 : 1 })));
|
||||
// the cabinet: steel, see-through like a perforated door, so the servers inside and the cables behind show
|
||||
const body = new T.Mesh(box(TILE - 0.02, h, 2 * TILE - 0.02), mat('rack' + r.auto, () => new T.MeshStandardMaterial({ color: 0x7d8794, roughness: 0.6, metalness: 0.3, transparent: true, opacity: r.auto ? 0.18 : 0.32, depthWrite: false })));
|
||||
body.position.y = h / 2;
|
||||
body.userData = { rack: r.name, tip: `${r.name} · ${st.ms.length} machines · ${st.used}/${H}U used${r.auto ? ' · not placed yet' : ''}` };
|
||||
const edges = new T.LineSegments(keep(new T.EdgesGeometry(body.geometry)), lineMat(sel ? '--accent' : '--muted'));
|
||||
edges.position.copy(body.position);
|
||||
// the rails' face, lighter than the cabinet: where the front is and what is empty
|
||||
const face = new T.Mesh(box(RAIL, H * U_M, 0.004), mat('face', () => new T.MeshStandardMaterial({ color: 0x3c434c, roughness: 0.9 })));
|
||||
const face = new T.Mesh(box(RAIL, H * U_M, 0.004), mat('face', () => new T.MeshStandardMaterial({ color: 0x59626d, roughness: 0.9, transparent: true, opacity: 0.55, depthWrite: false })));
|
||||
face.position.set(0, PLINTH + H * U_M / 2, -TILE + 0.008);
|
||||
const back = face.clone(); back.position.z = TILE - 0.008;
|
||||
g.add(body, edges, face, back); R3.pick.push(body);
|
||||
@@ -162,6 +163,10 @@ function build(site, room, racks, blocks) {
|
||||
s.userData = { rack: r.name, id: m.id, tip: `${nameOf(m)} · ${m.state} · U${b.unit}${span > 1 ? '–' + (b.unit + span - 1) : ''}${b.known ? '' : ' (height unknown)'} · ${[m.manufacturer, m.product].filter(Boolean).join(' ')}` };
|
||||
g.add(s); R3.pick.push(s);
|
||||
});
|
||||
// the chassis between front and back: solid and dark, apart from the cabinet
|
||||
const chassis = new T.Mesh(box(RAIL - 0.004, hh - 0.002, 2 * TILE - 0.07), mat('chassis' + b.known, () => new T.MeshStandardMaterial({ color: 0x202328, roughness: 0.7, metalness: 0.4, transparent: !b.known, opacity: b.known ? 1 : 0.5 })));
|
||||
chassis.position.set(0, y, -0.006);
|
||||
g.add(chassis);
|
||||
// the back: PSUs, the BMC port and the network cards with their ports
|
||||
const p = frontProfile(b, span), sleds = b.ms.slice(0, n).map(m => ({ ports: portsBy.get(m.id) || [] }));
|
||||
const rkey = p.style + '|' + span + '|' + JSON.stringify(sleds);
|
||||
@@ -248,10 +253,11 @@ function cables(cab, rackAt, portAt, keep, mat) {
|
||||
const W = (ra, x, y, z) => ra.g.localToWorld(new T.Vector3(x, y, z));
|
||||
for (const mp of cab.machines) for (const pt of mp.ports) {
|
||||
const a = pt.switch && portAt.get(mp.id + '|' + pt.name), sw = byName[pt.switch]; if (!a || !sw) continue;
|
||||
const ra = rackAt[a.rack], side = a.lx >= 0 ? RAIL / 2 + 0.03 : -RAIL / 2 - 0.03, lift = 0.004 * (switchPortNo(pt.name) % 5);
|
||||
const lane = 0.012 * (R3.switches.indexOf(sw) % 4); // each switch's cables up their own lane, side by side
|
||||
const ra = rackAt[a.rack], side = a.lx >= 0 ? RAIL / 2 + 0.03 + lane : -RAIL / 2 - 0.03 - lane, lift = 0.004 * (switchPortNo(pt.name) % 5);
|
||||
const pts = [W(ra, a.lx, a.ly, TILE + 0.01), W(ra, a.lx, a.ly - 0.01, OUT), W(ra, side, a.ly - 0.01, OUT)];
|
||||
if (sw.at) {
|
||||
const rb = rackAt[sw.at.rack], b = sw.port(pt.switch_port), sideB = b.lx >= 0 ? RAIL / 2 + 0.03 : -RAIL / 2 - 0.03;
|
||||
const rb = rackAt[sw.at.rack], b = sw.port(pt.switch_port), sideB = b.lx >= 0 ? RAIL / 2 + 0.03 + lane : -RAIL / 2 - 0.03 - lane;
|
||||
if (rb !== ra) { pts.push(W(ra, side, ra.h + 0.08 + lift, OUT), W(rb, sideB, rb.h + 0.08 + lift, OUT)); }
|
||||
if (rb === ra) pts.push(W(ra, side, b.ly + 0.012, OUT)); // down its own side to the switch, then across
|
||||
else pts.push(W(rb, sideB, b.ly + 0.012, OUT));
|
||||
@@ -259,8 +265,8 @@ function cables(cab, rackAt, portAt, keep, mat) {
|
||||
} else pts.push(W(ra, side, ra.h + 0.08 + lift, OUT), W(ra, side * 0.5, 2.9, OUT)); // to a switch in another room: up through the ceiling
|
||||
const dim = SITE.sel && lc(SITE.sel) !== a.rack && !(sw.at && lc(SITE.sel) === sw.at.rack);
|
||||
const col = pt.link === false ? '#7a7f86' : sw.color;
|
||||
const m = new T.Mesh(keep(new T.TubeGeometry(new T.CatmullRomCurve3(pts, false, 'catmullrom', 0.02), pts.length * 12, pt.kind === 'bmc' ? 0.0025 : 0.0035, 6, false)),
|
||||
mat('cab' + col + dim, () => new T.MeshStandardMaterial({ color: new T.Color(col), roughness: 0.5, transparent: dim, opacity: dim ? 0.15 : 1 })));
|
||||
const m = new T.Mesh(keep(new T.TubeGeometry(new T.CatmullRomCurve3(pts, false, 'catmullrom', 0.02), pts.length * 12, pt.kind === 'bmc' ? 0.004 : 0.0055, 6, false)),
|
||||
mat('cab' + col + dim, () => new T.MeshStandardMaterial({ color: new T.Color(col), emissive: new T.Color(col), emissiveIntensity: 0.45, roughness: 0.45, transparent: dim, opacity: dim ? 0.15 : 1 })));
|
||||
const m0 = S.machines.find(x => x.id === mp.id);
|
||||
m.userData = { rack: ra.r.name, id: mp.id, tip: `${m0 ? nameOf(m0) : mp.id} ${pt.kind === 'bmc' ? 'BMC' : pt.name}${pt.speed ? ' ' + pt.speed : ''} → ${pt.switch}${sw.role === 'oob' ? ' (OOB)' : ''} ${pt.switch_port}${pt.vlan ? ' · VLAN ' + pt.vlan : ''}${pt.link === false ? ' · no link' : ''}${sw.away ? ' (in ' + sw.away + ')' : ''} · ${pt.source === 'lldp' ? 'seen by LLDP' : pt.source === 'netbox' ? 'NetBox cable' : 'entered by hand'}${pt.netbox_says ? ' · NetBox says ' + pt.netbox_says : ''}` };
|
||||
R3.scene.add(m); if (!dim) R3.pick.push(m);
|
||||
|
||||
@@ -218,7 +218,7 @@ function pageRoom(siteName, roomArg) {
|
||||
${SITE.edit ? `<div class="actions" style="margin-bottom:8px"><button class="btn sm" id="rk-rotate">Rotate</button></div>` : ''}
|
||||
${rackElev(selRack)}${cablingTable(site, selRack)}</div>`
|
||||
: `<div class="card floor-side"><span class="muted">Click a rack for its front.${autoN ? `<br><br>${autoN} rack(s) have no place yet and are laid out automatically (${v3 ? 'see-through' : 'dashed'}): Edit layout to place them.` : ''}</span>${v3 ? switchTable() : ''}</div>`;
|
||||
const legend = v3 ? `<span>drag: turn · right or shift drag: move · wheel: zoom · double click: go to a rack · a click on a selected rack\'s server opens it · see-through: height unknown${frontView.get() === 'real' ? ' · fronts: the drive bays of the model, the strip on the left ear is the state' : ''}</span>`
|
||||
const legend = v3 ? `<span>drag: turn · right or shift drag: move · wheel: zoom · double click: go to a rack · a click on a selected rack\'s server opens it · see-through: height unknown${frontView.get() === 'real' ? ' · fronts: the drive bays of the model, the strip on the left ear is the state' : ''}${cablesView.get() ? ' · the cables run at the back: Turn around' : ''}</span>`
|
||||
: `<span><b class="lg vacant"></b>empty</span><span>thick edge: the front · bar: units used${SITE.edit ? ' · drag to move, select and Rotate' : ''}</span>`;
|
||||
return `<div class="page-head"><h1><a href="#/sites" class="muted" style="font-weight:400">Sites</a> / <a href="#/sites/${encodeURIComponent(site.name)}" class="muted" style="font-weight:400">${esc(site.name)}</a> / ${esc(room || '(no room)')}</h1>
|
||||
<span class="sub">${racks.length} racks · floor tiles of 600 mm</span>
|
||||
|
||||
Reference in new issue
Block a user