diff --git a/neode-ui/src/views/server/OpenWrtGateway.vue b/neode-ui/src/views/server/OpenWrtGateway.vue index 00e92e09..cda0374b 100644 --- a/neode-ui/src/views/server/OpenWrtGateway.vue +++ b/neode-ui/src/views/server/OpenWrtGateway.vue @@ -78,16 +78,15 @@ interface ScannedNetwork { // paint the last-known state instantly and a mount inside the TTL issues no // new RPC. No item id in this key — there is exactly one configured gateway // per node, not a per-item collection (route is the fixed `server/openwrt`, -// no :id param). `load(params)` is the explicit force-refresh path (connect, -// tollgate provision/save all want an unconditional re-fetch); `pendingParams` -// lets the fetcher read the params `load()` was called with without changing -// the cache key. -let pendingParams: Record | undefined +// no :id param). This fetcher only ever re-fetches with `{}` params (the +// server-persisted config, no credentials needed) — an explicit-params call +// (Connect form submit, or any caller passing connectedParams) never routes +// through this fetcher at all; see load()'s bypass path below (WR-03). const routerResource = useCachedResource({ key: 'server.openwrt-status', fetcher: (signal) => rpcClient.call({ method: 'openwrt.get-status', - params: pendingParams ?? {}, + params: {}, signal, timeout: 30000, dedup: true, @@ -149,9 +148,43 @@ const masqEnabled = ref(true) // Force-fetch: always refetches regardless of TTL. Used by every explicit // user action (connect, tollgate provision/save) that must reflect a fresh // server-side state, and by onMounted below when the cache is missing/stale. +// +// When explicit params are supplied, this bypasses routerResource.refresh() +// and calls rpcClient directly instead (WR-03): resources.ts's +// store.refresh() dedupes concurrent calls for the same key, so routing an +// explicit-params call (e.g. the Connect form's submit) through it could +// silently coalesce into an unrelated refresh already in flight (e.g. +// useCachedResource's own TTL-gated onActivated/focus revalidation) — +// the caller's own params would never actually be sent, and the caller has +// no way to tell. Writing the resolved result directly into +// routerResource.entry keeps the cache/TTL/status-panel rendering +// consistent with what a normal refresh() success does. async function load(params?: Record) { error.value = '' - pendingParams = params + if (params) { + try { + const result = await rpcClient.call({ + method: 'openwrt.get-status', + params, + timeout: 30000, + maxRetries: 1, + }) + routerResource.entry.data = result + routerResource.entry.error = null + routerResource.entry.fetchedAt = Date.now() + routerResource.entry.loadState = 'ready' + showConnectForm.value = false + connectedParams.value = params + } catch (e) { + const message = e instanceof Error ? e.message : String(e) + if (message.includes('No router configured')) { + showConnectForm.value = true + } else { + error.value = message + } + } + return + } await routerResource.refresh() const err = routerResource.error.value if (err) { @@ -162,7 +195,6 @@ async function load(params?: Record) { } } else { showConnectForm.value = false - if (params) connectedParams.value = params } }