🛸 Sync 2026-08-05 08:32:18

This commit is contained in:
github-actions[bot]
2026-08-05 08:32:18 +08:00
parent 3b5b6ffcb7
commit cf2d942509
6 changed files with 47 additions and 25 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
FOOTSTRAP_VERSION?=
ifneq ($(FOOTSTRAP_VERSION),)
PKG_VERSION:=$(FOOTSTRAP_VERSION)
PKG_RELEASE:=4
PKG_RELEASE:=5
endif
LUCI_TITLE:=Footstrap Theme
@@ -303,8 +303,10 @@ function colorAxis(key, attr, hueProp, colorProp) {
* EXPLICITLY — including the default, so it overrides a router default (see the header; lsDel would
* mean "inherit") — and remove the property AT the default, so 02-tokens' own value shows through.
* They differ ONLY in how the number formats onto the property (px vs a 0..2 multiplier), so that is
* the one argument that varies. The sd() field name is passed in explicitly: unlike enum/hue it is
* NOT the key minus 'fs-' ('fs-radius' -> rounding, 'fs-tint-strength' -> tint_strength). */
* the one argument that varies. The sd() field name is passed in explicitly because ONE instance
* needs a RENAME rather than a spelling: 'fs-radius' -> rounding. The other four would fall out of
* the same hyphen fold enumAxis and colorAxis do ('fs-tint-strength' -> tint_strength), but a
* factory that is right for four keys out of five is the trap those two walked into. */
function propAxis(key, sdKey, prop, min, max, dfl, fmt) {
const inRange = (n) => (typeof n === 'number' && n >= min && n <= max);
const def = () => { const d = sd(sdKey); return inRange(d) ? d : dfl; };
@@ -518,12 +518,18 @@ function silence(el) {
*
* A foreign sheet is injected by ONE page and has no business painting any other. Before this, an
* invasive sheet made the whole document spent (documentPoisoned) and the SPA fell back to a full
* load on the way OUT correct, but stock LuCI pays it: `view/status/cpu.js` and its four realtime
* siblings each `document.head.append(E('style', …))` at MODULE EVAL, and that style carries
* `svg text { fill: #eee; font-size: 9pt }` real properties on a bare selector, so invasive by the
* only definition that catches `[class] { padding: 0 !important }`. Measured on the router: 37 of 38
* pages navigated in-place instead of 38, `status/realtime/load` 15 ms -> 157 ms, and the whole
* realtime family 2-10x slower.
* load on the way OUT correct, and paid by ORDINARY pages: `luci-app-filemanager`, a stock app,
* calls its `insertCss()` at MODULE EVAL and lands two <style>s in <head> (its own rules plus the
* HexEditor's), and `luci-app-ssclash` adds four more as the Ace editor initialises. Both carry
* real properties on bare selectors (`.hexview:focus`, `.ace_gutter`), which is invasive by the
* only definition that also catches `[class] { padding: 0 !important }`.
*
* Measured on owrt2512, 25.12.4, with ownership taken out of documentPoisoned() and put back:
* leaving either page is a FULL LOAD, 5 runs of 5, and with ownership all 5 are in place
* medians 24 ms (filemanager) and 27 ms (ssclash). The control is a page that injects nothing
* (System -> General): in place either way. Stock LuCI's own pages inject nothing into <head> on
* 24.10/25.12 the realtime graphs style their SVG text with an inline `style=` attribute so
* what this costs a router is decided entirely by which apps are installed on it.
*
* Removing the sheet on the way out is NOT the fix, and it is the obvious one: the append is at
* module top level, `L.require` caches the module, so a second visit re-runs nothing and the page
@@ -88,7 +88,7 @@ if ! uci -q get footstrap.settings >/dev/null 2>&1; then
uci commit footstrap
fi
# Login/page background upload (Appearance -> Background image). cgi-upload writes the image to
# Login/page background upload (Appearance -> Wallpaper -> File). cgi-upload writes the image to
# /etc/footstrap/login-bg (a fixed, ACL-scoped path — kept in /etc so it survives a package upgrade
# and, via lib/upgrade/keep.d, a sysupgrade). uhttpd serves only /www, so a symlink there exposes it
# at /luci-static/footstrap/bg (uhttpd follows symlinks). Both are recreated on every install/upgrade
+1 -1
View File
@@ -141,7 +141,7 @@
* strength, 0 = none, up to 2. fs-prefs.js/head.ut set it inline (value/100) when the user
* drags the slider; paired with the hue, it is the second half of the router-identity Tint. */
--fs-tint-strength: 1;
/* The uploaded login/page background (Appearance Background image). --fs-login-bg-url is
/* The uploaded login/page background (Appearance Wallpaper File). --fs-login-bg-url is
* the url() head.ut/fs-prefs.js stamp inline; `none` here is the same stand-on-its-own
* default the hues carry above (a var() with no definition is invalid at computed-value
* time). --fs-photo-scrim is the wash laid OVER the photo so on-canvas text stays legible on
@@ -28,25 +28,39 @@
because the markup was assumed to be stock LuCI's. It is OURS: name the form instead. #}
<form method="post" class="fs-login">
{#
THE BACKEND'S MESSAGE WINS OVER THE CREDENTIALS ONE, and the order matters more than it looks.
`fuser` is not "the password was wrong" — it is the USERNAME the form posted, set in BOTH
branches of dispatcher.uc that render a sysauth. On the second step of a pluggable login (the
OTP prompt, dispatcher.uc:1018) the credentials were CORRECT and `fuser` is still set, with
`auth_message` carrying what actually happened. The generic template guards its message with
`&& !fuser`, so that step tells the user "Invalid username and/or password!" — about a password
that was right — and swallows "Too many failed attempts" / "System time is not calibrated"
entirely. luci-theme-bootstrap does not make that trade, and neither does this: message first,
credentials as the fallback. `auth_plugin` names the challenge that stopped the login, and it
is the one signal separating "the backend is asking for something" from an ordinary notice.
TWO INDEPENDENT ALERTS, because neither variable on its own says which of the two dispatcher
branches rendered this page — and each of the two spellings in the tree picks one and gets the
other wrong.
`fuser` is not "the password was wrong": it is the USERNAME the form posted, set in BOTH
branches. On the second step of a pluggable login (the OTP prompt, dispatcher.uc:1018) the
credentials were CORRECT and `fuser` is still set, so the generic template's
`auth_message && !fuser` reports "Invalid username and/or password!" about a password that was
right and swallows "Too many failed attempts" / "System time is not calibrated" entirely.
`auth_message` is not "the password was right" either. The FIRST branch copies it out of
get_challenges() BEFORE the password is checked (dispatcher.uc:956), and that call answers with
a message on every pending result — 'Additional verification required' when the plugin has none
of its own (authplugins.uc:312). So on a router running luci-plugin-2fa both are set at once
whenever a password is rejected, and luci-theme-bootstrap's `elif (fuser)` then never tells
that user their password was wrong.
`auth_plugin` is what separates the branches: it is set ONLY in the second one
(dispatcher.uc:1018), i.e. exactly when session_setup() ACCEPTED the credentials. Gate the
credentials alert on `!auth_plugin`, render the message BESIDE it rather than instead of it,
and all four states are covered: a wrong password, a wrong password with a challenge pending
(both alerts), the OTP prompt, a failed OTP. The classes keep them apart once they can appear
together — `error` is the danger variant, the plugin's own notice stays amber.
-#}
{% if (fuser && !auth_plugin): %}
<div class="alert-message error">
<p>{{ _('Invalid username and/or password! Please try again.') }}</p>
</div>
{% endif %}
{% if (auth_message): %}
<div class="alert-message{% if (auth_plugin): %} warning{% endif %}">
<p>{{ auth_message }}</p>
</div>
{% elif (fuser): %}
<div class="alert-message warning">
<p>{{ _('Invalid username and/or password! Please try again.') }}</p>
</div>
{% endif %}
<div class="cbi-map">