Racks: one rack whatever the case of its name
A rack created from a machine's location ("lab / pxe hall / R1") and the
same rack from NetBox ("Lab / PXE Hall / R1") showed twice on the Racks
page. The NetBox layout sync now folds such a rack into NetBox's spelling:
its floor place and comment carry over where the NetBox one has none, and
the machines located in it move to the NetBox spelling. A machine's
location no longer creates a rack that differs only in case, rack machine
counts ignore case, and the Racks page keys racks without case, NetBox's
spelling first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6da78311f3
commit
d9546fe264
3 files changed
+50
-5
No files matched your search
@@ -15,6 +15,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@@ -105,6 +106,38 @@ func (s *Store) SetRackPosition(ctx context.Context, building, room, name string
|
||||
return nil
|
||||
}
|
||||
|
||||
// RespellRack folds rack `from` into `to`, the same rack spelled otherwise
|
||||
// (NetBox's spelling of a rack typed in by hand): its place and comment go to
|
||||
// `to` where that has none, and machines located in `from` move to `to`.
|
||||
func (s *Store) RespellRack(ctx context.Context, from, to Rack) error {
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
if _, err := tx.Exec(ctx, `INSERT INTO racks(building, room, name, height, comment, x, y, facing)
|
||||
SELECT $4, $5, $6, height, comment, x, y, facing FROM racks WHERE building=$1 AND room=$2 AND name=$3
|
||||
ON CONFLICT (building, room, name) DO UPDATE SET comment=coalesce(nullif(racks.comment,''), excluded.comment),
|
||||
x=coalesce(racks.x, excluded.x), y=CASE WHEN racks.x IS NULL THEN excluded.y ELSE racks.y END,
|
||||
facing=CASE WHEN racks.x IS NULL THEN excluded.facing ELSE racks.facing END`,
|
||||
from.Building, from.Room, from.Name, to.Building, to.Room, to.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `DELETE FROM racks WHERE building=$1 AND room=$2 AND name=$3`, from.Building, from.Room, from.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(ctx, `UPDATE machines SET building=nullif($4,''), room=nullif($5,''), rack=$6
|
||||
WHERE coalesce(building,'')=$1 AND coalesce(room,'')=$2 AND rack=$3`, from.Building, from.Room, from.Name, to.Building, to.Room, to.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
// sameRack: one rack whatever the case of its building, room and name
|
||||
func sameRack(a, b Rack) bool {
|
||||
return strings.EqualFold(a.Building, b.Building) && strings.EqualFold(a.Room, b.Room) && strings.EqualFold(a.Name, b.Name)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- handlers
|
||||
|
||||
func (a *App) handleSites(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -240,6 +273,14 @@ func (a *App) syncLayout(ctx context.Context) error {
|
||||
if err := a.store.PutRack(ctx, x); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, h := range have { // the same rack typed in otherwise: NetBox's spelling wins
|
||||
if sameRack(h, x) && (h.Building != x.Building || h.Room != x.Room || h.Name != x.Name) {
|
||||
if err := a.store.RespellRack(ctx, h, x); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("layout: rack %s / %s / %s is %s / %s / %s in NetBox: merged", h.Building, h.Room, h.Name, x.Building, x.Room, x.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func (a *App) ensureRack(ctx context.Context, l Location) {
|
||||
}
|
||||
racks, _ := a.store.Racks(ctx)
|
||||
for _, r := range racks {
|
||||
if r.Building == l.Building && r.Room == l.Room && r.Name == l.Rack {
|
||||
if sameRack(r, Rack{Building: l.Building, Room: l.Room, Name: l.Rack}) { // "lab" is the rack NetBox calls "Lab"
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func (a *App) handleRacks(w http.ResponseWriter, r *http.Request) {
|
||||
ms, _ := a.store.List(r.Context())
|
||||
for i := range racks {
|
||||
for _, m := range ms {
|
||||
if m.Location.Building == racks[i].Building && m.Location.Room == racks[i].Room && m.Location.Rack == racks[i].Name {
|
||||
if sameRack(racks[i], Rack{Building: m.Location.Building, Room: m.Location.Room, Name: m.Location.Rack}) {
|
||||
racks[i].Machines++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -648,9 +648,13 @@ function pageRacks() {
|
||||
const r = route(); const sel = r.arg ? decodeURIComponent(r.arg) : '';
|
||||
const placed = S.machines.filter(m => m.location && m.location.rack);
|
||||
const unplaced = S.machines.length - placed.length;
|
||||
// racks from definitions plus any rack a machine mentions
|
||||
const keyOf = (b, rm, n) => `${b}|${rm}|${n}`;
|
||||
const racks = new Map(S.racks.map(x => [keyOf(x.building, x.room, x.name), { ...x }]));
|
||||
// racks from definitions plus any rack a machine mentions, one per name whatever its case
|
||||
const keyOf = (b, rm, n) => `${b}|${rm}|${n}`.toLowerCase();
|
||||
const racks = new Map();
|
||||
for (const x of S.racks) { // a rack typed in by hand and the same from NetBox: NetBox's spelling (its rooms are in S.rooms)
|
||||
const k = keyOf(x.building, x.room, x.name);
|
||||
if (!racks.has(k) || (S.rooms || []).some(r => r.building === x.building && r.name === x.room)) racks.set(k, { ...x });
|
||||
}
|
||||
for (const m of placed) { const l = m.location; const k = keyOf(l.building || '', l.room || '', l.rack); if (!racks.has(k)) racks.set(k, { building: l.building || '', room: l.room || '', name: l.rack, height: 42, machines: 0 }); }
|
||||
const list = [...racks.values()].sort((a, b) => keyOf(a.building, a.room, a.name).localeCompare(keyOf(b.building, b.room, b.name)));
|
||||
const buildings = [...new Set(list.map(x => x.building))].sort();
|
||||
|
||||
Reference in new issue
Block a user