diff --git a/luci-app-ssr-plus/Makefile b/luci-app-ssr-plus/Makefile index 378cf5361..fdb54d2c7 100644 --- a/luci-app-ssr-plus/Makefile +++ b/luci-app-ssr-plus/Makefile @@ -4,7 +4,7 @@ LUCI_TITLE:=luci-app-ssr-plus LUCI_PKGARCH:=all PKG_NAME:=luci-app-ssr-plus PKG_VERSION:=196 -PKG_RELEASE:=69 +PKG_RELEASE:=70 PKG_CONFIG_DEPENDS:= \ CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \ diff --git a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr index 3893d7638..240408992 100755 --- a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr +++ b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr @@ -609,6 +609,9 @@ prepare_clash_runtime_config() { local client_policy_stats local client_rules local enable_fake_ip + local socks5_auth + local socks5_user + local socks5_pass cache_file="$(get_clash_cache_file "$sid")" workdir="$(get_clash_workdir "$sid")" @@ -625,6 +628,16 @@ prepare_clash_runtime_config() { client_policy_stats="" client_rules=0 enable_fake_ip="$(uci_get_by_type server_subscribe enable_fake_ip "")" + socks5_auth="$(uci_get_by_type socks5_proxy socks5_auth noauth)" + socks5_user="$(uci_get_by_type socks5_proxy socks5_user "")" + socks5_pass="$(uci_get_by_type socks5_proxy socks5_pass "")" + + if [ "$socks5_auth" = "password" ]; then + if [ -z "$socks5_user" ] || [ -z "$socks5_pass" ]; then + echolog "警告:SOCKS5 代理未完整配置用户名或密码,已自动降级为无认证模式 (noauth)。" + socks5_auth="noauth" + fi + fi [ -s "$cache_file" ] || return 1 @@ -671,6 +684,12 @@ prepare_clash_runtime_config() { if [ -n "$socks_port" ] && [ "$socks_port" != "0" ]; then echo "socks-port: $socks_port" >>"$overlay_file" + if [ "$socks5_auth" = "password" ]; then + cat >>"$overlay_file" <<-EOF + authentication: + - "$socks5_user:$socks5_pass" + EOF + fi fi merge_stats="$(/usr/bin/lua "$CLASH_YAML_HELPER" merge "$raw_file" "$overlay_file" "$runtime_file" 2>/dev/null)" || return 1 diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua index c87d448a3..fcb9853da 100644 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/clash_yaml.lua @@ -56,7 +56,7 @@ local function load_yaml(path) end local function quote_nameserver_policy_keys(rendered) - rendered = rendered:gsub("[^\n]*", function(line) + rendered = rendered:gsub("[^\n]+", function(line) local indent = line:match("^(%s*)geosite:geolocation%-!cn:%s*$") if indent then return indent .. '"geosite:geolocation-!cn":' diff --git a/luci-theme-footstrap/Makefile b/luci-theme-footstrap/Makefile index d5b9331de..279334594 100644 --- a/luci-theme-footstrap/Makefile +++ b/luci-theme-footstrap/Makefile @@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap FOOTSTRAP_VERSION?= ifneq ($(FOOTSTRAP_VERSION),) PKG_VERSION:=$(FOOTSTRAP_VERSION) -PKG_RELEASE:=70 +PKG_RELEASE:=71 endif LUCI_TITLE:=Footstrap Theme diff --git a/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js b/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js index 8b7f59803..8e7e6497a 100644 --- a/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js +++ b/luci-theme-footstrap/htdocs/luci-static/resources/fs-fit.js @@ -1418,14 +1418,30 @@ function observeContent() { * normal with the engine ablated off. One pass and no `type` test, unlike the * `records.find()` above: this observer registers `childList` only, and a record of any * other type carries two empty node lists anyway. */ - let gone = 0, came = 0; - for (const m of records) + let gone = 0, came = 0, took = 0, gave = 0; + for (const m of records) { if (m.target === viewHost) { gone += m.removedNodes.length; came += m.addedNodes.length; } + if (m.type === 'childList') { took += m.removedNodes.length; gave += m.addedNodes.length; } + } if (gone && came) { forgetRest(); return; } - if (trustEngine) lateDrift(settled, grew, floorShrink); + /* NOT ON A BATCH THAT ONLY TOOK NODES AWAY — task twohalves. `dom.content()` is empty-then-fill, + * and where the two halves reach this observer as two batches, arming on the first aims the + * correction at a page that is about to stop existing: CI measured `wrote--834` for a refill + * that grew the page by 120, the reader thrown by the transient and nothing left for the real + * half. Superseding the armed call with the later batch was measured instead and is WORSE — + * a real tick delivers around twenty records, each one cancelling the last, and the sweep went + * from clean to 12 findings of ordinary drift (docs/anchoring.md, "The later batch must NOT + * win"). This is the discriminator that survives both: a batch that removed nodes, added none + * and left the floored box no taller is a removal, and a removal is not a page to correct + * against. A synchronous `dom.content()` — every real poll tick — delivers its removals and + * its additions in ONE batch and is untouched. */ + if (trustEngine) { + if (took && !gave && grew <= 0) why('emptying'); + else lateDrift(settled, grew, floorShrink); + } else scheduleAnchor(ref); }); for (const host of hosts) { diff --git a/luci-theme-shadcn/.dev/.env.example b/luci-theme-shadcn/.dev/.env.example new file mode 100644 index 000000000..dd588973c --- /dev/null +++ b/luci-theme-shadcn/.dev/.env.example @@ -0,0 +1,12 @@ +# Optional — run `pnpm setup:router` once and it writes/updates every value below. + +# OpenWrt router address — bare IP/hostname; the web proxy target and the +# .ut-sync ssh target both derive from it (ssh key selection etc. belongs +# in ~/.ssh/config) +# VITE_OPENWRT_HOST=192.168.1.1 + +# Dev server bind / port +VITE_DEV_PORT=5173 + +# 0.0.0.0 = LAN; 127.0.0.1 = local only +VITE_DEV_HOST=0.0.0.0 diff --git a/luci-theme-shadcn/.dev/.npmrc b/luci-theme-shadcn/.dev/.npmrc new file mode 100644 index 000000000..8d6010420 --- /dev/null +++ b/luci-theme-shadcn/.dev/.npmrc @@ -0,0 +1,4 @@ +# Fail `pnpm install` early with a clear message on unsupported Node versions +# (the range in package.json `engines` mirrors Vite 8's requirement) instead +# of letting the install die somewhere deep inside a dependency. +engine-strict=true diff --git a/luci-theme-shadcn/.dev/docs/build.md b/luci-theme-shadcn/.dev/docs/build.md new file mode 100644 index 000000000..67cf68abe --- /dev/null +++ b/luci-theme-shadcn/.dev/docs/build.md @@ -0,0 +1,46 @@ +# Build & dev server + +**Dual-layer build**: source in `.dev/` → OpenWrt-compatible output committed to `htdocs/luci-static/`. + +| source | output | +| ----------------------------------- | -------------------------------------------------------------------------------------- | +| `.dev/src/media/main.css` | `htdocs/luci-static/shadcn/main.css` | +| `.dev/src/media/login.css` | `htdocs/luci-static/shadcn/login.css` | +| `.dev/src/resource/*.js` | `htdocs/luci-static/resources/*.js` (`menu-shadcn`, `sidebar-shadcn`, `router-shadcn`) | +| `.dev/src/media/patches/*` | `htdocs/luci-static/shadcn/patches/*` (see [patches.md](patches.md)) | +| `.dev/public/shadcn/` | `htdocs/luci-static/shadcn/` — icons/images copied as-is | +| `ucode/template/themes/shadcn/*.ut` | not processed by Vite; pushed to a device via the SSH dev plugin | + +`htdocs/` is generated output checked into git. Rebuild it with `pnpm build`, or trigger the manual `frontend-assets-build.yml` workflow, which builds and commits `htdocs/**`. + +## Terser options that are load-bearing + +`.dev/src/resource/*.js` goes through terser (compress + local-scope mangle, no bundling); each file stays a standalone LuCI `L.require()`-able module. Two options must not be dropped: + +- `compress.directives: false` — keeps the leading `'require '` strings that LuCI's dependency scanner reads; +- `mangle.toplevel: false` — keeps top-level names (plus `parse.bare_returns` for the top-level `return`). + +## Dev-server environment + +All env vars are optional. `VITE_OPENWRT_HOST` is the bare router address (default `192.168.1.1`); the web proxy target and the `.ut`-sync SSH target (`root@`) both derive from it — key selection etc. belongs in `~/.ssh/config`. + +`ucode/template/themes/shadcn/*.ut` is pushed whole to `/usr/share/ucode/luci/template/themes/shadcn/` on dev-server startup and on every save (tar over ssh stdin), and `/cgi-bin` page loads wait for in-flight pushes. + +`pnpm setup:router` writes all of that to `.env` and installs the SSH key on the device in one shot (`pnpm setup:router ` to run non-interactively; safe to re-run after a reflash). + +## `vite.config.ts` plugins + +- `local-serve-plugin` — serves `main.css`/`login.css`/sidebar, menu & router JS at their `/luci-static/...` paths during `pnpm dev` and forces a full reload on change +- `ut-sync-plugin` — pushes the `.ut` template dir to the router over SSH (full push on startup + debounced push on save; `/cgi-bin` requests wait for pending pushes) +- `redirect-plugin` — redirects `/` to `/cgi-bin/luci` in dev +- `luci-js-compress` — runs `.dev/src/resource/*.js` through terser into `resources/` +- `mock-pages-plugin` — serves saved page snapshots at `/mocks/` against the live theme, and injects the mock bar into both those and proxied device pages (see [mock-pages.md](mock-pages.md)) + +## Tests & formatting + +No linter CLI; the only tests are `.dev/tests/*.test.js` (plain `node:test`, no deps), run with `pnpm test`. Prettier (with `prettier-plugin-tailwindcss`) runs on format-on-save and sorts `@apply`/class lists — don't hand-reorder them. + +## Releases + +- `Makefile` (`PKG_VERSION` / `PKG_RELEASE`) is the OpenWrt package manifest, built via `feeds/luci/luci.mk`. +- `.github/workflows/build-theme.yml` builds `.ipk`/`.apk` via `eamonxg/build-luci-package` on version tags, pushes to `main`/`feat/**`, or when the commit message contains `[build]`. diff --git a/luci-theme-shadcn/.dev/docs/css.md b/luci-theme-shadcn/.dev/docs/css.md new file mode 100644 index 000000000..a958b1dcc --- /dev/null +++ b/luci-theme-shadcn/.dev/docs/css.md @@ -0,0 +1,24 @@ +# CSS conventions + +Style with TailwindCSS v4 `@apply`, using CSS Nesting (`&:hover`, `&[disabled]`, `.parent &`, etc.) for scoped selectors — this is the dominant pattern across every component file. Fall back to raw CSS declarations only when `@apply` can't express the rule: custom properties, `@keyframes`/`animation`/`filter`, `clip-path`, `backdrop-filter`, and inline SVG data-URI backgrounds. The single deliberate exception is `patches/*.css`, plain CSS by design — see [patches.md](patches.md). + +## Import order + +`main.css` import order is meaningful (later imports win the cascade): `_tokens.css` → `_base.css` → `_layout.css` → `components/_*.css` → `_utilities.css` → `_shared.css`. New component styles get their own `components/_name.css`, imported before `_utilities.css`. Third-party app patches are **not** bundled into `main.css` — they load on demand per page. + +Built CSS keeps Tailwind's native `@layer` structure. Theme partials (`_base.css`, `components/*`, `_utilities.css`, …) are plain unlayered CSS — organization comes from the file split, never wrap rules in `@layer`. Unlayered partials outrank Tailwind's layered base/utilities regardless of specificity; the OKLCH tokens already gate browsers to ones with `@layer` support. + +## Tokens + +- **Token source**: the engine and resolver (operators, `createResolver`, CSS emission helpers) come from `@eamonxg/luci-theme-tokens` (`/engine`, `/emit`); this repo only keeps `tokens/spec.js` (derivations, baked-alpha variants) + `tokens/defaults.js` (input colors). Edit those, then run `pnpm gen:tokens`. Do not edit generated `src/media/_tokens.css` directly. +- **`_tokens.css`**: generated flat OKLCH custom properties for light and dark modes plus the shared `@theme inline` mapping. It is imported by both `main.css` and `login.css`; runtime token-based `color-mix()` and relative `oklch(from …)` are prohibited. +- **`login.css`**: separate Vite build entry for the login page; it does **not** import `main.css`, but re-imports the generated `_tokens.css`. + +## Dark mode + +`@custom-variant dark` keyed on `[data-darkmode=true]`, set by an inline script in `header.ut` before paint (reads `localStorage['shadcn.theme']`) to avoid a flash of the wrong theme. + +## Icons, two sources + +- `.dev/src/assets/icons/` (Lucide SVGs) are referenced from CSS via the `@assets` alias as `mask-image`/`mask`, so they inherit `currentColor`. +- `.dev/public/shadcn/icons/` are SVGs referenced directly via ``/JS (sidebar, menu, login, theme toggle) and copied verbatim to `htdocs/luci-static/shadcn/icons/`. diff --git a/luci-theme-shadcn/.dev/docs/mock-pages.md b/luci-theme-shadcn/.dev/docs/mock-pages.md new file mode 100644 index 000000000..1ff23ab64 --- /dev/null +++ b/luci-theme-shadcn/.dev/docs/mock-pages.md @@ -0,0 +1,33 @@ +# Mock pages + +Style a third-party app's page — write or adjust its `patches/*.css`, or check a `main.css`/component change against it — **without having the app (or a device) installed**. Snapshots live in `.dev/mocks/*.html` (git-ignored: large, device-specific, and they go stale). Served by `mock-pages-plugin` in `.dev/vite.config.ts`. + +## The mock bar + +Every HTML page the dev server hands out — proxied device pages and served snapshots alike — gets `scripts/mock-bar.client.js` (served at `/mocks/__bar.js`), a floating bottom-left bar in a Shadow DOM, so theme and patch CSS can neither restyle it nor be polluted by it. + +On a device page it lists what `.dev/mocks/` holds, so the workflow is reachable without typing the `/mocks/` URL: `◆` appears when this page's `data-page` matches a snapshot and opens it in one click, `⊕` captures the open page, and an empty `.dev/mocks/` shrinks the bar to a lone `⊕`. Inside a snapshot it names the open one, steps through the rest, and `↩` goes back to the same page on the device. `✕` collapses it to a dot, remembered in `localStorage['shadcn.mockbar.collapsed']`. + +The snapshot list is injected inline next to the script; both tags carry `data-shadcn-mock`, which is how a capture strips them back out — a snapshot must never bake in a list that is re-injected, current, on every serve. + +## Capture + +Open the page through the dev proxy and hit `⊕` on the mock bar — or press Alt/Option+Shift+S, or call `__shadcnMockCapture()`. It POSTs the live DOM to `/mocks/__save`, which writes `.dev/mocks/.html` — doctype included, dev-only script tags stripped, custom-header gated so a foreign origin can't drive it. Hand-saved HTML works too; the filename is free, since a snapshot's identity is its ``. + +**Capture from a device running _this_ theme — snapshots are not portable between themes.** A snapshot copies a rendered page verbatim, so it hard-codes the rendering theme in its stylesheet links (`/luci-static/shadcn/main.css` plus that page's patch), in the device's UCI token overrides inlined as ` + {% endif %} + + + + + + {% if (!blank_page): %} + + + + + + +
+
+
+
+ + +
+
+ {#- Command palette (⌘K): trigger only — the panel is built lazily + by menu-shadcn.js. The button wears the same msgid as the panel's + input placeholder, so the trigger reads as a preview of what it + opens. Not 'Search': that msgid is defined in exactly one LuCI + pot (luci-app-adblock) and would read English wherever adblock is + absent, whereas 'Type to filter…' comes from + luci-app-package-manager, a dependency of the default luci + collection. aria-label repeats the visible text (it hides below + the md breakpoint, and a mismatch would break Label in Name). -#} + +
+ +
+
+ +
+ {% if (getuid() == 0 && root_pw_unset && ctx && ctx.authsession): %} +
+

{{ _('No password set!') }}

+

{{ _('There is no password set on this router. Please configure a root password to protect the web interface.') }}

+ {% if (dispatcher.lookup("admin/system/admin")): %} + + {% endif %} +
+ {% endif %} + + {% if (boardinfo && boardinfo.rootfs_type == "initramfs"): %} +
+

{{ _('System running in recovery (initramfs) mode.') }}

+

{{ _('No changes to settings will be stored and are lost after rebooting. This mode should only be used to install a firmware upgrade') }}

+ {% if (dispatcher.lookup("admin/system/flash")): %} + + {% endif %} +
+ {% endif %} + + + + + {% endif %} diff --git a/luci-theme-shadcn/ucode/template/themes/shadcn/sysauth.ut b/luci-theme-shadcn/ucode/template/themes/shadcn/sysauth.ut new file mode 100644 index 000000000..b5aa8610d --- /dev/null +++ b/luci-theme-shadcn/ucode/template/themes/shadcn/sysauth.ut @@ -0,0 +1,106 @@ +{# + luci-theme-shadcn: login page + Copyright 2025 eamonxg + Licensed to the public under the Apache License 2.0. +-#} + +{%const boardinfo = ubus.call('system', 'board') ?? {};-%} +{% include('header', { blank_page: true }) %} + + +
+ + + + + + + +
+ + + +{% include('footer', { blank_page: true }) %} diff --git a/wwand/Makefile b/wwand/Makefile index 37c9ed7d5..1431853cd 100644 --- a/wwand/Makefile +++ b/wwand/Makefile @@ -50,11 +50,11 @@ include $(TOPDIR)/rules.mk PKG_NAME:=wwand PKG_VERSION:=1.6.6_p22 -PKG_RELEASE:=33 +PKG_RELEASE:=34 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/ddimension/wwand.git -PKG_SOURCE_VERSION:=f1c7fb3195f3ed7a69548bb5dbac648c4897031b +PKG_SOURCE_VERSION:=74c78019c425c6cbfb3938885046419e4a2ed9ba PKG_MIRROR_HASH:=skip PKG_LICENSE:=GPL-2.0-only