diff --git a/cmd/rollcall/web/app.js b/cmd/rollcall/web/app.js
index b135313..c5222da 100644
--- a/cmd/rollcall/web/app.js
+++ b/cmd/rollcall/web/app.js
@@ -605,7 +605,8 @@ function wireJobs() { $('#refresh').onclick = () => refresh(true); }
function rackElev(rk) {
const keyOf = (b, rm, n) => `${b}|${rm}|${n}`;
const placed = S.machines.filter(m => m.location && m.location.rack);
- const ms = placed.filter(m => (m.location.building || '') === rk.building && (m.location.room || '') === rk.room && m.location.rack === rk.name);
+ const lo = x => String(x || '').toLowerCase(); // a rack typed in by hand and the same from NetBox may differ in case
+ const ms = placed.filter(m => lo(m.location.building) === lo(rk.building) && lo(m.location.room) === lo(rk.room) && lo(m.location.rack) === lo(rk.name));
// blocks of units: a server spans its height (size from the firmware or the
// model, ?U when unknown); sleds of one multi-node chassis share one block
const H = rk.height || 42, noU = [], blocks = [];
diff --git a/cmd/rollcall/web/sites.js b/cmd/rollcall/web/sites.js
index c79848a..fcaf4e7 100644
--- a/cmd/rollcall/web/sites.js
+++ b/cmd/rollcall/web/sites.js
@@ -23,12 +23,28 @@ function siteList() {
return out;
}
+// siteRacks: a site's racks, one per room and name whatever their case (a
+// rack typed in by hand and the same rack from NetBox), NetBox's spelling and
+// a placed one preferred
+function siteRacks(site) {
+ const by = new Map();
+ for (const r of S.racks.filter(x => inSite(site, x.building))) {
+ const k = lc(r.room) + '|' + lc(r.name), cur = by.get(k);
+ const score = x => (x.x != null ? 2 : 0) + (S.rooms.some(rm => rm.name === x.room && inSite(site, rm.building)) ? 1 : 0);
+ if (!cur || score(r) > score(cur)) by.set(k, r);
+ }
+ return [...by.values()];
+}
+const sameRoom = (a, b) => lc(a) === lc(b);
+
function siteStats(site) {
const ms = S.machines.filter(m => m.location && inSite(site, m.location.building));
- const racks = S.racks.filter(r => inSite(site, r.building));
+ const racks = siteRacks(site);
const by = {}; for (const m of ms) by[stateClass(m.state)] = (by[stateClass(m.state)] || 0) + 1;
const worst = ['bad', 'warn', 'ok', 'idle'].find(k => by[k]) || 'idle';
- const rooms = [...new Set([...racks.map(r => r.room), ...S.rooms.filter(r => inSite(site, r.building)).map(r => r.name), ...ms.map(m => m.location.room || '')])].sort();
+ const named = new Map(); // one room per name whatever its case; NetBox's (S.rooms) spelling first
+ for (const n of [...S.rooms.filter(r => inSite(site, r.building)).map(r => r.name), ...racks.map(r => r.room), ...ms.map(m => m.location.room || '')]) if (!named.has(lc(n))) named.set(lc(n), n);
+ const rooms = [...named.values()].sort();
return { ms, racks, by, worst, rooms };
}
@@ -118,7 +134,7 @@ function pageSite(name) {
if (!site) return `
No site ${esc(name)}.
Sites `;
const st = siteStats(site);
const cards = st.rooms.map(rm => {
- const racks = st.racks.filter(r => r.room === rm), ms = st.ms.filter(m => (m.location.room || '') === rm);
+ const racks = st.racks.filter(r => sameRoom(r.room, rm)), ms = st.ms.filter(m => sameRoom(m.location.room || '', rm));
const bad = ms.filter(m => stateClass(m.state) === 'bad').length;
return `${esc(rm || '(no room)')}
${floorSVG(site, rm, { mini: true })}
@@ -141,7 +157,7 @@ function wireSite(name) {
// automatic places in rows of ten below the placed ones, fronts facing
// alternate aisles (cold aisle between rows 0/1, 2/3, ...).
function racksOnFloor(site, room) {
- const racks = S.racks.filter(r => inSite(site, r.building) && r.room === room)
+ const racks = siteRacks(site).filter(r => sameRoom(r.room, room))
.map(r => ({ ...r, auto: r.x == null, facing: r.facing || 'N' }));
const placed = racks.filter(r => !r.auto);
let y0 = placed.length ? Math.max(...placed.map(r => r.y + footprint(r).h)) + 2 : 1;
@@ -154,7 +170,7 @@ function racksOnFloor(site, room) {
const footprint = r => (r.facing === 'E' || r.facing === 'W') ? { w: 2, h: 1 } : { w: 1, h: 2 };
function rackState(site, r) {
- const ms = S.machines.filter(m => m.location && inSite(site, m.location.building) && (m.location.room || '') === r.room && m.location.rack === r.name);
+ const ms = S.machines.filter(m => m.location && inSite(site, m.location.building) && sameRoom(m.location.room || '', r.room) && lc(m.location.rack) === lc(r.name));
const used = ms.reduce((a, m) => a + (m.size && m.size.node ? 0 : Math.max(1, (m.size && m.size.u) || 1)), 0) +
new Set(ms.filter(m => m.size && m.size.node).map(m => m.location.unit)).size * 2;
const worst = ['bad', 'warn', 'ok', 'idle'].find(k => ms.some(m => stateClass(m.state) === k)) || 'vacant';
@@ -164,7 +180,7 @@ function rackState(site, r) {
// floorSVG draws the room; opt.mini: a thumbnail for the site page
function floorSVG(site, room, opt = {}) {
const racks = SITE.edit && !opt.mini ? SITE.edit.racks : racksOnFloor(site, room);
- const rm = S.rooms.find(r => inSite(site, r.building) && r.name === room) || {};
+ const rm = S.rooms.find(r => inSite(site, r.building) && sameRoom(r.name, room)) || {};
const T = opt.mini ? 9 : 40;
const W = Math.max(rm.width || 0, ...racks.map(r => r.x + footprint(r).w + 1), 8), D = Math.max(rm.depth || 0, ...racks.map(r => r.y + footprint(r).h + 1), 6);
const o = [`