Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4247,13 +4247,13 @@ func (a *API) BatchConfig(w http.ResponseWriter, r *http.Request) {
switch key {
case "ssid":
v, ok := value.(string)
if !ok || strings.TrimSpace(v) == "" || len([]byte(v)) > 64 {
if !ok || strings.TrimSpace(v) == "" || len([]byte(v)) > 32 {
http.Error(w, "invalid ssid", http.StatusBadRequest)
return
}
case "channel":
v, ok := value.(float64)
if !ok || math.IsNaN(v) || math.IsInf(v, 0) || v <= 0 {
if !ok || math.IsNaN(v) || math.IsInf(v, 0) || v <= 0 || math.Trunc(v) != v {
http.Error(w, "invalid channel", http.StatusBadRequest)
return
}
Expand Down Expand Up @@ -4293,7 +4293,7 @@ func (a *API) BatchConfig(w http.ResponseWriter, r *http.Request) {
results = append(results, map[string]any{"device_id": deviceID, "status": "failed", "error": "not found"})
continue
}
if err := a.Firmware.ApplyConfig(deviceID, ip, username, password, req.Changes); err != nil {
if err := a.Firmware.ApplyConfigContext(r.Context(), deviceID, ip, username, password, req.Changes); err != nil {
results = append(results, map[string]any{"device_id": deviceID, "status": "failed", "error": err.Error()})
continue
}
Expand Down
29 changes: 22 additions & 7 deletions internal/firmware/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1255,9 +1255,13 @@ func (s *Service) doUpgradeAirMAX(ctx context.Context, deviceID int64, ip, usern
return nil
}

// login authenticates to a Wave device and returns the auth token
// host should be just the IP/hostname, not a full URL
// login authenticates to a Wave device and returns the auth token.
func (s *Service) login(host, username, password string) (string, error) {
return s.loginContext(context.Background(), host, username, password)
}

// loginContext is the cancellation-aware form used by long-running callers.
func (s *Service) loginContext(ctx context.Context, host, username, password string) (string, error) {
baseURL := fmt.Sprintf("https://%s", host)
loginURL := baseURL + "/api/v1.0/user/login"

Expand All @@ -1266,7 +1270,7 @@ func (s *Service) login(host, username, password string) (string, error) {
"password": password,
})

req, err := http.NewRequest("POST", loginURL, bytes.NewReader(body))
req, err := http.NewRequestWithContext(ctx, "POST", loginURL, bytes.NewReader(body))
if err != nil {
return "", fmt.Errorf("create login request: %w", err)
}
Expand Down Expand Up @@ -2282,16 +2286,21 @@ func (s *Service) PushConfig(ip, username, password string, config []byte) error
return nil
}

// ApplyConfig applies specific configuration changes to a device
// ApplyConfig applies specific configuration changes to a device.
func (s *Service) ApplyConfig(deviceID int, ip, username, password string, changes map[string]any) error {
return s.ApplyConfigContext(context.Background(), deviceID, ip, username, password, changes)
}

// ApplyConfigContext is the cancellation-aware form used by request/job callers.
func (s *Service) ApplyConfigContext(ctx context.Context, deviceID int, ip, username, password string, changes map[string]any) error {
credential, err := s.resolveCredential(username, password, true)
if err != nil {
return err
}
username, password = credential.Username, credential.Password

// Login to Wave device
token, err := s.login(ip, username, password)
token, err := s.loginContext(ctx, ip, username, password)
if err != nil {
return fmt.Errorf("login failed: %w", err)
}
Expand Down Expand Up @@ -2338,7 +2347,7 @@ func (s *Service) ApplyConfig(deviceID int, ip, username, password string, chang
}

url := fmt.Sprintf("https://%s/api/v1.0/system/config", ip)
req, err := http.NewRequest("PATCH", url, bytes.NewReader(body))
req, err := http.NewRequestWithContext(ctx, "PATCH", url, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("create request: %w", err)
}
Expand All @@ -2361,7 +2370,13 @@ func (s *Service) ApplyConfig(deviceID int, ip, username, password string, chang
}

if storedNewPassword != "" {
if _, err := s.db.Exec(`UPDATE devices SET username = $2, password = $3 WHERE id = $1`, deviceID, username, storedNewPassword); err != nil {
// The device has already accepted the new password at this point. Do not
// let a client disconnect cancel persistence of the credential we now
// need for future management access. Preserve context values, but bound
// this post-accept commit independently.
persistCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second)
defer cancel()
if _, err := s.db.ExecContext(persistCtx, `UPDATE devices SET username = $2, password = $3 WHERE id = $1`, deviceID, username, storedNewPassword); err != nil {
return fmt.Errorf("configuration applied but new device credential could not be persisted: %w", err)
}
}
Expand Down
31 changes: 26 additions & 5 deletions web/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ body.page-devices #app.main-content {
.device-table .cell-site { width: 60px; min-width: 50px; }
.device-table .cell-signal { width: 65px; min-width: 55px; text-align: center; }
.device-table .cell-health { width: 60px; min-width: 50px; }
.device-table .cell-dir { width: 60px; min-width: 50px; }
.device-table .cell-distance { width: 60px; min-width: 50px; }
.device-table .cell-capacity { width: 75px; min-width: 65px; }
.device-table .cell-firmware { width: 100px; min-width: 80px; font-size: 0.8rem; }
Expand Down Expand Up @@ -7687,9 +7688,14 @@ html[data-theme="dark"] #mapContainer .leaflet-control-attribution a {
overflow: hidden;
}

/* Header table - fixed at top, not scrolled */
.virtual-header-table {
/* Header stays fixed vertically while its viewport mirrors body scrollLeft. */
.virtual-header-viewport {
flex-shrink: 0;
width: 100%;
overflow: hidden;
}

.virtual-header-table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
Expand All @@ -7705,8 +7711,7 @@ html[data-theme="dark"] #mapContainer .leaflet-control-attribution a {
.virtual-scroll-container {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
overflow: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
Expand All @@ -7723,7 +7728,6 @@ html[data-theme="dark"] #mapContainer .leaflet-control-attribution a {
position: absolute;
top: 0;
left: 0;
right: 0;
width: 100%;
table-layout: fixed;
border-collapse: collapse;
Expand Down Expand Up @@ -7781,6 +7785,9 @@ html[data-theme="dark"] #mapContainer .leaflet-control-attribution a {
.virtual-header-table .cell-health,
.virtual-body-table .cell-health { width: 60px; min-width: 50px; max-width: 70px; }

.virtual-header-table .cell-dir,
.virtual-body-table .cell-dir { width: 60px; min-width: 50px; max-width: 70px; }

.virtual-header-table .cell-distance,
.virtual-body-table .cell-distance { width: 60px; min-width: 50px; max-width: 70px; }

Expand Down Expand Up @@ -9565,3 +9572,17 @@ body.drilldown-active .main-content {
min-width: 760px;
}
}


.virtual-empty-state {
position: absolute;
inset: 0;
z-index: 2;
display: flex;
align-items: flex-start;
justify-content: center;
pointer-events: none;
}
.virtual-empty-state.hidden {
display: none;
}
2 changes: 1 addition & 1 deletion web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,6 @@ <h3>Batch Configuration</h3>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="anonymous"></script>
<script src="js/jszip.min.js"></script>
<script type="module" src="js/app.js?v=124"></script>
<script type="module" src="js/app.js?v=127"></script>
</body>
</html>
Loading
Loading