From: Paul Donald Date: Sat, 17 Jan 2026 19:18:37 +0000 (+0100) Subject: luci-base: dispatcher; improve wildcard routing X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=df90c60a72e51f4e402d42a7d2c285279a44d203;p=openwrt-luci.git luci-base: dispatcher; improve wildcard routing When a menu JSON describes an endpoint like "admin/app/edit/*" : { ... and the user navigates to admin/app/edit/ instead of the URI which supplies an ID to edit, like admin/app/edit/myfoobarthing we now can use 'alias' and 'rewrite' to redirect transparently for more generic endpoints. Without this, it's possible to navigate to admin/app/edit/ and the corresponding view does not receive a suitable path/ID to derive data from, when views use anything derived via L.env.requestpath. This menu JSON "admin/app/entry/*": { "action": { "type": "view", "path": "app/entry" } }, "admin/app/entries": { "title": "entries", "order": 5, "action": { "type": "view", "path": "app/entries" } }, "admin/app/entry": { "action": { "type": "alias", "path": "admin/app/entries" } }, Produces JSON with a wildcardaction element "entry": { "satisfied": true, "wildcard": true, "action": { "type": "alias", "path": "admin/app/entries" }, "wildcardaction": { "type": "view", "path": "app/entry" } }, "entries": { "satisfied": true, "action": { "type": "view", "path": "app/entries" }, "order": 5, "title": "entries" }, Signed-off-by: Paul Donald --- diff --git a/modules/luci-base/ucode/dispatcher.uc b/modules/luci-base/ucode/dispatcher.uc index 09e53b885a..0b31a38a10 100644 --- a/modules/luci-base/ucode/dispatcher.uc +++ b/modules/luci-base/ucode/dispatcher.uc @@ -388,10 +388,12 @@ function build_pagetree() { for (let path, spec in data) { if (type(spec) == 'object') { let node = tree; + let has_wildcard = false; for (let s in match(path, /[^\/]+/g)) { if (s[0] == '*') { node.wildcard = true; + has_wildcard = true; break; } @@ -405,6 +407,12 @@ function build_pagetree() { if (type(spec[k]) == t) node[k] = spec[k]; + /* Preserve distinct actions for wildcard vs. base path */ + if (has_wildcard && type(spec.action) == 'object') + node.wildcardaction = spec.action; + else if (type(spec.action) == 'object') + node.action = spec.action; + node.satisfied = check_depends(spec); } } @@ -635,16 +643,27 @@ function resolve_page(tree, request_path) { if (!login && node.auth?.login) login = true; + /* If this node is marked as wildcard, check if the next segment + * matches a child node. Only apply wildcard behaviour (capturing + * remaining segments as args) if no child matches, allowing + * deeper routes like foo/bar/* to work alongside + * foo/* + */ if (node.wildcard) { - ctx.request_args = []; - ctx.request_path = ctx.path ? [ ...ctx.path ] : []; + let next_segment = request_path[i + 1]; + let has_matching_child = next_segment && node.children?.[next_segment]?.satisfied; - while (++i < length(request_path)) { - push(ctx.request_path, request_path[i]); - push(ctx.request_args, request_path[i]); - } + if (!has_matching_child) { + ctx.request_args = []; + ctx.request_path = ctx.path ? [ ...ctx.path ] : []; - break; + while (++i < length(request_path)) { + push(ctx.request_path, request_path[i]); + push(ctx.request_args, request_path[i]); + } + + break; + } } } @@ -986,6 +1005,11 @@ dispatch = function(_http, path) { let action = resolved.node.action; + /* If this node matched a wildcard and we have request args, + * prefer the wildcard-specific action when defined. */ + if (length(resolved.ctx.request_args) && type(resolved.node.wildcardaction) == 'object') + action = resolved.node.wildcardaction; + if (action?.type == 'arcombine') action = length(resolved.ctx.request_args) ? action.targets?.[1] : action.targets?.[0];