diff --git a/app/node_modules b/app/node_modules new file mode 120000 index 00000000..dc08a899 --- /dev/null +++ b/app/node_modules @@ -0,0 +1 @@ +/home/nacho/temp/opencode-work/netpulse-511/app/node_modules \ No newline at end of file diff --git a/app/public/locales/en/translation.json b/app/public/locales/en/translation.json index a8f4d28f..a691bc8e 100644 --- a/app/public/locales/en/translation.json +++ b/app/public/locales/en/translation.json @@ -1400,7 +1400,7 @@ "currentChannel": "Current channel", "recommendedChannel": "Recommended channel", "neighbors": "Neighbor APs", - "noScans": "No recent scans. Enable the agent on a router to collect neighbors.", + "noScans": "No recent scans for this router. Routers running NetGrip (gateway, APs managed by its panel) do not scan neighbors; routers with the NetPulse agent do. Pick another router from the list (e.g. rt2 or rt4) to see its plan.", "noRadios": "This router does not report WiFi radios.", "apply": "Apply channel", "gateway": " (Gateway)", diff --git a/app/public/locales/es/translation.json b/app/public/locales/es/translation.json index 8648b882..98014e10 100644 --- a/app/public/locales/es/translation.json +++ b/app/public/locales/es/translation.json @@ -1400,7 +1400,7 @@ "currentChannel": "Canal actual", "recommendedChannel": "Canal recomendado", "neighbors": "APs vecinos", - "noScans": "No hay scans recientes. Activa el agente en un router para recoger vecinos.", + "noScans": "Sin escaneos recientes para este router. Los routers que corren NetGrip (gateway, APs gestionados por su panel) no escanean vecinos; los que llevan el agente NetPulse sí. Selecciona otro router de la lista (p. ej. rt2 o rt4) para ver su plan.", "noRadios": "Este dispositivo no reporta radios WiFi.", "apply": "Aplicar canal", "gateway": " (Puerta de enlace)", diff --git a/server-go/internal/channelplan/store.go b/server-go/internal/channelplan/store.go index c7128a50..35d1a0fb 100644 --- a/server-go/internal/channelplan/store.go +++ b/server-go/internal/channelplan/store.go @@ -165,6 +165,13 @@ func (s *Store) Recommend(routerID string, radios []probe.Radio, within time.Dur bestCh = ch } } + // #518: el canal ACTUAL puede ser DFS (52/100/112/116...) y no estar en + // candidateChannels (solo no-DFS para recomendar). Su score es + // informativo ("cuánto ruido tengo ahora") y debe calcularse igual, + // no quedarse en MaxInt y pintar 9223372036854775807 en la UI. + if currentScore == math.MaxInt { + currentScore = channelScore(byBand[band], r.Channel) + } if bestCh != 0 && bestScore != math.MaxInt { rec.Recommended = bestCh rec.CurrentScore = currentScore diff --git a/server-go/internal/channelplan/store_test.go b/server-go/internal/channelplan/store_test.go index 66f9584d..07d16efb 100644 --- a/server-go/internal/channelplan/store_test.go +++ b/server-go/internal/channelplan/store_test.go @@ -188,3 +188,44 @@ func TestRecommendPasaSeccion(t *testing.T) { t.Errorf("sin sección el iface debe caer al placeholder: %+v", *nosec) } } + +func TestRecommendCurrentDfsChannelScoreNotMaxInt(t *testing.T) { + // #518: si el canal ACTUAL es DFS (112) y no está en candidateChannels + // (solo no-DFS), su score debe calcularse igual (es informativo) y no + // quedarse en MaxInt (la UI pintaba 9223372036854775807). + d, err := db.Open(t.TempDir()) + if err != nil { + t.Fatalf("db: %v", err) + } + defer d.Close() + + st := channelplan.NewStore(d.DB) + now := time.Now().Unix() + scans := []probe.ScanResult{ + {Iface: "wlan1", BSSID: "aa:bb:cc:dd:ee:01", SSID: "V", Channel: 112, Freq: 5560, Signal: -60}, + {Iface: "wlan1", BSSID: "aa:bb:cc:dd:ee:02", SSID: "W", Channel: 44, Freq: 5220, Signal: -55}, + {Iface: "wlan1", BSSID: "aa:bb:cc:dd:ee:03", SSID: "X", Channel: 116, Freq: 5580, Signal: -70}, + } + if err := st.SaveScan("rt1", now, scans); err != nil { + t.Fatalf("save: %v", err) + } + + radios := []probe.Radio{{Name: "5 GHz", Channel: 112, WidthMhz: 80}} + recs, err := st.Recommend("rt1", radios, time.Hour) + if err != nil { + t.Fatalf("recommend: %v", err) + } + if len(recs) != 1 { + t.Fatalf("esperaba 1 radio, got %d", len(recs)) + } + rec := recs[0] + if rec.CurrentScore > 1_000_000_000_000 { + t.Fatalf("currentScore no debe ser MaxInt (canal DFS): %d", rec.CurrentScore) + } + if rec.Recommended == 112 { + t.Errorf("no debería recomendar el DFS 112 ocupado: %+v", rec) + } + if rec.Recommended != 36 { + t.Errorf("debería recomendar el 36 libre (no-DFS), got %d", rec.Recommended) + } +}