luci-app-wwand + luci-proto-wwand: add LuCI support for the wwand connection manager - #8917
luci-app-wwand + luci-proto-wwand: add LuCI support for the wwand connection manager#8917ddimension wants to merge 2 commits into
Conversation
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed 2 new commits (both messages match their diffs). 12 inline comments, the main ones being an rpc expect unwrapping bug in wwand.modemopts, two ACL gaps (modem_scan_start/modem_scan_status, file list /dev/), the undeclared protocol/qmi.js file collision with luci-proto-qmi, and a poller that outlives the interface-edit modal.
Generated by Claude Code
|
Updated to the current sources, addressing the inline review:
Still on the list (follow-ups, not blockers): a modem selector on the settings page instead of hard-binding |
e9a073a to
f52b465
Compare
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed 2 new commits (343ab75, f52b465). The earlier findings are addressed: the protocol/qmi.js path collision is gone, the statusRaw/.modems unwrap and sl.physical are fixed, the async-scan ACL grants are in, status.js uses literal _() headers, and the interface-edit poller now unregisters itself. 4 new inline comments — the notable one is a modem_reset ACL gap plus a section_id/modem-name mixup, both fallout from the modem_repower → modem_reset rename in the shared wwand.modemopts module.
Commit checks
- 343ab75 "luci-app-wwand: sync modem UI to current sources" — the two bullets cover the Modems-page additions and the review fixes, but not the largest parts of the diff. Undocumented: the Status → Modem detail page is deleted outright (
view/wwand/detail.js, −306 lines, plus itsadmin/status/wwand-detailmenu entry);view/wwand/settings.jsis split into five new shared resources (wwand.rpc/format/esim/netsel/modemsid, −729 lines there); the_repoweraction and itswwand.modem_repowerubus call become_reset/wwand.modem_reset; the Modem Tools menu node loses its title; and a newfcc_authmodem option is added. Removing a user-visible page and swapping a ubus method are the kind of thing worth naming in the message — the ACL fallout of that swap is the subject of one of the inline comments.
Generated by Claude Code
Package-G artefacts for the openwrt/packages#30185 + openwrt/luci#8917 push: - upstream/dossier-packages-30185.md — maturity/coexistence dossier to post as a PR comment (ModemManager precedent, independent-verification story, HW/test matrix, audit changelog, device-ownership answer, re-review request). - upstream/rfc-openwrt-devel.md — the architecture RFC (improve-existing-stack vs. a separate opt-in proto) for openwrt-devel. - upstream/checklist.md — the push/tag/PR/feed action sequence. Drafts for author review; sending/posting is manual. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Synced to the wwand 1.2.0 sources (paired with openwrt/packages#30185).
All JS |
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed 1 new commit (5da1788). 5 inline comments — the blocking one is a var plmn redeclaration in status.js that both kills the new NAS panel and throws on an unregistered modem; the rest are an unreachable plmnEditor() in settings.js and three items in the reworked ACL file.
Checked the new UCI surface (wwand_plmnlist / plmn_list / hard_reconnect_on_ip_change) against the backend-coupling rule: wwand is not in the packages feed yet, but the PR body and the follow-up comment already name openwrt/packages#30185 as the pairing, so not flagged. CI on the head SHA is green.
Commit checks
- 5da1788 "luci-app-wwand, luci-proto-wwand: sync to wwand 1.2.0 sources" — the four bullets describe real changes, but they leave out the largest one. Undocumented: the whole preferred-PLMN manager (
renderPlmnManager, ~200 new lines insettings.js), the newmodem_plmn_set/modem_plmn_restoreubus methods, a new UCI section typewwand_plmnlist(type+plmnlist) and theplmn_listoptions it adds to bothwwand.modemoptsandwwand.simlist. Also unmentioned: the new Datapath & muxing status panel (modem_datapath), the Preferred networks (NAS) panel, thecachedCall/slowCacherework that moves cells/slots/datapath/PLMN off the 1 s poll onto per-call TTLs, thefmt.term()tooltip pass acrossstatus.js, addingmodemmanagerto the migration list, and the tab→space reindent of the ACL file. Introducing a new config section type and a new poll-caching model are the kind of thing worth naming in the message.
Generated by Claude Code
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed 2 new commits (12ef4b1, d7bb9ee). These replace the single 5da1788 I reviewed last round — git diff 5da1788..d7bb9ee is empty, so this is a pure re-split of that commit into an app half and a proto half (plus the dropped Co-Authored-By trailer). The split itself is clean: 12ef4b1 touches only applications/luci-app-wwand/, d7bb9ee only protocols/luci-proto-wwand/.
Because the tree is unchanged, the five findings from the previous round are all still open, including the blocking one: the var plmn redeclaration in status.js, which leaves the new "Preferred networks (NAS)" panel permanently unrendered and throws on an unregistered modem. The modem_repower leftover grant and its mirror in luci-proto-wwand.json (missing modem_reset) are likewise untouched. One new nit inline, on the only hunk in this round's diff that I had not already commented on. CI on the head SHA is green (all three FormalityCheck runs pass).
Commit checks
- 12ef4b1 "luci-app-wwand: sync to wwand 1.2.0 sources" — the message is 5da1788's verbatim minus the proto bullet, so the omissions I flagged there carry over unchanged. Still undocumented in a 913-line diff: the whole preferred-PLMN manager (
renderPlmnManager, ~200 lines insettings.js), the newmodem_plmn_set/modem_plmn_restoreubus methods, a new UCI section typewwand_plmnlist(type+plmnlist) with theplmn_listoptions it adds to bothwwand.modemoptsandwwand.simlist, the Datapath & muxing panel (modem_datapath), the Preferred networks (NAS) panel, thecachedCall/slowCacherework that moves cells/slots/datapath/PLMN off the 1 s poll onto per-call TTLs, thefmt.term()tooltip pass overstatus.js,modemmanagerjoining the migration list, and the tab→space reindent of the ACL file. A new config section type and a new poll-caching model are worth naming in the message — and the ACL reindent is what hid the two grant bugs. - d7bb9ee "luci-proto-wwand: sync to wwand 1.2.0 sources" — "add the hard_reconnect_on_ip_change option" covers 4 of the 10 added lines; the other three hunks add help text to pre-existing
password,metricandpeerdnsoptions. Minor, but a "- plus help text for the PAP/CHAP password, gateway metric and peer-DNS options" bullet would make the diff match the body.
Generated by Claude Code
- status.js: fix the `var plmn` redeclaration. `plmn` was declared three times in one function scope (`res[5]` for the PLMN lists, then `reg.plmn` for the serving PLMN); by the NAS panel line `plmn` was the serving object, so `plmn.nas` was always undefined — the "Preferred networks (NAS)" panel never rendered and threw on an unregistered modem (swallowed by the status poller's error handler → the page silently stopped refreshing). Rename the lists reply to `plmnLists`. - settings.js: drop the unreachable `plmnEditor()` (~62 dead lines; the only render path is `renderPlmnManager`). It also carried a latent `list.map` index-as-noRat bug. - ACL: remove the dead `modem_repower` grant (the ubus method was renamed to `modem_reset`) and the `modem_sms_send` grant (no caller in the app — the SMS UI only lists/deletes). Restore tab indentation + inline single-element arrays to match the tree convention (100/104 acl.d files use tabs), which keeps the ACL delta reviewable. - format/esim/netsel: move the shared `.wwe-*` stylesheet into wwand.format and inject it once from the settings view. Previously esim.js and netsel.js each emitted their own <style>, so the settings page (rendering both) got it twice, and netsel depended on esim purely for that string. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ACL: the write grant still named the removed `modem_repower` and did not grant `modem_reset`, so the Reset button rendered from the shared wwand.modemopts on the interface-edit page hit access-denied. Grant `modem_reset` instead. - protocol/wwand.js: drop the redundant `o.default = '0'` on the hard_reconnect_on_ip_change flag — form.Flag already defaults to disabled, and it was the only literal '0'/'1' default in the file (the flags around it use the symbolic `o.enabled`/`o.disabled`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed 2 new commits (7e1c0ec, 442ba27). Both messages match their diffs, so no commit checks this round — and this pair closes every finding from the last two rounds. One nit inline; CI on the head SHA is green (all three FormalityCheck runs).
What I verified:
status.jsscope bug — fixed properly.plmnListsis now the only name bound tores[5], andvar plmn = reg.plmnat :405 is the soleplmnleft inrenderLive(used only at :427, guarded byplmn ? …).renderNasList()already returnsnullfor a missing/empty list, so an unregistered modem no longer throws into the poller's error handler.plmnEditor()removal — no dangling references.plmnEditRowsurvives becauserenderPlmnManagerstill uses it (:358, :437) with an explicit wrapper rather than a barelist.map, andcallPlmnSetis still reached at :407. No require insettings.jsbecame unused by the deletion.- ACL — both files now match the actual call surface exactly. Every method
wwand.rpcdeclares is granted and has at least one caller; conversely nothing is granted without a caller.modem_sms_sendis correctly dropped (sms_sendappears nowhere in either package), andluci-proto-wwand.json'smodem_resetcloses the access-denied on the Reset button thatwwand.modemoptsrenders on the interface-edit Modem & SIM tab.modemopts' other calls from that page (status,modem_sim_slots,modem_probe,file list /sys/class/gpio/) are all covered by the proto grant too. Tab indentation matches the tree convention. Both files still parse as JSON. - CSS move — safe.
wwand.esimandwwand.netselare required only byview/wwand/settings.js, which is exactly wherefmt.injectStyle()now runs, and it sits in the same returned tree asnetsel.render()(:806) andesim.render()(:807), so neither panel loses its.wwe-*rules. NoESIM_CSS/esim.CSSreferences remain anywhere, and dropping thewwand.esimrequire fromnetsel.jsleaves all of its remaining requires in use. o.default = '0'— confirmed a no-op:form.js:5036-5037setsthis.disabled = '0'; this.default = this.disabled;.
Generated by Claude Code
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed 1 new commit (eba976f). git diff 442ba27..eba976f is a single blank line removed from wwand/esim.js — the message matches the diff exactly, so no commit checks this round. Everything closed in the previous two rounds stays closed; nothing in this diff reopens a prior finding. CI on the head SHA is green (all three FormalityCheck runs).
The one nit inline is about where the commit sits in the series rather than what it does — the blank line it removes was introduced by 7e1c0ec earlier in this same series, so it reads as squash material.
Generated by Claude Code
|
Rebased onto current master and squashed from nine commits to two — one per package — and the sources are re-synced from upstream, which they had drifted from (the branch was still at the "wwand 1.2.0 sources" state). Six files changed in the re-sync, the two substantive ones both being fixes found in the field since:
The rest are the status/modems/format/rpc/ACL updates that go with them. All JS passes No |
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed the rebase: the nine-commit series is now two commits, one per package, and both messages match their diffs exactly (d41c76c touches only protocols/luci-proto-wwand/, b0f6e4e only applications/luci-app-wwand/) — so no commit checks this round.
The content delta against the previous head (eba976f) is the six files the PR comment names: git diff eba976f..b0f6e4e -- applications/luci-app-wwand protocols/luci-proto-wwand is +124/−6. The two fixes called out in that comment both check out — MODE_BITS_MASK correctly preserves the 0x23 (CDMA/EVDO/TD-SCDMA) bits the picker does not render, and describeError() recovers the detail.result/detail.code that a QMI-level rejection carries. Everything closed in the previous rounds stays closed; nothing here reopens a prior finding. CI on the head SHA is green (all three FormalityCheck runs).
Six inline comments, all on the new hunks. The two worth a second look are the control-protocol switch, whose comment claims a "supports both" gate that the code does not implement (and which the daemon documents as a Quectel-only AT command, broken on the RG650E for MBIM), and "Reset to defaults", which still sends DEFAULTS.mode_preference = 0x50 verbatim and so re-clobbers exactly the unrendered bits collect() now preserves.
I also re-checked the backend coupling for the two new ubus methods rather than assuming: modem_repower and modem_set_protocol both exist in the daemon (src-ucode/ubus.uc, src-ucode/hwops.uc, src-ucode/daemon.uc), both are called only from luci-app-wwand pages, and both are granted under write in luci-app-wwand.json — so no proto-side ACL gap this time, and no frontend-only option.
Generated by Claude Code
|
All six addressed. Five changed the code; one did not, and I want to be explicit about which. Control-protocol switch. You were right that the comment promised a gate the code did not implement — but the underlying capability was already there and simply unexposed, so the fix is a real gate rather than a reworded comment. One correction to the review: the switch is not Quectel-specific. Your RG650E point survives the gate, though, and it is the sharper half: that model has a recipe, so "Reset to defaults". Correct, and it was worse than the bug it sat next to: 0x7F → 0x50 versus the 0x5C the Apply path produced. A reset now means "defaults for what is on screen" and keeps the bits the picker cannot render. Protocol-switch failure path. Fixed — it now uses Duplicated lock formatting. Moved to
Note the gate needs the wwand release carrying the |
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed the amended head. d41c76c is byte-identical to the previous round; ad0894b replaces b0f6e4e, so the new content is git diff b0f6e4e..ad0894b — +88/−28 across five files, still scoped to applications/luci-app-wwand/ only, and the message matches the diff. No commit checks this round. CI on the head SHA is green (all three FormalityCheck runs).
All six findings from the last round are closed, and the follow-up comment's account of them checks out:
- Duplicated lock formatting —
fmt.fmtLocks()is now the single implementation, and both call sites use it (status.js:409,settings.js:616). It renders theearfcn:pci/pci:arfcn:scs:bandspelling the lock editor accepts, matching the placeholders inrenderCellLock(), instead ofJSON.stringifyoutput. The disarmed-lock omission you called out is visible in the code (l.enabled === falseskips the group), and unknown keys still fall through to the JSON branch, so nothing the daemon grows later is silently dropped. - "Reset to defaults" — now seeds from
~MODE_BITS_MASKexactly likecollect(), andObject.assignkeepsDEFAULTSitself unmutated across repeated presses. One question inline about which bits "defaults" should mean. - Control-protocol switch — gated on
data.info.proto_switch, which comes off the same per-modem status objectstatus.jsreadscaps.ratsfrom, and fails closed when the daemon predates the field. The MBIM direction now warns about the MBIM_OPEN rejection; the QMI direction keeps the old wording. Failure path usesdescribeError(), so a QMI-level rejection reports result/code instead of bare "qmi". - NR band ordering — the narrower-before-broader invariant now holds at every overlap I could check: n14 before n28, n18 and n5 before the superset n26, n12 before n85, n261 before the superset n257. One nit inline on the new tie-break paragraph.
- Em dash in the
format.jsblock comment — literal now. admin/network/wwand-tools— title and order restored, andload()falls back to the first modem when no?modem=is present, so the menu entry lands on a usable page rather than an empty one.
Generated by Claude Code
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed the amended head (9a6dc44 replaces b0f6e4e; d41c76c is unchanged). The commit message is byte-identical to the one I reviewed last round and still matches the package it adds, so no commit checks this round. git diff b0f6e4e..9a6dc44 is +100/−31 across five files, all in luci-app-wwand, and it is exactly the follow-up the previous round's comments asked for. CI on the head SHA is green (all three FormalityCheck runs).
What I verified closed:
Reset to defaultsre-clobbering the unrendered RAT bits — fixed on both halves.DEFAULTS.mode_preferenceis now derived fromMODE_BITS_MASKinstead of the hardcoded0x50, and the button ORs in the& ~MODE_BITS_MASKremainder of the reported value, so a reset can no longer drop CDMA/EVDO/TD-SCDMA.Object.assign({}, DEFAULTS)keeps the shared band arrays unmutated.- Control-protocol switch gate — the section now actually gates on a capability (
data.info.proto_switch) rather than on "the modem currently speaks QMI or MBIM", and the MBIM direction no longer promises the connections come back on their own. The dependency on a not-yet-released daemon field is called out in your comment, and the failure direction is safe (section simply does not render), so not flagged. - Protocol-switch failure path — now routed through
describeError(), which is in scope at :90. - Duplicated lock formatting —
fmt.fmtLocks()is a genuine single source: the widths it groups by (2 for LTE, 4 for NR5G) match the spellings the cell-lock editor accepts (earfcn:pci,pci:arfcn:scs:band, placeholders at settings.js :585/:587), and both former call sites are gone. NolockRows/liveLocksleftovers, andfmtis required in both files. Modem Toolsmenu title — restored, withorder: 60afterModemsat 55. No collision with the coreadmin/network/*orders (10–50). The JSON parses and all four changed JS files passnode --check.
Three inline comments. The one worth a look is in bands.js: the reorder pass fixed n14, n18/n5 and n261, but n74 is still listed behind n50/n75, which leaves it reachable only for a 1 MHz sliver — the same class of bug this hunk was written to remove.
Generated by Claude Code
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed the amended head. 1a66040 is byte-identical to the d406f26 I reviewed last round; 46fb9e1 replaces 0e41f7c with the same message, and it is still the single "add the package" commit for luci-app-wwand, so no commit checks this round.
git diff 0e41f7c..46fb9e1 is +19/−1 in one file — the group() payload widening from the last open thread. CI on the head SHA is green (all three FormalityCheck runs).
The widening does what your table says for every shape it lists — I ran the function rather than reading it: {enabled:true,values:[1300,246,5230,118]} → LTE 1300:246, 5230:118, 'n78' → band_lock n78, {values:'n41'} → band_lock n41, true → band_lock armed, {enabled:false,values:[7]} omitted, and the known keys still pair by width. Nothing from the previous rounds reopens, and no new UCI option or ubus method here, so nothing new for the backend-coupling rule.
Two inline comments, both on the new branch chain. The one worth a look is a shape the chain does not cover: a bare array ({ lte: [1300, 246] }) reaches the l.values != null test, and since Array.prototype.values is a function that test passes — the row renders as LTE function values() { [native code] }.
Generated by Claude Code
…ing k=v Two follow-up findings on the previous fix, both from review on openwrt/luci#8917 and both real. A bare array — { lte: [1300, 246] }, the most obvious shape after { values: [...] } — fell into the `l.values != null` branch, because Array.prototype.values is a real function on every array. v became [ Array.prototype.values ] and the row rendered as LTE function values() { [native code] } An explicit Array.isArray(l) branch ahead of the l.values tests fixes it and restores the colon pairing for that shape (LTE 1300:246). And the k=v branch fed its strings through the width pairing, so the same payload rendered differently depending on the key it arrived under: { lte: { earfcn: 1300, pci: 246 } } gave "LTE earfcn=1300:pci=246" while { band_lock: {...} } (width 0) gave "band_lock earfcn=1300, pci=246". Colon pairing only means something for a positional values array, so that branch clears the width. Checked every shape against the real function, not by reading it.
|
Both correct, both fixed — the array one is a good catch.
The width point is right too, and it was inconsistent in a way that would have Ran every shape through the real function again:
The PR body is updated to v1.4.7 as well — you were right that it had fallen |
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed the amended head. 1a66040 is byte-identical to the proto commit I reviewed last round; bc1667f replaces 46fb9e1 with a byte-identical message and is still the single "add the package" commit scoped to applications/luci-app-wwand/ — so no commit checks this round.
git diff 46fb9e1..bc1667f is +5/−1 in one file, exactly the two threads left open from the last round, and both close:
- Bare-array payload. The explicit
Array.isArray(l)branch now sits ahead of thel.valuestests, soArray.prototype.valuescan no longer be mistaken for a payload. I ran the function rather than reading it:{lte:[1300,246]}→LTE 1300:246and{nr5g:[242,431070,1,78]}→NR5G 242:431070:1:78, both with the colon pairing restored, in place of thefunction values() { [native code] }string. Branch order is right —l === trueis still tested first, so the "armed, no detail" spelling is unaffected, andtypeof l != 'object'still catches scalars, since an array never reaches it. - Width on the
k=vbranch.width = 0there makes the same payload render identically under a known and an unknown key:{lte:{earfcn:1300,pci:246}}→LTE earfcn=1300, pci=246, matching{band_lock:{earfcn:1300,pci:246}}→band_lock earfcn=1300, pci=246. Previously the first producedLTE earfcn=1300:pci=246. Reassigning the parameter is local to the call, so the widths passed at the three call sites are untouched.
Every row of the table in your comment reproduces against the real function, including the ones this round did not change ({enabled:false,values:[7]} omitted, 'n78' → band_lock n78, {values:'n41'} → band_lock n41, true → band_lock armed, and the two known keys pairing by width). Nothing from the previous rounds reopens.
No new UCI option or ubus method in this diff, so nothing new to check against the backend-coupling rule. All 13 JS files pass node --check and all three JSON files parse. CI on the head SHA is green (all three FormalityCheck runs).
One nit inline, and it is about the PR description rather than the code.
Generated by Claude Code
a5c7fba to
b001972
Compare
openwrt-ai
left a comment
There was a problem hiding this comment.
Reviewed the rebased head. The branch moved onto current master, so the content delta against my last review is git diff bc1667f..b001972 -- applications/luci-app-wwand protocols/luci-proto-wwand — +279/−56 across seven files. Both commits are new objects (f9a395c, b001972) but the messages are byte-identical to the ones I reviewed last round and each is still the single "add the package" commit scoped to one package (f9a395c → protocols/luci-proto-wwand/ only, 3 files; b001972 → applications/luci-app-wwand/ only, 15 files) — so no commit checks this round. CI on the head SHA is green (all three FormalityCheck runs).
Most of the delta is one hardening pass plus three functional changes, and I checked the pass rather than taking the comments' word for it:
- Bare string → one-element array. The premise is right:
dom.append()turns an array element intocreateTextNodebut assigns a bare string throughinnerHTML, and an element passed either way is appended as itself. So wrapping is a real escape, not cosmetics. I looked specifically for the failure mode the change could introduce — a row whose value is already an array, which[ r[1] ]would nest andcreateTextNodewould stringify with commas — and there is none: every one of the 58 row entries reachingfmt.tbl()`` fromstatus.jsand the proto handler is a string or a single element; the multi-node ones (`capsBadges`, the PCI lock cells at `wwand.js:219` / `:270`) are already wrapped in their own `E('span', …)`. `renderWarnings`, `simSlotRow` and the netsel/esim cells were array-valued to begin with. - The GridSection status columns on the Modems page are not affected, in case that was the worry —
col()'s DummyValues go throughAbstractValue.textvalue(), which%h-escapes beforerenderTextValuehands the string toE(). renderWidgetoverrides. Both claims in the comments hold.getUIElement()resolves throughmap.findElement('id', …)over the whole map anddata-fieldlives on the containerrender()builds, not on the widget node — so wrapping the AT2 flag in a<div>is safe.this.super('renderWidget', …)from an own-property override also resolves correctly:Class.super()starts its walk atObject.getPrototypeOf(this), skipping own properties. The keylist swap works becausetransformChoices()readskeylist/vallistsynchronously insideValue.renderWidget, and neither filter can empty the list (autohasproto: null,'0'is always inslotsOf), so it can never silently degrade the Combobox to a plain Textfield.tx_aggregationun-nesting is correct —pandkidTxare computed above therx_aggregationguard, so moving the uplink row out of it does not take anything out of scope.- Actions-column flex wrap.
renderRowActionsreally does returnE('td', { class: '… nowrap cbi-section-actions' }, E('div')), sotd.firstElementChildis the button wrapper and not a button — the styles land where intended, andwhite-space: nowrapon the parenttddoes not govern flex line breaking, so the wrap does take effect.
Three inline comments. The one worth a look is in wwand.modemopts: the new per-row load() hooks index status().modems by the raw section_id, which is the modem name only on the Modems page — on the interface-edit surface it is the interface section, and the SIM-slot dropdown loses its slot list there entirely.
No new UCI option or ubus method in this delta (globals.datapaths, qmap_version, dp.extra and at2_released are all new status fields, read-only), so nothing new to check against the backend-coupling rule; the ACL files are unchanged.
Generated by Claude Code
| return L.resolveDefault(callStatus(), {}).then(function(reply) { | ||
| if (!((reply && reply.modems) || {})[section_id]) | ||
| return self.super('load', [section_id]); | ||
|
|
||
| return Promise.all([ L.resolveDefault(callSlots(section_id), {}) ]) |
There was a problem hiding this comment.
The comment above says "the section id is the modem name", but that only holds on the Modems page. addModemSim is also called from the proto handler at protocol/wwand.js:509, where `section_id` is the *interface* section and the modem name has to be resolved through [`modemsid.modemSid()`](https://github.com/openwrt/luci/blob/b001972ef879061e33273e5a5c51d3daf26f9085/applications/luci-app-wwand/htdocs/luci-static/resources/wwand/modemsid.js#L18-L22)`` — which this file already does for the Reset button at modemopts.js:158`` (modemsid.modemSid(section_id) || section_id).
So on Network → Interfaces → Modem & SIM, reply.modems[section_id] is always undefined, the early return fires, callSlots() is never issued and the SIM-slot field is left with just 0 — leave as-is. The previous sweep-all-modems version did populate it there, so this is a regression on that surface rather than only a narrowing.
| return L.resolveDefault(callStatus(), {}).then(function(reply) { | |
| if (!((reply && reply.modems) || {})[section_id]) | |
| return self.super('load', [section_id]); | |
| return Promise.all([ L.resolveDefault(callSlots(section_id), {}) ]) | |
| return L.resolveDefault(callStatus(), {}).then(function(reply) { | |
| var modem = modemsid.modemSid(section_id) || section_id; | |
| if (!((reply && reply.modems) || {})[modem]) | |
| return self.super('load', [section_id]); | |
| return Promise.all([ L.resolveDefault(callSlots(modem), {}) ]) |
Same raw-section_id lookup in the two other new hooks in this commit, both of which also silently no-op on the interface page (they fail open, so the effect is milder — the AT2 hint never appears, and the mux list is never filtered):
- line 193 —
var m = ((res || {}).modems || {})[section_id]; - lines 250-251 —
self.dpProto[section_id] = ((reply || {}).modems || {})[section_id] ? … .protocol : null;
Keying slotsOf / at2Port / dpProto by section_id is right — it is only the lookup into the status reply that needs the resolved modem name.
(Minor, while here: with a single-element array the Promise.all([…]).then(function(results) { results.forEach(…) }) wrapper is a leftover from the sweep and could just be .then(function(r) { … }).)
Generated by Claude Code
There was a problem hiding this comment.
Confirmed, and it was a regression exactly as you describe — fixed.
The proto handler calls modemopts.addModemSim(s, 'modem', bindModem) with the
interface section (protocol/wwand.js), so reply.modems[section_id] was
always undefined on Network → Interfaces → Modem & SIM, the early return fired
and the SIM-slot field kept only 0 — leave as-is.
What made this worth more than a one-line patch: the Reset button in the same
file already resolved through modemsid.modemSid(section_id) || section_id and
carried a comment spelling out the trap. Repeating that inline a fourth time
would have left the fifth to be found by someone else, so the resolution is now
one modemOf() helper and every lookup into a status reply goes through it —
including the two you flagged at lines 193 and 250-251. The per-row caches stay
keyed by section_id, which was the right key.
Checked that the fallback cannot misfire on the Modems page: option modem
belongs to the interface section and a wwand_modem section never carries it
(the daemon's config parser reads it only on interfaces), so modemSid()
returns null there and the raw section id is used, as before.
The single-element Promise.all is gone too.
| how 'raw-ip' gets written, and the daemon canonicalises it */ | ||
| if (value == null || value === '' || /^[a-z][a-z0-9_-]*$/.test(value)) | ||
| return true; | ||
| return _('A datapath name: lowercase letters, digits and underscore.'); |
There was a problem hiding this comment.
nit: the error message contradicts the regex two lines up — the pattern deliberately accepts - (per the comment, so raw-ip can be typed), but the message tells the user only letters, digits and underscore are allowed. Someone who types raw-ip, gets it rejected for an unrelated reason and reads this message will conclude the hyphen was the problem.
| return _('A datapath name: lowercase letters, digits and underscore.'); | |
| return _('A datapath name: lowercase letters, digits, underscore and hyphen.'); |
Generated by Claude Code
There was a problem hiding this comment.
Applied as suggested. The regex accepts the hyphen deliberately, and the message
now says so — someone typing raw-ip and hitting an unrelated rejection would
have drawn exactly the wrong conclusion.
| rows.push([ k.replace(/_/g, ' ').replace(/^./, function(c){ return c.toUpperCase(); }), | ||
| '' + v ]); |
There was a problem hiding this comment.
nit: the block comment names "whether the NSS shim was loaded" as one of the keys this loop is expected to render, i.e. a boolean — and '' + v turns that into a bare, untranslated true / false sitting next to translated labels like "Parent device" and "Uplink NTB". The _size branch already special-cases one type, so a boolean branch fits the same shape:
| rows.push([ k.replace(/_/g, ' ').replace(/^./, function(c){ return c.toUpperCase(); }), | |
| '' + v ]); | |
| if (typeof v == 'boolean') | |
| v = v ? _('yes') : _('no'); | |
| rows.push([ k.replace(/_/g, ' ').replace(/^./, function(c){ return c.toUpperCase(); }), | |
| '' + v ]); |
(false is deliberately still rendered — the v == null || v === '' guard above lets it through, and "NSS shim: no" is the interesting answer.)
Generated by Claude Code
There was a problem hiding this comment.
Applied, though the premise does not hold for the code as it stands — worth
recording so the reasoning survives.
No datapath in tree sends a boolean here. The NSS one reports
nss_shim: 'loaded' or 'absent' — a string, so nothing untranslated reaches
the page today. The block comment describes that key in prose ("whether the NSS
shim was loaded"), which reads like a boolean but is not one.
The suggestion is still right, as hardening rather than a fix: the contract in
the daemon's datapath interface says the keys are shown as given, and
docs/extending.md explicitly invites out-of-tree datapaths, so a boolean is a
value this loop should be able to render. Taken with your reasoning on false
kept in the comment, since that is the non-obvious half.
modemopts.js is shared by two surfaces and `section_id` does not mean the same
thing on both: on Network → Modems it IS the wwand_modem name, on Network →
Interfaces → Modem & SIM it is the interface section. Status replies are keyed
by modem name, so three lookups missed entirely on the interface page — and
missed quietly, reading as "this modem has nothing to report":
- addModemSim took the early return every time, so callSlots() was never
issued and the SIM-slot field stayed at "0 — leave as-is". The sweep-all
version it replaced did populate it there, so this was a regression on that
surface, not just a narrowing.
- the AT2 hint never appeared, and the datapath list was never filtered to
the modem's protocol. Those fail open, so they only lost information.
The Reset button in the same file already resolved through modemsid.modemSid()
and carried a comment explaining exactly this trap. Rather than repeat it a
fourth time, it is now one modemOf() helper that every status-reply lookup goes
through; the per-row caches stay keyed by section_id, which was right. A
wwand_modem section never carries `option modem` (config.uc reads it only on
the interface), so the fallback leaves the Modems page unchanged.
Also drops a single-element Promise.all left over from the sweep, and two nits
found in review: the datapath validator's message denied the hyphen its own
regex deliberately accepts (so `raw-ip` can be typed), and a datapath's extra
status rows now render a boolean as a translated yes/no. No datapath in tree
sends one — both send strings — but docs/extending.md invites out-of-tree ones
and the contract promises the keys are shown as given.
Reported by openwrt-ai on openwrt/luci#8917.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
e00de07 to
ae8f20f
Compare
| acting = false; | ||
|
|
||
| if (el) | ||
| el.click(); |
There was a problem hiding this comment.
createHandlerFn puts the spinner and disabled on ev.currentTarget (ui.js:5681-5684) — here a node that is never inserted into the page, so Reattach/Reboot/Repower now run with no visible busy state and the dropdown stays selectable throughout. The comment at :350-353 claims the spinner keeps working; it does not. Put the busy state on ddNode for the duration of the returned promise instead.
Generated by Claude Code
There was a problem hiding this comment.
| var st = d.st.state || d.cfg.state || '?'; | ||
| var rows = [ | ||
| [ _('Interface'), d.cfg.interface + (d.cfg.mux_id ? ' · mux %d'.format(d.cfg.mux_id) : '') ], | ||
| [ _('State'), E('strong', { 'style': 'color:%s'.format(st == 'CONNECTED' ? '#3c3' : '#da3') }, st) ] |
There was a problem hiding this comment.
Missed by this round's array-wrapping pass: st is d.st.state || d.cfg.state, i.e. daemon/uci text, and a bare string child is assigned through innerHTML (luci.js:1395). This is the last one I can find in either package.
| [ _('State'), E('strong', { 'style': 'color:%s'.format(st == 'CONNECTED' ? '#3c3' : '#da3') }, st) ] | |
| [ _('State'), E('strong', { 'style': 'color:%s'.format(st == 'CONNECTED' ? '#3c3' : '#da3') }, [ st ]) ] |
Generated by Claude Code
There was a problem hiding this comment.
| recognise is honoured, but hardware recovery then stays disarmed for | ||
| that modem, because an AT port answers on QMI and MBIM modems too and | ||
| so proves nothing about the pin. */ | ||
| o = s.taboption(tab, form.ListValue, 'protocol', _('Control protocol'), |
There was a problem hiding this comment.
nit: the Tools page carries a second control with this exact label (settings.js:933),`` but it issues modem_set_protocol — a firmware switch — and leaves this option untouched. Using it on a modem that has `protocol` pinned here produces exactly the "pin contradicts the driver wwand recognises" state whose consequence (hardware recovery disarmed) this help text warns about. Should the Tools switch also rewrite `option protocol`, or should the two be named apart?
Generated by Claude Code
There was a problem hiding this comment.
Both, in the end — the two halves turned out not to be alternatives.
The Tools switch now clears the pin. modem_set_protocol wraps its callback so a successful firmware switch drops a option protocol that named the old protocol (daemon.uc:2466-2467 calling the clear_protocol_pin dep from main.uc:339). That is the part that mattered: a pin the driver contradicts is not inert — recovery.revoke_arming withdraws permission to touch hardware for that modem, persistently, so the modem would come back on the new protocol with its recovery ladder disabled. Leaving the stale pin in place was the actual bug behind your question.
And the two controls are named apart. This page's heading is now Firmware protocol mode, with the reason recorded at the site so it does not get "tidied" back:
/* NOT "Control protocol": that is the name of the uci option
... */
E('h3', {}, _('Firmware protocol mode')),Both confirm texts and the description say what happens to the pin, e.g.
A "Control protocol" pinned on the modem page is cleared back to "detect", because a pin the driver contradicts disables hardware recovery for this modem.
so the modem page keeps Control protocol as the name of the uci option, and the Tools page no longer claims that name for a different operation.
The rename is in this PR; the daemon side is in wwand v1.6.3, which the companion openwrt/packages#30185 pins.
7fc1d13 to
ec3f279
Compare
`option protocol` names the protocol wwand uses to drive a modem. A pin that contradicts the driver wwand recognises is not inert: recovery.revoke_arming withdraws the permission to touch that modem's hardware, and persists it. So a modem pinned to `qmi` and then switched to MBIM came back with its reset and power-cycle rungs silently disabled, and nothing saying why. The path there is ordinary — the pin exists because detection failed once, the switch happens on the Tools page months later. Raised in review on openwrt/luci#8917, where the observation was that the two controls carry the same label. The label is worth fixing, but it is the smaller half: renaming makes the collision less likely and leaves the trap. CLEARED, not rewritten to the target. The option exists for a control device wwand cannot classify — its own help says leave it on detect — and the reason this one was pinned, detection failing on the OLD protocol, usually does not survive the switch. Writing the target would leave a pin that outlives its cause and needs cleaning up by hand. If detection is broken on the new protocol too, the daemon already says so with the remedy; a stated failure beats a silent one. In the daemon rather than in LuCI, for three reasons: only here is it known whether the switch actually succeeded (a failed one leaves the pin alone, and a test fails if that changes); it covers the ubus/CLI path too; and the daemon already owns three uci write-backs, so there is a place for it. Deliberately not gated on auto_correct_config: that gate is for the daemon correcting config on its own initiative, and this runs only because an operator asked for the switch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… what it clears Two controls were both labelled "Control protocol" while doing different things: the one on the modem page sets `option protocol` (which protocol wwand uses to drive the modem), this one rewrites the modem's FIRMWARE mode and resets the device. That is how someone ends up with one pinned and the other switched — which disables hardware recovery for that modem, persistently (recovery.revoke_arming). Raised in review on openwrt/luci#8917. This one is now "Firmware protocol mode". The daemon clears a contradicting pin when the switch succeeds, so the confirmation says so rather than changing the operator's configuration silently. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| o.boundTo = {}; | ||
|
|
||
| o.cfgvalue = function(sid) { | ||
| var opt = this.ucioption || this.option; | ||
| var msid = modemSid(sid); | ||
| this.boundTo[sid] = msid; | ||
| return uci.get('network', msid || sid, opt); | ||
| }; |
There was a problem hiding this comment.
Stamping inside cfgvalue() makes the guard a tautology: parse() calls this.cfgvalue(section_id) and then this.remove(section_id) in the same synchronous block (form.js:2149-2154), so boundTo[sid] is refreshed with the post-save modem reference microseconds before msid is recomputed from it — this.boundTo[sid] === msid can never be false, and the RUTC50 uci del network.wwmodem_auto.path still happens. Stamp what the form read at render time instead; parse() never calls render().
| o.boundTo = {}; | |
| o.cfgvalue = function(sid) { | |
| var opt = this.ucioption || this.option; | |
| var msid = modemSid(sid); | |
| this.boundTo[sid] = msid; | |
| return uci.get('network', msid || sid, opt); | |
| }; | |
| o.boundTo = {}; | |
| /* Stamped from render(), NOT from cfgvalue(): parse() calls cfgvalue() | |
| immediately before remove() (form.js:2149-2154), so a stamp written | |
| there always agrees with the modemSid() remove() computes and the | |
| guard below could never fire. render() runs only on the load path. */ | |
| var baseRender = o.render; | |
| o.render = function(option_index, section_id, in_table) { | |
| this.boundTo[section_id] = modemSid(section_id); | |
| return baseRender.apply(this, arguments); | |
| }; | |
| o.cfgvalue = function(sid) { | |
| var opt = this.ucioption || this.option; | |
| var msid = modemSid(sid); | |
| return uci.get('network', msid || sid, opt); | |
| }; |
Generated by Claude Code
There was a problem hiding this comment.
Confirmed, and the reporter proved it on a shipped build before I read this —
reset_gpio went too, so it was every modem-bound field
(ddimension/luci-app-wwand#7).
Your diagnosis is exact: cfgvalue() and remove() run in the same
synchronous block inside save(), so the stamp always agrees with the
modemSid() that remove() computes. I had assumed cfgvalue was a
render-time call.
I went further than the suggestion rather than moving the stamp to render(),
for two reasons:
- an option that is not rendered still reaches
remove()—form.js:2167
removes an inactive option withcfgvaluenever called at all — so a
render-time stamp is absent exactly when it would have to decide, and absent
reads the same as "never showed a value"; - more fundamentally, no record of when something ran is worth trusting for
this. Two attempts have now died on that.
So the redirect no longer deletes on the wwand_modem section at all. It
belongs to the interface form, which only references a modem; the section is
the hardware's and is shared with every other interface on it. Clearing a
modem-level option is the Modems page's job, where the section is the
wwand_modem and the binding is a pass-through — there remove() reaches it
directly and means what it says. What this one still does is drop a legacy
inline copy from the interface, which touches nothing shared.
Checked against a model of the real save path this time, not by reading: with
the old guard it reproduces the reporter's two uci del lines exactly; with the
fix the modem section keeps both options, in the active and the inactive path.
Pushed, along with the two nits from this round — the unguarded mnc in
fmtOperator's sibling ('%02d'.format(null) really is "00", verified in
cbi.js:753) and the stale qmi alias comment.
| /* same two shapes as fmtOperator; prefer the name, then either | ||
| spelling of the numeric id, and only then the generic word */ | ||
| var op = (reg.plmn && (reg.plmn.description || | ||
| (reg.plmn.mcc != null ? '%d/%02d'.format(reg.plmn.mcc, reg.plmn.mnc) : null) || |
There was a problem hiding this comment.
nit: only mcc is guarded, but '%02d'.format(null) is Math.floor(+null || 0) → "00" (cbi.js:753-754), so a half-populated plmn renders a real-looking 260/00 here instead of falling through to plmn.id. fmtOperator in this same hunk guards both halves.
| (reg.plmn.mcc != null ? '%d/%02d'.format(reg.plmn.mcc, reg.plmn.mnc) : null) || | |
| ((reg.plmn.mcc != null && reg.plmn.mnc != null) ? '%d/%02d'.format(reg.plmn.mcc, reg.plmn.mnc) : null) || |
Generated by Claude Code
There was a problem hiding this comment.
| /* One protocol descriptor, registered under both the current name `wwand` | ||
| and the historical `qmi` alias — see the tail of this file. */ |
There was a problem hiding this comment.
nit: stale — the tail of the file (:585-588) and the Makefile say the qmi alias was dropped and only wwand is registered.
| /* One protocol descriptor, registered under both the current name `wwand` | |
| and the historical `qmi` alias — see the tail of this file. */ | |
| /* One protocol descriptor, registered under the single name `wwand` — see the | |
| tail of this file. */ |
Generated by Claude Code
There was a problem hiding this comment.
ec3f279 to
c090594
Compare
'%02d'.format(null) is Math.floor(+null || 0) -> "00" (cbi.js:753-754), so a half-populated plmn rendered a real-looking 260/00 instead of falling through to plmn.id. fmtOperator in the same file already guarded both halves; this site did not — the same defect the mcc/mnc formatter was written to end, one caller further on. Raised in review on openwrt/luci#8917. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The comment said the descriptor is registered under both `wwand` and a historical `qmi` alias; the registration 236 lines below has said `wwand` and nothing else since the alias was dropped. netifd sources every handler in /lib/netifd/proto, so two claiming `qmi` would be settled by load order, which no package can control. Raised in review on openwrt/luci#8917. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
c090594 to
0ea61e5
Compare
| }); | ||
| }) }, _('Switch protocol')), | ||
| E('div', { 'class': 'cbi-value-description' }, | ||
| _('Flips the control protocol (QMI ↔ MBIM). The modem resets; use only when the other protocol is known to work on this module.')), |
There was a problem hiding this comment.
The description under the renamed heading still calls this "the control protocol" — the exact term the h3 and both confirm texts were moved away from, and the one the modem page's option owns.
| _('Flips the control protocol (QMI ↔ MBIM). The modem resets; use only when the other protocol is known to work on this module.')), | |
| _('Rewrites the modem firmware mode (QMI ↔ MBIM). The modem resets; use only when the other mode is known to work on this module.')), |
Generated by Claude Code
There was a problem hiding this comment.
solved by renaming this control's wording to "firmware mode" and quoting "Control protocol" only as the modem-page option, rather than dropping the sentence — thanks.
Generated by Claude Code
| ? _('Switch the modem firmware to MBIM? The modem resets and re-enumerates. Some firmwares reject MBIM even though the switch itself succeeds — if the modem does not come back, switch it to QMI again. A "Control protocol" pinned on the modem page is cleared back to "detect", because a pin the driver contradicts disables hardware recovery for this modem.') | ||
| : _('Switch the modem firmware to %s? The modem resets and re-enumerates — its connections come back on their own. A "Control protocol" pinned on the modem page is cleared back to "detect", because a pin the driver contradicts disables hardware recovery for this modem.').format(target.toUpperCase())))) |
There was a problem hiding this comment.
Both texts promise the user that a pinned protocol is cleared back to "detect", but nothing in either package writes it: wrpc.setProtocol() at :965 is the only call and no uci.unset('network', …, 'protocol') follows it, so the promise can only be kept by the daemon's modem_set_protocol. Does it unset the uci pin, and from which wwand version — otherwise the dialog states an outcome that does not happen and the modem is left in the pin-contradicts-driver state that modemopts.js:184`` warns about.
Generated by Claude Code
There was a problem hiding this comment.
Good question to ask rather than assume, and the answer is yes — but not in
either of these packages, which is exactly why it was worth checking.
wrpc.setProtocol() reaches the daemon's modem_set_protocol, and that clears
the pin: on a successful switch it removes option protocol from the
wwand_modem section when the pin no longer names what was switched to. A
failed switch leaves it alone — the pin still describes reality then — and a
test fails if that changes.
It is cleared, not rewritten to the target. The option exists for a control
device wwand cannot classify (its own help says leave it on detect), and the
reason a pin was set — detection failing on the OLD protocol — usually does not
survive the switch. Writing the target would leave a pin that outlives its
cause; clearing returns the modem to the recommended state, and if detection is
broken on the new protocol too the daemon says so and names the option.
Version: ddimension/wwand@3469e73, released in v1.6.3, which is what this
PR series pins. So for the combination these packages are built against, the
promise holds.
You are right that the page was making a promise it cannot itself keep, though.
The description now attributes the action — "the daemon clears a contradicting
Control protocol pin when the switch succeeds" — rather than stating it in the
page's own voice, so someone running an older daemon reads a description of the
system rather than a guarantee from the dialog.
Fixed along with the description still borrowing "control protocol" from the
option page, and the trailing tabs on my comment separators.
| var opt = this.ucioption || this.option; | ||
|
|
||
| /* NEVER delete on the wwand_modem section from here. | ||
|
|
There was a problem hiding this comment.
nit: trailing tabs on this comment's separator line, same at :67 and :79 — the only trailing whitespace in either package.
Generated by Claude Code
There was a problem hiding this comment.
…g tabs The heading and both confirmation texts were moved off "control protocol" — that term belongs to the option on the modem page — but the description under them still used it, which puts the borrowed word back where the confusion started. It now says what the control does and names the daemon as the actor that clears a contradicting pin, rather than promising it in the page's own voice. Also strips trailing tabs from three blank separator lines inside a block comment I added; they were the only trailing whitespace in either package. Both raised in review on openwrt/luci#8917. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0ea61e5 to
9c843bb
Compare
| }); | ||
| }) }, _('Switch protocol')), | ||
| E('div', { 'class': 'cbi-value-description' }, | ||
| _('Rewrites the modem firmware mode (QMI ↔ MBIM). The modem resets; use only when the other mode is known to work on this module. The daemon clears a contradicting "Control protocol" pin when the switch succeeds.')), |
There was a problem hiding this comment.
nit: the PR body still says the series pairs with "wwand 1.6.0", but the daemon behaviour this sentence now attributes only exists from v1.6.3 per your reply. Bump the body so the pin and the text agree.
Generated by Claude Code
There was a problem hiding this comment.
fixed, thanks — the body now reads "Pairs with openwrt/packages#30185 (wwand 1.6.3)", so it agrees with what that PR pins (PKG_VERSION:=1.6.3) and with the daemon behaviour this description attributes.
The other version mentions in the body are historical rather than stale — "Since v1.6.0 the QMI mux datapath is an interface…" describes when that change landed, not what is pinned — so I left those as they are.
Netifd protocol handler for `proto wwand`, the cellular connection manager proposed in openwrt/packages#30185. It renders the connection options the daemon consumes — APN, PDP type, authentication, MUX id, MTU handling — and resolves the modem an interface is bound to. wwand registers `proto wwand` only; the historical `qmi` proto name stays uqmi's, so this handler never competes with the stock one for an interface. Signed-off-by: André Valentin <avalentin@marcant.net>
9c843bb to
d073be1
Compare
| msgstr "" | ||
|
|
||
| #: htdocs/luci-static/resources/view/wwand/status.js:770 | ||
| msgid "Reset peak" |
There was a problem hiding this comment.
The template was not regenerated for this round, in both directions: this msgid and Peak: RSRP %s dBm · SINR %s dB, Signal — aim the antenna for the highest RSRP/SINR and the three Reference Signal Received … descriptions no longer exist in any source, while none of the new strings are present — Signal strength, Reference signal quality (RSRQ), 3G signal quality (Ec/Io), the seven RSRP … / RSSI … / SINR … / Ec/Io 3G series labels, excellent/good/fair, avg, peak, not reported, Signal graphs unavailable: …, registered — this modem reports no signal detail and the rewritten page description. wwand/graph.js is not referenced anywhere in the file. Regenerate it against the current sources before merge.
Generated by Claude Code
There was a problem hiding this comment.
fixed, thanks — regenerated with build/i18n-scan.pl from the package root, so the paths stay package-relative.
Both directions confirmed gone/present: Reset peak, Peak: RSRP %s dBm · SINR %s dB and Signal — aim the antenna for the highest RSRP/SINR are out; Signal strength, Reference signal quality (RSRQ), 3G signal quality (Ec/Io), the seven series labels, excellent/good/fair, not reported, Signal graphs unavailable: …, registered — this modem reports no signal detail and the rewritten page description are in, and graph.js is now referenced 34 times.
One correction to the list: of the "Reference Signal Received …" descriptions, the panel's three are gone, but two remain on purpose — they are the cell-table column headers at status.js:50-51, which still exist and still describe the RSRP/RSRQ columns of the carrier-aggregation and neighbour tables.
| load: function() { | ||
| return Promise.all([ | ||
| L.resolveDefault(callStatus(), {}), | ||
| /* the graph canvas: threshold rules only, series drawn by graph.js */ | ||
| request.get(L.resource('wwand/signal.svg')).then(function(r) { | ||
| return r.ok ? r.text() : null; | ||
| }).catch(function() { return null; }), | ||
| ]); | ||
| }, | ||
|
|
||
| render: function(loaded) { | ||
| var svgText = loaded[1]; |
There was a problem hiding this comment.
nit: loaded[0] is never read — refresh() issues its own callStatus() a moment later — so this spends a throwaway ubus call on every page load, and the Promise.all exists only to carry it.
| load: function() { | |
| return Promise.all([ | |
| L.resolveDefault(callStatus(), {}), | |
| /* the graph canvas: threshold rules only, series drawn by graph.js */ | |
| request.get(L.resource('wwand/signal.svg')).then(function(r) { | |
| return r.ok ? r.text() : null; | |
| }).catch(function() { return null; }), | |
| ]); | |
| }, | |
| render: function(loaded) { | |
| var svgText = loaded[1]; | |
| load: function() { | |
| /* the graph canvas: threshold rules only, series drawn by graph.js */ | |
| return request.get(L.resource('wwand/signal.svg')).then(function(r) { | |
| return r.ok ? r.text() : null; | |
| }).catch(function() { return null; }); | |
| }, | |
| render: function(svgText) { |
Generated by Claude Code
There was a problem hiding this comment.
fixed, thanks — and it was worse than a wasted call: nothing read loaded[0] at all, so the round trip was pure loss on every page load. load() now returns the canvas alone and render() takes it directly, as suggested.
| dom.content(ctx.legend[i], [ (cur != null) | ||
| ? '%.1f %s (%s %.1f, %s %.1f)'.format(cur, s.unit, | ||
| _('avg'), avg, _('peak'), Math.max.apply(null, seen)) | ||
| : _('not reported') ]); |
There was a problem hiding this comment.
nit: avg and peak reach the catalogue as two bare words with no context, and the sentence that frames them is not translatable at all, so its word order and punctuation are fixed for every language. The code this replaces shipped the whole thing as one msgid (Peak: RSRP %s dBm · SINR %s dB).
| dom.content(ctx.legend[i], [ (cur != null) | |
| ? '%.1f %s (%s %.1f, %s %.1f)'.format(cur, s.unit, | |
| _('avg'), avg, _('peak'), Math.max.apply(null, seen)) | |
| : _('not reported') ]); | |
| dom.content(ctx.legend[i], [ (cur != null) | |
| ? _('%.1f %s (avg %.1f, peak %.1f)').format(cur, s.unit, | |
| avg, Math.max.apply(null, seen)) | |
| : _('not reported') ]); |
Generated by Claude Code
There was a problem hiding this comment.
fixed, thanks — one msgid now, as the code it replaced had.
Taking your suggestion surfaced a second defect in the same line, which is worth recording: my literal had two spaces before the bracket, and i18n-scan collapses whitespace when it builds the catalogue (build/i18n-scan.pl:43, applied at :85). The generated msgid could therefore never have matched the runtime string, and the translation would have silently never applied — no shipped template in the tree contains a double-space msgid. It is a single space now, so the entry the scanner emits is the string the page asks for.
From openwrt/luci#8917 review: - The translation template was stale in both directions — it still carried "Reset peak", "Peak: RSRP %s dBm · SINR %s dB" and the removed panel's three Reference-Signal-Received descriptions, and none of the new strings, with graph.js not referenced at all. Regenerated with build/i18n-scan.pl from the package root. (The two "Reference Signal Received" entries that remain are the cell-table column headers at status.js:50-51, which are still live — not leftovers.) - load() fetched the status and threw the result away: nothing read loaded[0], and refresh() issues its own callStatus() the moment the view renders. One ubus round trip per page load, for nothing. load() now returns the canvas alone. - The legend line was assembled from three msgids, so "avg" and "peak" reached the catalogue as context-free words and the sentence around them was not translatable at all — its word order, spacing and punctuation fixed for every language. One msgid now, as the code it replaced had. And the one the regeneration surfaced: that msgid had TWO spaces before the bracket, and i18n-scan collapses whitespace when it builds the catalogue (build/i18n-scan.pl:43,85). The generated msgid could therefore never match the runtime string, and the translation would have silently never applied — no shipped template in the tree contains a double-space msgid. One space now. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Status and management UI for the wwand cellular connection manager proposed in openwrt/packages#30185: modem overview, per-modem tools (radio technologies, band selection, network selection and scan, cell lock), SIM and eSIM profile management, SMS, and the per-interface migration entry point that converts a stock cellular interface to `proto wwand` in place. The status page graphs the signal live — one canvas per quantity with its published thresholds, one series per radio technology — so a modem dropping from 5G to LTE, or a hand on the antenna, is visible rather than inferred. The history is kept in the browser: nothing is buffered on the router and nothing is sampled while the page is closed. Everything is driven over the daemon's ubus API; the ACL file lists exactly the methods the pages call. Signed-off-by: André Valentin <avalentin@marcant.net>
d073be1 to
eded769
Compare
LuCI companion packages for the wwand cellular connection manager (submitted to openwrt/packages as
net/wwand, upstream repo https://github.com/ddimension/wwand).proto qmi/mbim/ncminterface in place toproto wwand(user-triggered). Ships the sharedwwand.bandsband/frequency resource.proto 'wwand'(Network → Interfaces): modem selection, APN / PDP type / auth, per-interface options.Good-citizen coexistence. luci-proto-wwand ships only
protocol/wwand.js— it does not install aprotocol/qmi.jsalias and does not conflict with luci-proto-qmi.proto qmi/mbim/ncminterfaces stay with the stock LuCI proto handlers; wwand manages onlyproto wwand, and moving an interface across is the explicit "Migrate" action above. Pairs with openwrt/packages#30185 (wwand 1.6.4).🤖 Generated with Claude Code