Live updates and filtering on every list; stop broadcasting model events to everyone - #218
Merged
Merged
Conversation
…ts to everyone
Only the Proxy List updated itself, and it did so through code written
by hand in that one view. Two root causes, both fixed here.
Only Host ever published. Every model registers a ModelPs proxy, but
only host.js *exports* it -- the rest export the raw class, and the
routes import those files directly. So LocalGroup and Permission
published nothing at all, and the Groups and Permissions pages
subscribed to events that were never sent.
ModelPs published the class name as the primary key. getIndex() read
model[Model._key] with `model` being the class on a static call, and
every class has a built-in `.name` -- so for any model keyed on 'name'
the pk of every event was the literal string "LocalGroup". Creates
appended a row that matched nothing; updates would have duplicated the
record. Host was the sole model that behaved, purely because its key is
'host' and Host.host is undefined.
The socket bridge broadcast everything to everyone. app.io.emit sent
every model event, with its full record, to every authenticated socket
-- no per-model or per-row read check, so a viewer scoped to one domain
received live payloads for every other domain in the install. It is now
gated per socket by utils/socket_pubsub.js, mirroring the REST read
guards, with rights cached briefly and busted when a grant or group
changes. Models without a gate are not broadcast at all, so a new model
must opt in rather than start leaking the moment it is wrapped.
Clients could also inject topics: socket.on('P2PSub') republished
anything a client sent to every other client. No app code ever published
locally, so this carried no legitimate traffic; events now flow
server -> client only. That also fixes a crash where a delete event's
null body was tagged unconditionally.
Hosts, Permissions and Groups now use app.filter.live() from
@simpleworkjs/frontend: one changed row is patched instead of reloading
the whole list, and each list gains a search box, a count, and (for
hosts) a type facet. Hosts search now covers IP, port and DNS provider,
not just the hostname.
Verified in a browser: cross-tab live create/update/delete, filtered
live inserts, and a domain-scoped viewer receiving only the events for
its own domain while the admin receives both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxAmDhp1KABTwWUFca7sQR
… had
The DNS page drove $.scope[Model] straight off the event topic inside
empty catch blocks. Two things made it a no-op: the scopes declared
`jq-repeat-index` -- an attribute jq-repeat WRITES onto rendered rows and
never reads as configuration -- instead of `jq-index-key`, so they had no
key and every remove/update lookup returned -1; and ModelPs published the
class name as the pk. The catch{} swallowed both, so the page looked
wired up while doing nothing.
Providers and dynamic records now use app.filter.live() with an explicit
key, and both gain a search box and a count. The same wrong attribute is
corrected on the Permissions list.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BxAmDhp1KABTwWUFca7sQR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Only the Proxy List updated itself, and only via code hand-written in that one view. Two root causes, plus a security problem found on the way.
Why only one page ever updated
Only
Hostpublished events. Every model registers aModelPsproxy, but onlyhost.jsexports it — the rest export the raw class, and the routes import those files directly.LocalGroupandPermissionpublished nothing, so the Groups and Permissions pages were subscribed to events that were never sent.ModelPspublished the class name as the primary key.getIndex()readmodel[Model._key], and on a static callmodelis the class — which always has a built-in.name. So for any model keyed on'name', the pk of every event was the literal string"LocalGroup". Creates appended a row matching nothing; updates would have duplicated the record.Hostbehaved only because its key is'host'andHost.hostis undefined.Security: the bridge broadcast everything to everyone
app.io.emit('P2PSub', …)sent every model event, with its full record, to every authenticated socket. No per-model or per-row read check — a viewer scoped to one domain received live payloads for every other domain in the install.utils/socket_pubsub.jsnow gates per socket, mirroring the REST read guards (Host→ viewer on that domain,DnsProvider→ admin, …), caching resolved rights briefly and busting them when a grant or group membership changes. Models without a gate are not broadcast at all, so a new model must opt in deliberately rather than start leaking the moment someone wraps it inModelPs.Clients could also inject topics:
socket.on('P2PSub')republished anything a client emitted to every other client. No app code has ever calledapp.publish(), so this carried no legitimate traffic; events now flow server → client only. That also fixes a crash where a delete event'snullbody was tagged unconditionally.Lists
Hosts, Permissions and Groups now use
app.filter.live()from@simpleworkjs/frontend. The one changed row is patched rather than reloading the whole list, so scroll position, checkbox selection and open dropdowns survive another user's edit. Each list gains a search box and a count; hosts also gets a type facet, and its search now covers IP, port and DNS provider instead of hostname only.Verification
216 unit tests pass, including 10 new ones for the read gate. Driven in a real browser:
model:Host:update:alpha.example.comwhiledelta.test.localwas withheld entirely — the admin tab received bothNote on the dependency
Stays at
^0.3.0, which0.3.1satisfies, sonpm cikeeps working. Two framework fixes this UI wants (Bootstrapd-flexrows not hiding, and a stale filter count) are in@simpleworkjs/frontend@0.3.1, which is merged and tagged but not yet published to npm. Once it is, a routinenpm installhere picks it up with no code change.🤖 Generated with Claude Code
https://claude.ai/code/session_01BxAmDhp1KABTwWUFca7sQR