Connection modal properly refreshes after create/edit/delete

- Connection handlers now re-render modal inline instead of redirecting
- Rack slots rendered top-to-bottom (U42 first, U1 last)
- Shared renderConnectionModal helper for DRY handler code
- Full connection tracing works through patch panels and wall sockets
master
Joca 2026-06-03 23:22:03 -03:00
parent 1a8799d711
commit 67ef5be483
Signed by: jocadbz
GPG Key ID: B1836DCE2F50BDF7
3 changed files with 125 additions and 122 deletions

View File

@ -11,71 +11,7 @@ import (
func (h *Handlers) ConnectionModal(w http.ResponseWriter, r *http.Request) { func (h *Handlers) ConnectionModal(w http.ResponseWriter, r *http.Request) {
portIDStr := r.PathValue("portId") portIDStr := r.PathValue("portId")
portID, err := strconv.ParseInt(portIDStr, 10, 64) h.renderConnectionModal(w, portIDStr)
if err != nil {
http.Error(w, "invalid port id", http.StatusBadRequest)
return
}
trace, err := services.TraceConnection(h.Store, portID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
connTypes, _ := h.Store.ConnectionTypeGetAll()
if connTypes == nil {
connTypes = []models.ConnectionType{}
}
type ConnectionModalData struct {
Trace *services.TraceResult
ConnectionTypes []models.ConnectionType
AllPorts []FlatPort
Error string
}
allDevices, _ := h.Store.DeviceGetAllUnracked()
rackedDevicesMap := map[int64]bool{}
var flatPorts []FlatPort
addDevicePorts := func(devices []models.Device) {
for _, d := range devices {
if rackedDevicesMap[d.ID] {
continue
}
rackedDevicesMap[d.ID] = true
for _, p := range d.Ports {
flatPorts = append(flatPorts, FlatPort{
ID: p.ID,
Name: p.Name,
Side: p.Side,
DeviceID: d.ID,
DeviceName: d.Name,
DeviceModel: "",
})
if d.Model != nil {
flatPorts[len(flatPorts)-1].DeviceModel = d.Model.Name
}
}
}
}
addDevicePorts(allDevices)
for _, rack := range h.mustGetAllRacks() {
if devs, err := h.Store.DeviceGetByRackID(rack.ID); err == nil {
addDevicePorts(devs)
}
if devs, err := h.Store.DeviceGetUnrackedByRackID(rack.ID); err == nil {
addDevicePorts(devs)
}
}
h.render(w, "connection_modal.html", ConnectionModalData{
Trace: trace,
ConnectionTypes: connTypes,
AllPorts: flatPorts,
})
} }
type FlatPort struct { type FlatPort struct {
@ -95,6 +31,67 @@ func (h *Handlers) mustGetAllRacks() []models.Rack {
return racks return racks
} }
func (h *Handlers) ConnectionDelete(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, _ := strconv.ParseInt(idStr, 10, 64)
returnPortID := r.URL.Query().Get("return_port_id")
h.Store.ConnectionDelete(id)
if returnPortID != "" {
h.renderConnectionModal(w, returnPortID)
return
}
h.redirect(w, r, "/")
}
func (h *Handlers) ConnectionEdit(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, _ := strconv.ParseInt(idStr, 10, 64)
r.ParseForm()
_, err := h.Store.ConnectionGetByID(id)
if err != nil {
http.Error(w, "connection not found", http.StatusNotFound)
return
}
connTypeID, _ := strconv.ParseInt(r.FormValue("connection_type_id"), 10, 64)
label1 := r.FormValue("label_1")
label2 := r.FormValue("label_2")
color := r.FormValue("color")
returnPortID := r.FormValue("return_port_id")
if color == "" {
color = "#808080"
}
var label1Ptr, label2Ptr *string
if label1 != "" {
label1Ptr = &label1
}
if label2 != "" {
label2Ptr = &label2
}
_, err = h.Store.ConnectionGetByID(id)
if err == nil {
_ = h.Store.ConnectionUpdate(&models.Connection{
ID: id,
ConnectionTypeID: connTypeID,
Label1: label1Ptr,
Label2: label2Ptr,
Color: color,
})
}
if returnPortID != "" {
h.renderConnectionModal(w, returnPortID)
return
}
h.redirect(w, r, "/")
}
func (h *Handlers) ConnectionCreate(w http.ResponseWriter, r *http.Request) { func (h *Handlers) ConnectionCreate(w http.ResponseWriter, r *http.Request) {
r.ParseForm() r.ParseForm()
@ -138,79 +135,85 @@ func (h *Handlers) ConnectionCreate(w http.ResponseWriter, r *http.Request) {
} }
if err := h.Store.ConnectionCreate(conn); err != nil { if err := h.Store.ConnectionCreate(conn); err != nil {
h.redirect(w, r, "/connections/"+returnPortID) if returnPortID != "" {
h.renderConnectionModal(w, returnPortID)
return
}
h.redirect(w, r, "/")
return return
} }
if returnPortID != "" { if returnPortID != "" {
h.redirect(w, r, "/connections/"+returnPortID) h.renderConnectionModal(w, returnPortID)
return return
} }
h.redirect(w, r, "/") h.redirect(w, r, "/")
} }
func (h *Handlers) ConnectionEdit(w http.ResponseWriter, r *http.Request) { func (h *Handlers) renderConnectionModal(w http.ResponseWriter, returnPortID string) {
idStr := r.PathValue("id") portID, err := strconv.ParseInt(returnPortID, 10, 64)
id, _ := strconv.ParseInt(idStr, 10, 64) if err != nil {
r.ParseForm() http.Error(w, "invalid port id", http.StatusBadRequest)
conn, err := h.Store.ConnectionGetByID(id)
if err != nil || conn == nil {
http.Error(w, "connection not found", http.StatusNotFound)
return return
} }
connTypeID, _ := strconv.ParseInt(r.FormValue("connection_type_id"), 10, 64) trace, err := services.TraceConnection(h.Store, portID)
label1 := r.FormValue("label_1") if err != nil {
label2 := r.FormValue("label_2")
color := r.FormValue("color")
returnPortID := r.FormValue("return_port_id")
if color == "" {
color = "#808080"
}
conn.ConnectionTypeID = connTypeID
if label1 != "" {
conn.Label1 = &label1
} else {
conn.Label1 = nil
}
if label2 != "" {
conn.Label2 = &label2
} else {
conn.Label2 = nil
}
conn.Color = color
if err := h.Store.ConnectionUpdate(conn); err != nil {
if returnPortID != "" {
h.redirect(w, r, "/connections/"+returnPortID)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
if returnPortID != "" { connTypes, _ := h.Store.ConnectionTypeGetAll()
h.redirect(w, r, "/connections/"+returnPortID) if connTypes == nil {
return connTypes = []models.ConnectionType{}
}
h.redirect(w, r, "/")
} }
func (h *Handlers) ConnectionDelete(w http.ResponseWriter, r *http.Request) { type ConnectionModalData struct {
idStr := r.PathValue("id") Trace *services.TraceResult
id, _ := strconv.ParseInt(idStr, 10, 64) ConnectionTypes []models.ConnectionType
returnPortID := r.URL.Query().Get("return_port_id") AllPorts []FlatPort
h.Store.ConnectionDelete(id)
if returnPortID != "" {
h.redirect(w, r, "/connections/"+returnPortID)
return
} }
h.redirect(w, r, "/")
allDevices, _ := h.Store.DeviceGetAllUnracked()
visitedMap := map[int64]bool{}
var flatPorts []FlatPort
addPorts := func(devices []models.Device) {
for _, d := range devices {
if visitedMap[d.ID] {
continue
}
visitedMap[d.ID] = true
for _, p := range d.Ports {
flatPorts = append(flatPorts, FlatPort{
ID: p.ID,
Name: p.Name,
Side: p.Side,
DeviceID: d.ID,
DeviceName: d.Name,
})
if d.Model != nil {
flatPorts[len(flatPorts)-1].DeviceModel = d.Model.Name
}
}
}
}
addPorts(allDevices)
for _, rack := range h.mustGetAllRacks() {
if devs, err := h.Store.DeviceGetByRackID(rack.ID); err == nil {
addPorts(devs)
}
if devs, err := h.Store.DeviceGetUnrackedByRackID(rack.ID); err == nil {
addPorts(devs)
}
}
h.render(w, "connection_modal.html", ConnectionModalData{
Trace: trace,
ConnectionTypes: connTypes,
AllPorts: flatPorts,
})
} }
func (h *Handlers) ConnectionGetByID(id int64) (*models.Connection, error) { func (h *Handlers) ConnectionGetByID(id int64) (*models.Connection, error) {

View File

@ -82,8 +82,8 @@ func buildRackSlots(heightUnits int, devices []models.Device) (front, back []Rac
front = make([]RackSlot, heightUnits) front = make([]RackSlot, heightUnits)
back = make([]RackSlot, heightUnits) back = make([]RackSlot, heightUnits)
for i := range front { for i := range front {
front[i].Unit = i + 1 front[i].Unit = heightUnits - i
back[i].Unit = i + 1 back[i].Unit = heightUnits - i
} }
for _, d := range devices { for _, d := range devices {
@ -94,7 +94,7 @@ func buildRackSlots(heightUnits int, devices []models.Device) (front, back []Rac
if d.Model != nil && d.Model.HeightUnits != nil { if d.Model != nil && d.Model.HeightUnits != nil {
height = *d.Model.HeightUnits height = *d.Model.HeightUnits
} }
start := *d.RackUnitStart - 1 start := heightUnits - *d.RackUnitStart
if start < 0 || start >= heightUnits { if start < 0 || start >= heightUnits {
continue continue
} }

BIN
lostcavewireplanner Executable file

Binary file not shown.