feat: monitor connected Wi-Fi signal per interface (#2367)

This commit is contained in:
Vito Cappello
2026-09-25 17:43:57 -04:00
committed by GitHub
parent badd4c8245
commit 86ab0fae8b
24 changed files with 814 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
//go:build testing
package systems
import (
"testing"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/stretchr/testify/require"
)
func TestCreateRecordsWiFiDisconnectReconnect(t *testing.T) {
sys, app := newTestSystemWithHub(t)
signal := -50.0
for _, snapshot := range []map[string]system.WiFi{
{"wlan0": {SSID: "home", Signal: &signal}, "wlan1": {SSID: "other"}},
{}, nil,
{"wlan0": {SSID: "new", Signal: &signal}},
} {
_, err := sys.createRecords(&system.CombinedData{Info: system.Info{WiFi: snapshot}, Stats: system.Stats{WiFi: snapshot}})
require.NoError(t, err)
record, err := app.FindRecordById("systems", sys.Id)
require.NoError(t, err)
var info system.Info
require.NoError(t, record.UnmarshalJSONField("info", &info))
require.Len(t, info.WiFi, len(snapshot), "current info must replace previous connection state")
}
}

View File

@@ -35,6 +35,14 @@ func UnmarshalResponse(resp common.AgentResponse, action common.WebSocketAction,
}
// Try generic Data field first (0.19+)
if len(resp.Data) > 0 {
// Wi-Fi maps are complete snapshots. CBOR otherwise merges entries into
// reused destinations, retaining disconnected interfaces and old RSSI.
if action == common.GetData {
if data, ok := dest.(*system.CombinedData); ok {
data.Info.WiFi = nil
data.Stats.WiFi = nil
}
}
if err := cbor.Unmarshal(resp.Data, dest); err != nil {
return fmt.Errorf("failed to unmarshal generic response data: %w", err)
}

View File

@@ -0,0 +1,35 @@
package transport
import (
"github.com/fxamacker/cbor/v2"
"github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/stretchr/testify/require"
"testing"
)
func TestWiFiSequentialResponseSnapshots(t *testing.T) {
signal := -50.0
var decoded system.CombinedData
for _, snapshot := range []map[string]system.WiFi{
{"wlan0": {SSID: "home", Signal: &signal}, "wlan1": {Signal: &signal}},
{"wlan0": {SSID: "home"}}, {}, nil,
{"wlan1": {SSID: "new", Signal: &signal}},
} {
payload, err := cbor.Marshal(system.CombinedData{Info: system.Info{WiFi: snapshot}, Stats: system.Stats{WiFi: snapshot}})
require.NoError(t, err)
require.NoError(t, UnmarshalResponse(common.AgentResponse{Data: payload}, common.GetData, &decoded))
require.Len(t, decoded.Info.WiFi, len(snapshot))
require.Len(t, decoded.Stats.WiFi, len(snapshot))
for id, want := range snapshot {
require.Equal(t, want, decoded.Info.WiFi[id])
require.Equal(t, want, decoded.Stats.WiFi[id])
}
}
// An older generic-response agent may omit both fields entirely.
payload, err := cbor.Marshal(map[int]any{0: map[int]any{}, 1: map[int]any{}})
require.NoError(t, err)
require.NoError(t, UnmarshalResponse(common.AgentResponse{Data: payload}, common.GetData, &decoded))
require.Empty(t, decoded.Info.WiFi)
require.Empty(t, decoded.Stats.WiFi)
}