diff --git a/clashoo/Makefile b/clashoo/Makefile index c55e7796d..b1918a71c 100644 --- a/clashoo/Makefile +++ b/clashoo/Makefile @@ -5,14 +5,14 @@ PKG_UPSTREAM_VERSION:=1.19.33 PKG_UPSTREAM_TAG:=v$(PKG_UPSTREAM_VERSION) PKG_SHORT_SHA:=3f27ef3 -PKG_VERSION:=2026.10.27-r5 -PKG_RELEASE:=22 +PKG_VERSION:=2026.11.04.$(PKG_UPSTREAM_VERSION) +PKG_RELEASE:=23 PKG_SOURCE:=$(PKG_NAME)-stable-$(PKG_UPSTREAM_TAG)-$(PKG_SHORT_SHA).tar.gz PKG_SOURCE_SUBDIR:=$(PKG_NAME)-stable-$(PKG_UPSTREAM_TAG)-$(PKG_SHORT_SHA) PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR) PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-clashoo/releases/download/mihomo-src -PKG_SOURCE_VERSION:=aab968687034c10cd073a21293a3dc200a95171c +PKG_SOURCE_VERSION:=2d264afc85d34b9a70e07c8505e7e94c235d79c9 PKG_HASH:=skip PKG_BUILD_VERSION:=$(PKG_UPSTREAM_TAG) diff --git a/clashoo/files/etc/init.d/clashoo b/clashoo/files/etc/init.d/clashoo index a58f650ea..075c5ee52 100755 --- a/clashoo/files/etc/init.d/clashoo +++ b/clashoo/files/etc/init.d/clashoo @@ -1551,6 +1551,34 @@ _ensure_cn_domain_rule_provider() { ' "$CONFIG_YAML" 2>/dev/null || true } +# fake-ip-filter 引用当前配置没定义的 rule-set 时,mihomo 会拒绝启动 +# (dns.fake-ip-filter[i] "RULE-SET,x,real-ip": rule-set 'x' not found)。 +# 切换配置后最常出现,启动前丢掉并记日志,免得整机莫名断网。 +prune_fakeip_missing_rulesets() { + command -v yq >/dev/null 2>&1 || return 0 + [ -s "$CONFIG_YAML" ] || return 0 + + local providers entry _rs + providers="$(yq -M e -r '.rule-providers // {} | keys | .[]' "$CONFIG_YAML" 2>/dev/null)" + + yq -M e -r '.dns."fake-ip-filter" // [] | .[]' "$CONFIG_YAML" 2>/dev/null | + while IFS= read -r entry; do + case "$entry" in + RULE-SET,*|rule-set:*) ;; + *) continue ;; + esac + _rs="${entry#RULE-SET,}" + _rs="${_rs#rule-set:}" + _rs="${_rs%%,*}" + [ -n "$_rs" ] || continue + printf '%s\n' "$providers" | grep -Fxq "$_rs" && continue + CLASHOO_DROP_ENTRY="$entry" yq -M e -i \ + '.dns."fake-ip-filter" = [.dns."fake-ip-filter"[] | select(. != strenv(CLASHOO_DROP_ENTRY))]' \ + "$CONFIG_YAML" 2>/dev/null || true + log_msg "Skipped fake-ip-filter entry, rule-set not found: $entry" "已跳过 fake-ip-filter 条目,规则集不存在:$entry" + done +} + append_luci_fake_ip_filter_extras() { [ -n "${CLASHOO_KEEP_FAKEIP_FILTER:-}" ] || return 0 command -v yq >/dev/null 2>&1 || return 0 @@ -2252,6 +2280,7 @@ _start_prepare_config() { [ "$_reload_candidate" = "1" ] || _prepare_geo_bootstrap_mode _remap_dns_system_nameserver >/dev/null 2>&1 ensure_dns_cn_policy_fallback >/dev/null 2>&1 + prune_fakeip_missing_rulesets if [ ! -f "$CONFIG_YAML" ];then diff --git a/clashoo/files/usr/share/clashoo/runtime/mixin.uc b/clashoo/files/usr/share/clashoo/runtime/mixin.uc index 6ace606f4..61e0e0c90 100644 --- a/clashoo/files/usr/share/clashoo/runtime/mixin.uc +++ b/clashoo/files/usr/share/clashoo/runtime/mixin.uc @@ -175,7 +175,7 @@ function dns_server(address, protocol, port) { let dns_roles = {}; uci.foreach('clashoo', 'dnsservers', function(sec) { - if (b(sec.enabled) == false) return; + if (sec.enabled != null && b(sec.enabled) == false) return; let role = s(sec.ser_type, 'nameserver'); if (role == 'fallback' && cfg['dns']['enhanced-mode'] == 'fake-ip') return; let srv = dns_server(sec.ser_address, sec.protocol, sec.ser_port); @@ -202,7 +202,7 @@ if (length(bootstrap) && (!dns_present['default-nameserver'] || dns_force['defau let policies = {}; let proxy_policies = {}; uci.foreach('clashoo', 'dns_policy', function(sec) { - if (b(sec.enabled) == false) return; + if (sec.enabled != null && b(sec.enabled) == false) return; let role = s(sec.policy_type, 'nameserver-policy'); if (role != 'nameserver-policy' && role != 'proxy-server-nameserver-policy') return; let matcher = trim(sec.matcher || ''); @@ -252,7 +252,7 @@ if (need_cn_rs) { if (ab('authentication')) { cfg['authentication'] = []; uci.foreach('clashoo', 'authentication', function(sec) { - if (b(sec.enabled) == false) return; + if (sec.enabled != null && b(sec.enabled) == false) return; let user = trim(sec.username || ''); let pass = trim(sec.password || ''); if (user) push(cfg['authentication'], user + ':' + pass); @@ -262,7 +262,7 @@ if (ab('authentication')) { /* ── hosts ────────────────────────────────────────── */ cfg['hosts'] = {}; uci.foreach('clashoo', 'hosts', function(sec) { - if (b(sec.enabled) == false) return; + if (sec.enabled != null && b(sec.enabled) == false) return; let domain = trim(sec.adress || ''); let ip = trim(sec.ip || ''); if (domain && ip) cfg['hosts'][domain] = ip; diff --git a/frpc-next/Makefile b/frpc-next/Makefile index 40326dbe3..ec05fcd92 100644 --- a/frpc-next/Makefile +++ b/frpc-next/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=frpc-next -PKG_VERSION:=0.71.1 -PKG_RELEASE:=2 +PKG_VERSION:=0.72.0 +PKG_RELEASE:=3 # Architecture mapping for pre-built binaries ifeq ($(ARCH),mipsel) diff --git a/honk/Makefile b/honk/Makefile index d0c8e3697..268a55781 100644 --- a/honk/Makefile +++ b/honk/Makefile @@ -5,8 +5,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=honk -PKG_VERSION:=2026.11.03~88d470c -PKG_RELEASE:=29 +PKG_VERSION:=2026.11.04~88d470c +PKG_RELEASE:=30 HONK_SRC_URL:=https://github.com/daeuniverse/honk.git HONK_SRC_VERSION:=88d470c662e0ee7a3de3e67afc6129f67e66c1bf diff --git a/lua-periphery/Makefile b/lua-periphery/Makefile index 31a7ee26b..370c21f51 100644 --- a/lua-periphery/Makefile +++ b/lua-periphery/Makefile @@ -1,12 +1,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=lua-periphery -PKG_VERSION:=2.5.1 -PKG_RELEASE:=2 +PKG_VERSION:=2.5.3 +PKG_RELEASE:=3 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/vsergeev/lua-periphery.git -PKG_SOURCE_VERSION:=6c966f76a1ba280d71871d737f4802c34d72ff55 +PKG_SOURCE_VERSION:=3c60e814022353157d1c47b5acc126bb6eb5d12f PKG_MIRROR_HASH:=skip PKG_LICENSE:=MIT diff --git a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js index 43e6ccac6..25c03d410 100644 --- a/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js +++ b/luci-app-adguardhome/htdocs/luci-static/resources/view/adguardhome/config.js @@ -35,8 +35,6 @@ const DANGEROUS_RW_PATHS = ['/', '/etc', '/root', '/usr', '/bin', '/sbin', '/lib const POLL_INTERVAL = 5; -const RUNNING_SPAN = `${_('Running')}`; -const NOT_RUNNING_SPAN = `${_('Not running')}`; const STORAGE_KEY = 'luci-app-adguardhome'; const STORAGE_KEY_CORE = 'luci-app-adguardhome_core_update'; @@ -213,35 +211,71 @@ async function getLatestRelease(channel, force) { } } -// Hint appended to the "Current Version" row; onUpdate runs when "Update" is clicked. -// Built from DOM nodes only (no HTML strings). +// Hint appended to the "Current Version" row. The whole text is the button: a click +// runs onUpdate (go to the Core Update tab). A span, not a link, so the theme's link +// colour / underline never applies. Built from DOM nodes only (no HTML strings). function buildVersionHint(release, onUpdate) { - return E('span', { 'id': 'agh-version-hint', 'style': 'margin-left: 12px;' }, [ - E('strong', { 'style': 'color: var(--warn-color-high, #d9822b);' }, - '→ ' + _('New version %s available').replace('%s', release.tag)), - ' ', - E('a', { 'href': release.url, 'target': '_blank', 'rel': 'noopener noreferrer' }, _('Release notes')), - ' | ', - E('a', { - 'href': '#', - 'click': function(ev) { + return E('span', { + 'id': 'agh-version-hint', + 'role': 'button', + 'tabindex': '0', + 'title': _('Go to Core Update'), + 'style': 'margin-left: 12px; cursor: pointer;', + 'click': function(ev) { + ev.preventDefault(); + onUpdate(); + }, + 'keydown': function(ev) { + if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); onUpdate(); } - }, _('Update')) - ]); + } + }, E('strong', { 'style': 'color: var(--warn-color-high, #d9822b);' }, + '\u2192 ' + _('New version %s available').replace('%s', release.tag))); } -// Tighter spacing for the "Current Version" / "Service Status" rows. The themes -// give every row a lot of padding; !important is needed to win over them. -const STATUS_COMPACT_CSS = - '.agh-status-row { margin: 0 !important; padding-top: 2px !important; padding-bottom: 2px !important; min-height: 0 !important; }' + - '.agh-status-row .cbi-value-title, .agh-status-row .cbi-value-field { margin: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important; min-height: 0 !important; line-height: 1.5 !important; }'; +// Header card above the settings: one line with the Service Status pill (which is also +// the Enable switch) and the Current Version. The pill follows the style of the Main +// Switch of luci-app-bypass. +const HEAD_CSS = + '.agh-head { --agh-on-fg:#1a7f37; --agh-on-bg:#dafbe1; --agh-warn-fg:#9a6700; --agh-warn-bg:#fff8c5; --agh-off-fg:#82071e; --agh-off-bg:#ffebe9; display:flex; flex-direction:column; gap:.5rem; margin:.5rem 0 1rem; padding:.7rem 1rem; border-radius:var(--radius-card, 6px); background:var(--surface-raised, rgba(128,128,128,.06)); box-shadow:0 4px 8px rgba(0,0,0,.03); }' + + ':root[data-argon-mode="dark"] .agh-head, :root[data-darkmode="true"] .agh-head { --agh-on-fg:#7ee2a0; --agh-on-bg:rgba(46,160,67,.22); --agh-warn-fg:#e3b341; --agh-warn-bg:rgba(187,128,9,.22); --agh-off-fg:#ffa9ae; --agh-off-bg:rgba(248,81,73,.18); }' + + '.agh-head-row { display:flex; align-items:center; flex-wrap:wrap; gap:.4rem 2rem; }' + + '.agh-head-item { display:inline-flex; align-items:center; flex-wrap:wrap; gap:.25rem .75rem; line-height:1.5; }' + + '.agh-head-note { display:flex; align-items:flex-start; gap:.5rem; line-height:1.5; padding:.45rem .75rem; border-left:4px solid var(--error-color-high, #d9534f); border-radius:4px; background:rgba(217,83,79,.12); color:var(--error-color-high, #d9534f); font-weight:600; }' + + '.agh-head-note-icon { flex:none; font-size:1.15em; line-height:1.3; }' + + '.agh-switch { display:inline-flex; align-items:center; gap:.4rem; height:auto; min-height:0; line-height:1.4; padding:.25rem .8rem; margin:0; border-radius:999px; font-weight:600; font-size:.9rem; border:0; box-shadow:none; cursor:pointer; transition:filter .15s; }' + + '.agh-switch:hover { filter:brightness(.95); }' + + '.agh-switch::before { content:""; width:.55rem; height:.55rem; border-radius:50%; background:currentColor; }' + + '.agh-switch.on { color:var(--agh-on-fg); background:var(--agh-on-bg); }' + + '.agh-switch.warn { color:var(--agh-warn-fg); background:var(--agh-warn-bg); }' + + '.agh-switch.off { color:var(--agh-off-fg); background:var(--agh-off-bg); }'; +// The clickable of the Core Update tab in the tab menu (not the tab pane, which +// carries the same data-tab value). function findCoreUpdateTabLink(root) { - return root.querySelector('a[href="#cbi-tab-adguardhome-core_update"]') || - root.querySelector('a[href="#core_update"]') || - root.querySelector('[data-tab="core_update"]'); + return root.querySelector('.cbi-tabmenu li[data-tab="core_update"] a') || + root.querySelector('li[data-tab="core_update"] a') || + root.querySelector('a[href="#cbi-tab-adguardhome-core_update"]') || + root.querySelector('a[href="#core_update"]'); +} + +// Click the tab as soon as it exists and is visible; the tab menu is built and +// shown/hidden (depends) shortly after render, so retry for a couple of seconds. +function activateCoreUpdateTab(root, tries) { + const link = findCoreUpdateTabLink(root); + const li = link ? link.parentNode : null; + // LuCI marks every inactive tab 'cbi-tab-disabled', so that class means nothing here; + // a tab that is not available (no visible option yet) has display:none. + const hidden = li && li.style.display === 'none'; + if (link && !hidden) { + link.click(); + return; + } + if ((tries || 0) < 40) { + window.setTimeout(function() { activateCoreUpdateTab(root, (tries || 0) + 1); }, 50); + } } // The Core Update tab only exists while "Core Update" is ticked in General Settings. @@ -260,9 +294,8 @@ function isCoreUpdateTabEnabled(root) { // tab is created and selected on the next load. function goToCoreUpdateTab(root) { if (isCoreUpdateTabEnabled(root)) { - const link = findCoreUpdateTabLink(root); - if (link) { - link.click(); + if (findCoreUpdateTabLink(root)) { + activateCoreUpdateTab(root, 0); return; } } @@ -299,10 +332,6 @@ async function getStatus() { } } -function getStatusValue(isRunning) { - return isRunning ? RUNNING_SPAN : NOT_RUNNING_SPAN; -} - async function getVersion() { try { const res = await fs.exec('/usr/bin/AdGuardHome', ['--version']); @@ -316,16 +345,6 @@ async function getVersion() { } } -function updateStatus(node) { - const output = node ? node.querySelector('output') : null; - return output - ? async function() { - const isRunning = await getStatus(); - dom.content(output, getStatusValue(isRunning)); - } - : function() {}; -} - function validateAbsolutePath(value) { if (!value.startsWith('/')) { return _('Path must be absolute.'); @@ -465,22 +484,6 @@ return view.extend({ const map = new form.Map('adguardhome', _('AdGuard Home')); - const statusSect = map.section(form.TypedSection, 'status'); - statusSect.anonymous = true; - statusSect.cfgsections = function() { return ['status_section']; }; - - const versionOpt = statusSect.option(form.DummyValue, '_version', _('Current Version')); - versionOpt.cfgvalue = function() { - return version - ? esc(version) - : '' + esc(_('Not installed')) + ''; - }; - versionOpt.rawhtml = true; - - const statusOpt = statusSect.option(form.DummyValue, '_status', _('Service Status')); - statusOpt.rawhtml = true; - statusOpt.cfgvalue = function() { return getStatusValue(isRunning); }; - const mainSect = map.section(form.TypedSection, 'adguardhome'); mainSect.anonymous = true; @@ -493,9 +496,6 @@ return view.extend({ const enabledOpt = mainSect.taboption('general', form.Flag, 'enabled', _('Enable')); enabledOpt.default = '0'; enabledOpt.rmempty = false; - if (!coreExists) { - enabledOpt.description = '' + esc(_('Core binary not found. Enable the service to trigger an automatic download.')) + ''; - } const configFileOpt = mainSect.taboption('general', form.Value, 'config_file', _('Configuration file')); configFileOpt.placeholder = DEFAULT_CONFIG_FILE; @@ -672,21 +672,146 @@ return view.extend({ if (sessionStorage.getItem('agh_active_tab') === 'core_update') { sessionStorage.removeItem('agh_active_tab'); window.location.hash = 'core_update'; - window.setTimeout(function() { - const tabLink = findCoreUpdateTabLink(rendered); - if (tabLink) tabLink.click(); - }, 50); + activateCoreUpdateTab(rendered, 0); } - // Compact "Current Version" / "Service Status" rows. - const versionRow = map.findElement('data-field', versionOpt.cbid('status_section')); - const statusRow = map.findElement('data-field', statusOpt.cbid('status_section')); - if (versionRow) versionRow.classList.add('agh-status-row'); - if (statusRow) statusRow.classList.add('agh-status-row'); - rendered.appendChild(E('style', {}, STATUS_COMPACT_CSS)); + // ---- Header: Service Status pill (= Enable switch) + Current Version ----------- + // The pill drives the (hidden) "Enable" checkbox of the form, so saving and applying + // work exactly as before. LuCI re-renders the whole form after "Save" and throws away + // everything we added, so decorate() builds the header again after every render. + const enableSecId = sections.length > 0 ? sections[0]['.name'] : null; - const statusNode = map.findElement('data-field', statusOpt.cbid('status_section')); - poll.add(updateStatus(statusNode), POLL_INTERVAL); + function readEnabled() { + const el = enableSecId ? enabledOpt.getUIElement(enableSecId) : null; + if (el) { + return el.getValue() === '1'; + } + return sections.length > 0 && sections[0].enabled === '1'; + } + + // The pill shows the live service state and, when clicked, flips the Enable setting. + let running = Boolean(isRunning); + let enableSwitch = null; + let versionField = null; + let head = null; + + function paintEnableSwitch() { + if (!enableSwitch) { + return; + } + const enabled = readEnabled(); + let cls, text, hint; + + if (running && enabled) { + cls = 'on'; + text = _('Running'); + hint = _('Click to disable'); + } else if (running) { + cls = 'warn'; + text = _('Running') + ' \u00b7 ' + _('Disabled'); + hint = _('Will stop after "Save & Apply". Click to keep it enabled.'); + } else if (enabled) { + cls = 'warn'; + text = _('Not running'); + hint = _('Enabled but not running. Click "Save & Apply" to start it, or click to disable.'); + } else { + cls = 'off'; + text = _('Disabled'); + hint = _('Click to enable'); + } + enableSwitch.className = 'agh-switch ' + cls; + dom.content(enableSwitch, text); + enableSwitch.title = hint + ' - ' + _('Apply with "Save & Apply"'); + } + + // Line the "Service Status" label up with the text of the first tab ("General + // Settings") below. The offset depends on the theme, so measure it once the page + // is laid out (and again when the window is resized). + function alignHead(tries) { + const box = head; + const tab = rendered.querySelector('.cbi-tabmenu li a'); + let left = 0; + if (!box) { + return; + } + if (tab && tab.getBoundingClientRect().width > 0) { + const range = document.createRange(); + range.selectNodeContents(tab); + left = range.getBoundingClientRect().left; + } + if (left > 0 && box.getBoundingClientRect().width > 0) { + box.style.paddingLeft = '0px'; + const pad = left - box.getBoundingClientRect().left; + box.style.paddingLeft = Math.max(0, Math.round(pad)) + 'px'; + } else if (tries < 40) { + window.setTimeout(function() { if (box === head) alignHead(tries + 1); }, 50); + } + } + + function decorate() { + const enableRow = enableSecId ? map.findElement('data-field', enabledOpt.cbid(enableSecId)) : null; + if (enableRow) { + enableRow.style.display = 'none'; + } + + enableSwitch = E('button', { + 'type': 'button', + 'click': function(ev) { + ev.preventDefault(); + const el = enableSecId ? enabledOpt.getUIElement(enableSecId) : null; + if (el) { + el.setValue(readEnabled() ? '0' : '1'); + } + paintEnableSwitch(); + } + }); + paintEnableSwitch(); + + versionField = E('span', {}, + coreExists + ? version + : E('span', { 'style': 'color: var(--error-color-high); font-weight: bold;' }, _('Not installed'))); + + const headRows = [ + E('div', { 'class': 'agh-head-row' }, [ + E('span', { 'class': 'agh-head-item' }, [ E('span', {}, _('Service Status')), enableSwitch ]), + E('span', { 'class': 'agh-head-item' }, [ E('span', {}, _('Current Version')), versionField ]) + ]) + ]; + if (!coreExists) { + headRows.push(E('div', { 'class': 'agh-head-note', 'role': 'alert' }, [ + E('span', { 'class': 'agh-head-note-icon', 'aria-hidden': 'true' }, '\u26a0\ufe0e'), + E('span', {}, _('Core binary not found. Enable the service to trigger an automatic download.')) + ])); + } + head = E('div', { 'class': 'agh-head' }, headRows); + + const title = rendered.querySelector('h2'); + rendered.insertBefore(head, title ? title.nextSibling : rendered.firstChild); + rendered.appendChild(E('style', {}, HEAD_CSS)); + alignHead(0); + } + + decorate(); + window.addEventListener('resize', function() { alignHead(40); }); + + // After "Save" LuCI renders the form again into the same root element: add the + // header back and bring the (also re-created) update controls up to date. + let afterRerender = null; + const origRenderContents = map.renderContents; + map.renderContents = function() { + return Promise.resolve(origRenderContents.apply(this, arguments)).then(function(res) { + if (afterRerender) { + afterRerender(); + } + return res; + }); + }; + + poll.add(async function() { + running = Boolean(await getStatus()); + paintEnableSwitch(); + }, POLL_INTERVAL); let updatePollId = null; let currentState = 'init'; @@ -749,12 +874,7 @@ return view.extend({ // Show (release) or remove (null) the new-version hint after the version number. function setVersionHint(release) { - const field = versionRow - ? (versionRow.querySelector('output') || versionRow.querySelector('.cbi-value-field')) - : null; - if (!field) { - return; - } + const field = versionField; const old = field.querySelector('#agh-version-hint'); if (old) { old.remove(); @@ -1060,6 +1180,15 @@ return view.extend({ // delays it, and stays silent if GitHub cannot be reached. detectNewRelease(detectedChannel); + afterRerender = function() { + decorate(); + if (currentState !== 'updating') { + currentState = 'init'; + detectedChannel = currentChannel(); + detectNewRelease(detectedChannel); + } + }; + // Changing the Core Branch dropdown invalidates what the Action button shows. rendered.addEventListener('change', function() { if (['init', 'updatable', 'latest', 'check_failed'].indexOf(currentState) === -1) { diff --git a/luci-app-adguardhome/patches/03_config_js.patch b/luci-app-adguardhome/patches/03_config_js.patch index b58d586f4..f1cc34f84 100644 --- a/luci-app-adguardhome/patches/03_config_js.patch +++ b/luci-app-adguardhome/patches/03_config_js.patch @@ -8,11 +8,11 @@ Hardens the LuCI page, replaces update_url with update_source, and shows a new-core-version hint with a one-click path to the Core Update tab. The Service Status pill doubles as the Enable switch, next to the version. --- - .../resources/view/adguardhome/config.js | 1207 +++++++++++++++-- - 1 file changed, 1076 insertions(+), 131 deletions(-) + .../resources/view/adguardhome/config.js | 1210 +++++++++++++++-- + 1 file changed, 1079 insertions(+), 131 deletions(-) diff --git a/htdocs/luci-static/resources/view/adguardhome/config.js b/htdocs/luci-static/resources/view/adguardhome/config.js -index 6dc2d45..07aa5cc 100644 +index 6dc2d45..25c03d4 100644 --- a/htdocs/luci-static/resources/view/adguardhome/config.js +++ b/htdocs/luci-static/resources/view/adguardhome/config.js @@ -5,6 +5,7 @@ @@ -23,7 +23,7 @@ index 6dc2d45..07aa5cc 100644 'require view'; const DEFAULT_CONFIG_FILE = '/etc/adguardhome/adguardhome.yaml'; -@@ -18,12 +19,292 @@ const DEFAULT_GOMEMLIMIT = '0'; +@@ -18,12 +19,293 @@ const DEFAULT_GOMEMLIMIT = '0'; const PATH_REGEX = new RegExp('^/etc(/[^/]+)?/?$'); @@ -253,7 +253,8 @@ index 6dc2d45..07aa5cc 100644 + ':root[data-argon-mode="dark"] .agh-head, :root[data-darkmode="true"] .agh-head { --agh-on-fg:#7ee2a0; --agh-on-bg:rgba(46,160,67,.22); --agh-warn-fg:#e3b341; --agh-warn-bg:rgba(187,128,9,.22); --agh-off-fg:#ffa9ae; --agh-off-bg:rgba(248,81,73,.18); }' + + '.agh-head-row { display:flex; align-items:center; flex-wrap:wrap; gap:.4rem 2rem; }' + + '.agh-head-item { display:inline-flex; align-items:center; flex-wrap:wrap; gap:.25rem .75rem; line-height:1.5; }' + -+ '.agh-head-note { line-height:1.5; }' + ++ '.agh-head-note { display:flex; align-items:flex-start; gap:.5rem; line-height:1.5; padding:.45rem .75rem; border-left:4px solid var(--error-color-high, #d9534f); border-radius:4px; background:rgba(217,83,79,.12); color:var(--error-color-high, #d9534f); font-weight:600; }' + ++ '.agh-head-note-icon { flex:none; font-size:1.15em; line-height:1.3; }' + + '.agh-switch { display:inline-flex; align-items:center; gap:.4rem; height:auto; min-height:0; line-height:1.4; padding:.25rem .8rem; margin:0; border-radius:999px; font-weight:600; font-size:.9rem; border:0; box-shadow:none; cursor:pointer; transition:filter .15s; }' + + '.agh-switch:hover { filter:brightness(.95); }' + + '.agh-switch::before { content:""; width:.55rem; height:.55rem; border-radius:50%; background:currentColor; }' + @@ -318,7 +319,7 @@ index 6dc2d45..07aa5cc 100644 function getServiceInfo(name) { const fn = rpc.declare({ -@@ -32,7 +313,9 @@ function getServiceInfo(name) { +@@ -32,7 +314,9 @@ function getServiceInfo(name) { params: ['name'], expect: { [name]: { instances: { [name]: {} }}}, }); @@ -329,7 +330,7 @@ index 6dc2d45..07aa5cc 100644 } const getAGHServiceInfo = getServiceInfo('adguardhome'); -@@ -40,18 +323,14 @@ const getAGHServiceInfo = getServiceInfo('adguardhome'); +@@ -40,18 +324,14 @@ const getAGHServiceInfo = getServiceInfo('adguardhome'); async function getStatus() { try { const res = await getAGHServiceInfo(); @@ -350,7 +351,7 @@ index 6dc2d45..07aa5cc 100644 async function getVersion() { try { const res = await fs.exec('/usr/bin/AdGuardHome', ['--version']); -@@ -61,26 +340,30 @@ async function getVersion() { +@@ -61,26 +341,30 @@ async function getVersion() { return version; } catch (e) { console.error(e); @@ -392,7 +393,7 @@ index 6dc2d45..07aa5cc 100644 } if (value.endsWith('/')) { return _('Path must not end with a slash.'); -@@ -95,167 +378,829 @@ function validateWorkDir(_unused, value) { +@@ -95,167 +379,831 @@ function validateWorkDir(_unused, value) { if (value == null || value === '') { return true; } @@ -906,8 +907,10 @@ index 6dc2d45..07aa5cc 100644 + ]) + ]; + if (!coreExists) { -+ headRows.push(E('div', { 'class': 'agh-head-note', 'style': 'color: var(--error-color-high); font-weight: bold;' }, -+ _('Core binary not found. Enable the service to trigger an automatic download.'))); ++ headRows.push(E('div', { 'class': 'agh-head-note', 'role': 'alert' }, [ ++ E('span', { 'class': 'agh-head-note-icon', 'aria-hidden': 'true' }, '\u26a0\ufe0e'), ++ E('span', {}, _('Core binary not found. Enable the service to trigger an automatic download.')) ++ ])); + } + head = E('div', { 'class': 'agh-head' }, headRows); + diff --git a/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js b/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js index c76bbc080..87c1133f0 100644 --- a/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js +++ b/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/config.js @@ -1349,8 +1349,8 @@ return view.extend({ o = s.option(form.ListValue, 'udp_mode', _("UDP Mode")); o.value('tun', 'TUN'); o.value('tproxy', 'TPROXY'); o.value('off', _("Off")); o = s.option(form.ListValue, 'stack', _("TUN Network Stack")); - o.value('system', 'System'); o.value('gvisor', 'gVisor'); o.value('mixed', 'Mixed'); - o.description = _("TUN stack is used when either TCP or UDP mode is TUN."); + o.value('system', 'System'); o.value('gvisor', 'gVisor'); o.value('mixed', 'Mixed'); o.value('mips', 'Mips'); + o.description = _("TUN stack is used when either TCP or UDP mode is TUN. System: kernel stack. gVisor / Mixed: userspace stack, needs a core built with with_gvisor. Mips: mihomo's own userspace stack (metacubex/mipstack), always available — pick it when gVisor is not in the core."); o = s.option(form.Flag, 'disable_quic_gso', _("Disable QUIC GSO")); o = s.option(form.Flag, 'block_quic', _("Block QUIC")); o.description = _("Reject proxied UDP 443 so some apps fall back to TCP. Try only when downloads or video stall."); @@ -1577,7 +1577,9 @@ return view.extend({ o = s.option(form.DynamicList, 'fake_ip_filter', _("Fake-IP Filter Domains")); o.placeholder = '+.lan / geosite:cn / RULE-SET,cn_domain,real-ip'; - o.description = _("Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as RULE-SET,xxx,real-ip / DOMAIN-SUFFIX,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn becomes RULE-SET,cn_domain,real-ip via built-in cn.mrs acceleration (other geosite:xxx becomes GEOSITE,xxx,real-ip). Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains."); + o.description = _(`Blocklist (default): listed domains get a real IP — +.lan, rule-set:cn_domain, geosite:cn.
+Allowlist: only listed domains get fake-IP.
+Rule mode: every entry needs a fake-ip / real-ip action; Clashoo adds real-ip for you.`); o.depends('enhanced_mode', 'fake-ip'); o.remove = function () {}; o = s.option(form.DynamicList, 'default_nameserver', 'Bootstrap DNS'); @@ -1613,8 +1615,15 @@ return view.extend({ o.value('nameserver', _("Domestic Upstream DNS")); o.value('direct-nameserver', _("Direct Domain Resolver")); o.value('proxy-server-nameserver', _("Proxy Node Domain Resolver")); - o.value('fallback', _("Overseas Encrypted DNS (anti-pollution)")); + /* fake-ip 先返回合成地址,fallback 这一角色会被 mixin 跳过,界面上要说清 */ + var dnsFakeIpMode = (uci.get('clashoo', 'config', 'enhanced_mode') || 'fake-ip') === 'fake-ip'; + o.value('fallback', dnsFakeIpMode + ? _("Overseas Encrypted DNS (no effect in fake-ip mode)") + : _("Overseas Encrypted DNS")); o.default = 'nameserver'; + o.description = dnsFakeIpMode + ? _("Currently fake-ip: this role is skipped, the entry is saved but not used. Switch to redir-host to make it a secondary resolver.") + : _("redir-host mode: used as a secondary encrypted resolver."); o = s.option(form.Value, 'ser_address', _("DNS Address")); o.placeholder = 'https://dns.alidns.com/dns-query / https://doh.pub/dns-query'; o = s.option(form.ListValue, 'protocol', _("Protocol")); diff --git a/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/overview.js b/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/overview.js index 056f719a8..0598a7ce8 100644 --- a/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/overview.js +++ b/luci-app-clashoo/htdocs/luci-static/resources/view/clashoo/overview.js @@ -876,11 +876,23 @@ return view.extend({ _stackLabel: function (stack) { var s = String(stack || '').toLowerCase(); - var map = { gvisor: 'gVisor', system: 'System', mixed: 'Mixed' }; + var map = { gvisor: 'gVisor', system: 'System', mixed: 'Mixed', mips: 'Mips' }; return map[s] || (stack ? String(stack) : 'System'); }, _tpModeRows: function (st) { + /* eBPF 内核自己接管(cgroup + packet_rewrite),不跑 TUN/Redirect,也不建 tun 设备 */ + if (st && st.core_is_ebpf) { + return { + ebpf: true, + rows: [ + { proto: 'TCP', mode: 'eBPF', suffix: _("Mode") }, + { proto: 'UDP', mode: 'eBPF', suffix: _("Mode") }, + { proto: _("Network Stack"), mode: _("Core-managed"), suffix: '' } + ] + }; + } + var tcpRaw = st.tcp_node || st.effective_tcp_mode || st.tcp_mode || uci.get('clashoo', 'config', 'tcp_mode') || 'off'; var udpRaw = st.udp_node || st.effective_udp_mode || st.udp_mode || uci.get('clashoo', 'config', 'udp_mode') || tcpRaw; var stackRaw = st.stack_type || st.stack || uci.get('clashoo', 'config', 'stack') || 'system'; @@ -898,6 +910,8 @@ return view.extend({ _isTpModeDegraded: function (st) { if (!st) return false; + if (st.core_is_ebpf) + return false; if (st.runtime_degraded) return true; @@ -919,6 +933,8 @@ return view.extend({ }); if (this._isTpModeDegraded(st)) rows.push(E('div', { 'class': 'cl-check-updated cl-mode-degraded' }, _("Degraded"))); + if (data.ebpf) + rows.push(E('div', { 'class': 'cl-check-updated cl-mode-degraded' }, _("eBPF core captures traffic itself; turn off Clashoo forwarding rules"))); return E('div', { id: 'cl-tpmode', 'class': 'cl-mode-stack' }, rows); }, @@ -1565,10 +1581,14 @@ return view.extend({ var enhancedMode = String(st.enhanced_mode || 'fake-ip').toLowerCase(); var tpMode = 'custom'; - if (tcpMode === 'tun' && udpMode === 'tun' && stackMode === 'mixed') + if (st.core_is_ebpf) + tpMode = 'ebpf'; + else if (tcpMode === 'tun' && udpMode === 'tun' && stackMode === 'mixed') tpMode = 'tun-mixed'; else if (tcpMode === 'tun' && udpMode === 'tun' && stackMode === 'gvisor') tpMode = 'tun-gvisor'; + else if (tcpMode === 'tun' && udpMode === 'tun' && stackMode === 'mips') + tpMode = 'tun-mips'; else if (tcpMode === 'tun' && udpMode === 'tun') tpMode = 'tun-system'; else if (tcpMode === 'redirect' && udpMode === 'tproxy') @@ -1583,7 +1603,9 @@ return view.extend({ return E('option', { value: o[0], selected: o[0] === val ? '' : null, disabled: o[2] ? '' : null }, o[1]); })); }; - var tpModeOptions = [['fake-ip','Fake-IP'],['redir-host','Redir-Host'],['tun-system','System'],['tun-gvisor','gVisor'],['tun-mixed','Mixed']]; + var tpModeOptions = [['fake-ip','Fake-IP'],['redir-host','Redir-Host'],['tun-system','System'],['tun-gvisor','gVisor'],['tun-mixed','Mixed'],['tun-mips','Mips']]; + if (tpMode === 'ebpf') + tpModeOptions.unshift(['ebpf', 'eBPF (' + _("core-managed") + ')', true]); if (tpMode === 'custom') tpModeOptions.unshift(['custom',_("Custom")]); @@ -1649,6 +1671,10 @@ return view.extend({ self._lastSt.tcp_mode = 'tun'; self._lastSt.udp_mode = 'tun'; self._lastSt.stack = 'gvisor'; + } else if (mode === 'tun-mips') { + self._lastSt.tcp_mode = 'tun'; + self._lastSt.udp_mode = 'tun'; + self._lastSt.stack = 'mips'; } else if (mode === 'tun-system') { self._lastSt.tcp_mode = 'tun'; self._lastSt.udp_mode = 'tun'; diff --git a/luci-app-clashoo/po/zh-cn/clashoo.po b/luci-app-clashoo/po/zh-cn/clashoo.po index e3717393c..44ed21677 100644 --- a/luci-app-clashoo/po/zh-cn/clashoo.po +++ b/luci-app-clashoo/po/zh-cn/clashoo.po @@ -213,8 +213,8 @@ msgstr "黑名单" msgid "Blocklist (listed items use real IP, default)" msgstr "黑名单(列入走真 IP,默认)" -msgid "Blocklist mode: enter domains or shorthand like geosite:cn. Rule mode: entries are converted to rule syntax automatically, such as RULE-SET,xxx,real-ip / DOMAIN-SUFFIX,xxx,real-ip; usually add MATCH,fake-ip at the end as fallback. geosite:cn becomes RULE-SET,cn_domain,real-ip via built-in cn.mrs acceleration (other geosite:xxx becomes GEOSITE,xxx,real-ip). Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains." -msgstr "黑名单模式:填域名或 geosite:cn 简写。规则模式:条目会自动转成 RULE-SET,xxx,real-ip / DOMAIN-SUFFIX,xxx,real-ip 这种与 rules 同语法的写法,末尾通常加一条 MATCH,fake-ip 兜底。geosite:cn 会转成 RULE-SET,cn_domain,real-ip,走内置 cn.mrs 加速(其它 geosite:xxx 则转成 GEOSITE,xxx,real-ip)。白名单模式下随包自带的默认项含义相反,会被跳过,请自行填写需要 Fake-IP 的域名。" +msgid "Blocklist (default): listed domains get a real IP — +.lan, rule-set:cn_domain, geosite:cn.
\nAllowlist: only listed domains get fake-IP.
\nRule mode: every entry needs a fake-ip / real-ip action; Clashoo adds real-ip for you." +msgstr "黑名单(默认):列出的域名返回真实 IP,可写 +.lan、rule-set:cn_domain、geosite:cn。
白名单:只有列出的域名走 Fake-IP。
规则模式:每条必须带 fake-ip / real-ip 动作,Clashoo 会自动补 real-ip。" msgid "Bypass China IPv4" msgstr "大陆 IPv4 绕过" @@ -261,6 +261,9 @@ msgstr "清理失败(mihomo 可能未运行)" msgid "Cleanup interval (hours)" msgstr "清理间隔(小时)" +msgid "Currently fake-ip: this role is skipped, the entry is saved but not used. Switch to redir-host to make it a secondary resolver." +msgstr "当前是 fake-ip:这一项会被跳过,配置会保存但不生效。改成 redir-host 后才会作为备用解析器。" + msgid "Clear" msgstr "清理" @@ -273,6 +276,9 @@ msgstr "清理 Smart 缓存" msgid "Clear the current log file? This cannot be undone." msgstr "确定要清空当前日志文件吗?此操作不可恢复。" +msgid "redir-host mode: used as a secondary encrypted resolver." +msgstr "redir-host 模式:作为备用加密解析器。" + msgid "Clear View" msgstr "清空显示" @@ -927,8 +933,11 @@ msgstr "输出文件名(不含扩展名,留空自动填写)" msgid "Overseas" msgstr "境外" -msgid "Overseas Encrypted DNS (anti-pollution)" -msgstr "国外加密 DNS(防污染)" +msgid "Overseas Encrypted DNS" +msgstr "国外加密 DNS" + +msgid "Overseas Encrypted DNS (no effect in fake-ip mode)" +msgstr "国外加密 DNS(fake-ip 下不生效)" msgid "Overview" msgstr "概览" @@ -1616,3 +1625,15 @@ msgstr "仅在本机转换" msgid "If local conversion fails, the selected external service receives the complete subscription URL, including its access token. Leave Local conversion only selected to keep the URL on this router." msgstr "如果本机转换失败,所选第三方服务会收到完整订阅链接,包括访问令牌。保持选择「仅在本机转换」可避免向第三方发送链接。" + +msgid "TUN stack is used when either TCP or UDP mode is TUN. System: kernel stack. gVisor / Mixed: userspace stack, needs a core built with with_gvisor. Mips: mihomo's own userspace stack (metacubex/mipstack), always available — pick it when gVisor is not in the core." +msgstr "TCP 或 UDP 模式为 TUN 时生效。System:系统协议栈。gVisor / Mixed:用户态栈,内核需要带 with_gvisor 编译。Mips:mihomo 自带用户态栈(metacubex/mipstack),始终可用,内核没有 gVisor 时选它。" + +msgid "core-managed" +msgstr "内核接管" + +msgid "Core-managed" +msgstr "内核接管" + +msgid "eBPF core captures traffic itself; turn off Clashoo forwarding rules" +msgstr "eBPF 内核自行接管流量,请关闭 Clashoo 的转发规则" diff --git a/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo b/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo index 890504123..e5e158f63 100755 --- a/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo +++ b/luci-app-clashoo/root/usr/share/rpcd/ucode/luci.clashoo @@ -361,6 +361,13 @@ p.close(); return v; } +/* eBPF 入站只存在于第三方 mihomo 分支(如 alpha-smart-xxx-ebpf), + * 官方 mihomo / smart / sing-box 都没有,这里只做识别。 */ +function core_is_ebpf_build(binary) { + let v = get_core_version(binary); + return !!match(v, /ebpf/i); +} + function sync_legacy_core_fields(c) { let family = c.get('clashoo', 'config', 'core_type') || 'mihomo'; let dcore = c.get('clashoo', 'config', 'dcore') || '2'; @@ -2169,6 +2176,7 @@ return { running, pid, dash_port, dash_pass, proxy_mode, conf_path: conf_name, c runtime_degraded, runtime_degrade_reason, health_status, health_detail, has_tun_device, core_type: configured_core, dcore, local_ip, + core_is_ebpf: core_is_ebpf_build(find_binary()), has_smart: !!find_smart_binary() }; } catch (e) { return { success: false, error: 'status_failed', message: '' + e }; @@ -2259,14 +2267,14 @@ set_mode: { args: { mode: 'mode' }, call: function(req) { let mode = req.args?.mode || 'fake-ip'; - if (mode != 'fake-ip' && mode != 'redir-host' && mode != 'tun-system' && mode != 'tun-gvisor' && mode != 'tun-mixed') + if (mode != 'fake-ip' && mode != 'redir-host' && mode != 'tun-system' && mode != 'tun-gvisor' && mode != 'tun-mixed' && mode != 'tun-mips') return { error: 'invalid mode' }; let c = cursor(); c.load('clashoo'); - if (mode == 'tun-system' || mode == 'tun-gvisor' || mode == 'tun-mixed') { + if (mode == 'tun-system' || mode == 'tun-gvisor' || mode == 'tun-mixed' || mode == 'tun-mips') { c.set('clashoo', 'config', 'tun_mode', '1'); c.set('clashoo', 'config', 'tcp_mode', 'tun'); c.set('clashoo', 'config', 'udp_mode', 'tun'); - c.set('clashoo', 'config', 'stack', mode == 'tun-mixed' ? 'mixed' : (mode == 'tun-gvisor' ? 'gvisor' : 'system')); + c.set('clashoo', 'config', 'stack', mode == 'tun-mixed' ? 'mixed' : (mode == 'tun-gvisor' ? 'gvisor' : (mode == 'tun-mips' ? 'mips' : 'system'))); c.set('clashoo', 'config', 'enable_udp', '1'); c.set('clashoo', 'config', 'enhanced_mode', 'fake-ip'); } else { diff --git a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js index 1e98855b8..3da25fb2d 100644 --- a/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js +++ b/luci-app-mptcp/htdocs/luci-static/resources/view/mptcp/mptcp.js @@ -63,6 +63,10 @@ return L.view.extend({ return kernelMajor > major || (kernelMajor == major && kernelMinor >= minor); } + // x86_64 routers aggregate up to 16 WANs, the others 8: the most the + // mptcp init passes to "ip mptcp limits set" + var mptcpLimitMax = (boardinfo.release && boardinfo.release.target == 'x86/64') ? 16 : 8; + function normalizeSchedulerValue(value) { if (value == null) return value; @@ -320,10 +324,10 @@ return L.view.extend({ o.depends("mptcp_pm_type","1"); o.retain = true; - // The kernel takes 8 at most for both limits, and one value above - // it made "ip mptcp limits set" fail for both + // One value above the kernel's maximum made "ip mptcp limits set" + // fail for both limits o = s.option(form.Value, "mptcp_subflows", _("Max subflows"),_("specifies the maximum number of additional subflows allowed for each MPTCP connection")); - o.datatype = "range(0,8)"; + o.datatype = "range(0," + mptcpLimitMax + ")"; o.rmempty = false; o.default = 3; @@ -333,7 +337,7 @@ return L.view.extend({ o.default = 4; o = s.option(form.Value, "mptcp_add_addr_accepted", _("Max add address"),_("specifies the maximum number of ADD_ADDR (add address) suboptions accepted for each MPTCP connection")); - o.datatype = "range(0,8)"; + o.datatype = "range(0," + mptcpLimitMax + ")"; o.rmempty = false; o.default = 1; diff --git a/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp b/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp index 6b9a944c1..d7110f4a3 100755 --- a/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp +++ b/luci-app-mptcp/root/usr/libexec/rpcd/luci.mptcp @@ -595,13 +595,20 @@ end -- What set_settings / set_interface_settings accept for each field. They -- wrote any string: the init script builds a file path from mptcp_scheduler -- ("../" in it registered an arbitrary .o as the scheduler), passes the --- limits to "ip mptcp limits set" (a value above 8 made it fail for both) --- and the rest to sysctl. +-- limits to "ip mptcp limits set" (a value above the kernel's maximum made +-- it fail for both) and the rest to sysctl. local function valid_uint(v, max) local n = tonumber(v) return v:match("^%d+$") ~= nil and n ~= nil and (max == nil or n <= max) end +-- x86_64 routers aggregate up to 16 WANs, the others 8: the most the mptcp +-- init passes to "ip mptcp limits set" +local function valid_mptcp_limit(v) + local max = exec_output("uname -m 2>/dev/null") == "x86_64" and 16 or 8 + return valid_uint(v, max) +end + -- 1 to 256, as "ip route" takes it and the forms ask: the BPF schedulers -- read 0 as no weight (neutral 100) and add the weight to a signed counter local function valid_weight(v) @@ -671,9 +678,9 @@ local setting_checks = { mptcp_disable_initial_config = function(v) return v == "0" or v == "1" end, mptcp_force_multipath = function(v) return v == "0" or v == "1" end, mptcpd_enable = function(v) return v == "enable" or v == "disable" end, - mptcp_subflows = function(v) return valid_uint(v, 8) end, + mptcp_subflows = valid_mptcp_limit, mptcp_stale_loss_cnt = valid_uint, - mptcp_add_addr_accepted = function(v) return valid_uint(v, 8) end, + mptcp_add_addr_accepted = valid_mptcp_limit, mptcp_add_addr_timeout = valid_uint, mptcp_blackhole_timeout = valid_uint, mptcp_close_timeout = valid_uint, diff --git a/luci-app-open-box/Makefile b/luci-app-open-box/Makefile index 146c80e71..523c28df9 100644 --- a/luci-app-open-box/Makefile +++ b/luci-app-open-box/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-open-box -PKG_VERSION:=0.1.397 -PKG_RELEASE:=62 +PKG_VERSION:=0.1.400 +PKG_RELEASE:=63 PKG_MAINTAINER:=liandu2024 PKG_LICENSE:=GPL-3.0-or-later diff --git a/luci-theme-aurora/.dev/src/media/login.css b/luci-theme-aurora/.dev/src/media/login.css index df9148199..9765c9c00 100644 --- a/luci-theme-aurora/.dev/src/media/login.css +++ b/luci-theme-aurora/.dev/src/media/login.css @@ -159,6 +159,10 @@ body[data-bg] .login-card { .login-error { @apply bg-danger-surface text-danger border-hairline rounded-2xl border px-3 py-2 text-center text-sm; + + &.warning { + @apply bg-warning-surface text-warning; + } } .login-actions { diff --git a/luci-theme-aurora/.dev/tests/build-performance.test.js b/luci-theme-aurora/.dev/tests/build-performance.test.js index b254a50e7..210ea62e2 100644 --- a/luci-theme-aurora/.dev/tests/build-performance.test.js +++ b/luci-theme-aurora/.dev/tests/build-performance.test.js @@ -19,7 +19,8 @@ test("production assets stay within raw-transfer budgets", () => { // 187K: pseudo-elements are nested rules, one content declaration each. assert.ok(main <= 187_000, "main.css exceeds 187 KB"); - assert.ok(login <= 12_000, "login.css exceeds 12 KB"); + // 12.5K: auth_plugin warning tokens added ~0.35 KB. + assert.ok(login <= 12_500, "login.css exceeds 12.5 KB"); // 22K: palette recents (record on pick, pure-LRU browse order, storage // validation) added ~0.8 KB and the ">" logout command ~0.2 KB. The total // budget below moved by the same amount. diff --git a/luci-theme-aurora/Makefile b/luci-theme-aurora/Makefile index 68f42bfef..92854e9a6 100644 --- a/luci-theme-aurora/Makefile +++ b/luci-theme-aurora/Makefile @@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS) LUCI_DEPENDS:=+luci-base -PKG_VERSION:=1.4.2 -PKG_RELEASE:=86 +PKG_VERSION:=1.4.3 +PKG_RELEASE:=87 PKG_LICENSE:=Apache-2.0 LUCI_MINIFY_CSS:= diff --git a/luci-theme-aurora/htdocs/luci-static/aurora/login.css b/luci-theme-aurora/htdocs/luci-static/aurora/login.css index b8a2a3b3d..e5ade23c7 100644 --- a/luci-theme-aurora/htdocs/luci-static/aurora/login.css +++ b/luci-theme-aurora/htdocs/luci-static/aurora/login.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-content:"";--tw-shadow:0 0 #0000;--tw-inset-shadow:0 0 #0000;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-shadow:0 0 #0000;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-2xl:calc(var(--radius-base) * 2);--animate-spin:spin 1s linear infinite}}@layer base{*,:before,:after{box-sizing:border-box}body,hr,p,button,input{margin:0}button,input{font:inherit}a{text-decoration-line:none}[hidden]{display:none!important}}@layer components,utilities;:root{--bg:#f7fafc;--surface:#fff;--text:#121a22;--brand:#0085b7;--on-brand:#fff;--danger:#8d1925;--text-muted:#5f666d;--text-subtle:#7f858b;--hairline:#121a2221;--brand-subtle:#ddecf5;--danger-surface:#ffe8e6;--control-bg:#fff;--app-shadow-md:0 4px 16px #00000014, 0 1px 3px #0000000a;--font-sans:"Lato", ui-sans-serif, system-ui, sans-serif;--spacing:.25rem;--radius-base:.5rem}@supports (color:lab(0% 0 0)){:root{--bg:lab(98.1553% -.760227 -1.25223);--surface:lab(100% 0 0);--text:lab(8.89727% -1.68239 -6.93074);--brand:lab(51.3503% -19.237 -40.2116);--on-brand:lab(100% 0 0);--danger:lab(31.1495% 47.9256 25.8661);--text-muted:lab(42.8254% -1.57365 -4.8763);--text-subtle:lab(55.3248% -1.4126 -4.053);--hairline:lab(8.89727% -1.68239 -6.93074/.13);--brand-subtle:lab(92.5794% -3.9458 -6.17858);--danger-surface:lab(93.849% 11.2841 5.29771);--control-bg:lab(100.011% -.760317 -1.25225);--app-shadow-md:0 4px 16px lab(0% 0 0/.08), 0 1px 3px lab(0% 0 0/.04)}}[data-darkmode=true]{--bg:#05070e;--surface:#141822;--text:#f9fafb;--brand:#00958e;--on-brand:#fff;--danger:#f17070;--text-muted:#909297;--text-subtle:#5d6067;--hairline:#f9fafb1a;--brand-subtle:#061a20;--danger-surface:#541f20;--control-bg:#0a0e17;--app-shadow-md:0 10px 28px #0000006b}@supports (color:lab(0% 0 0)){[data-darkmode=true]{--bg:lab(1.93894% .0855997 -3.22397);--surface:lab(8.24713% -.0405461 -7.31381);--text:lab(98.252% -.0618994 -.756907);--brand:lab(55.0978% -44.5567 -7.62978);--on-brand:lab(100% 0 0);--danger:lab(63.5705% 50.8105 25.3639);--text-muted:lab(60.5379% -.225931 -3.05105);--text-subtle:lab(40.6865% -.278354 -4.23647);--hairline:lab(98.252% -.0618994 -.756907/.1);--brand-subtle:lab(7.95734% -6.34954 -6.72844);--danger-surface:lab(20.3014% 25.4342 12.827);--control-bg:lab(3.9802% .0812709 -5.64623);--app-shadow-md:0 10px 28px lab(0% 0 0/.42)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-property:none!important;animation:none!important}.spinning:before{animation:var(--animate-spin)!important}}html{background-color:var(--bg);min-height:100dvh;font-family:var(--font-sans);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}html[data-darkmode=true]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}html body{background-color:var(--bg);min-height:100dvh}.page-bg{pointer-events:none;background-image:var(--page-bg-lqip,var(--page-bg));background-position:50%;background-repeat:no-repeat;background-size:cover;position:fixed;inset:0;transform:translateZ(0)}.page-bg:after{pointer-events:none;background-image:var(--page-bg);opacity:0;--tw-content:"";content:var(--tw-content);background-position:50%;background-repeat:no-repeat;background-size:cover;transition:opacity .5s;position:absolute;inset:0}.page-bg.full-loaded:after{opacity:1}@keyframes divider-in{0%{opacity:0;transform:scaleX(.15)}}.login-screen{min-height:100dvh;padding-inline:calc(var(--spacing) * 5);padding-bottom:calc(var(--spacing) * 14);flex-direction:column;justify-content:center;align-items:center;display:flex;position:relative}.login-shell{width:100%;max-width:var(--container-md)}.page-bg{--page-bg:var(--login-bg);--page-bg-lqip:var(--login-bg-lqip)}body[data-bg] .page-bg:before{pointer-events:none;z-index:1;--tw-content:"";content:var(--tw-content);background:var(--bg);position:absolute;inset:0}@supports (color:color-mix(in lab, red, red)){body[data-bg] .page-bg:before{background:color-mix(in srgb,var(--bg) var(--login-bg-scrim,0%),transparent)}}body[data-bg] .login-card{-webkit-backdrop-filter:blur(var(--login-bg-blur,0px)) saturate(150%);backdrop-filter:blur(var(--login-bg-blur,0px)) saturate(150%);background:var(--surface)}@supports (color:color-mix(in lab, red, red)){body[data-bg] .login-card{background:color-mix(in srgb,var(--surface) var(--login-bg-alpha,100%),transparent)}}@media (prefers-reduced-transparency:reduce){body[data-bg] .login-card{--login-bg-alpha:100%;-webkit-backdrop-filter:none!important;backdrop-filter:none!important}}.login-card{gap:calc(var(--spacing) * 7);border-radius:calc(var(--radius-base) * 4);background-color:var(--surface);padding:calc(var(--spacing) * 14);--tw-shadow:var(--app-shadow-md);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);flex-direction:column;display:flex}@media not all and (width>=48rem){.login-card{border-radius:calc(var(--radius-base) * 3);padding:calc(var(--spacing) * 9)}}.login-brand{align-items:center;gap:calc(var(--spacing) * 3.5);display:flex}.login-divider{border-style:var(--tw-border-style);--tw-gradient-position:to right;border-width:0;width:100%;height:1px;animation:.7s cubic-bezier(.16,1,.3,1) .1s both divider-in}@supports (background-image:linear-gradient(in lab, red, red)){.login-divider{--tw-gradient-position:to right in oklab}}.login-divider{background-image:linear-gradient(var(--tw-gradient-stops));--tw-gradient-from:transparent;--tw-gradient-via:var(--hairline);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));--tw-gradient-to:transparent}.login-logo{height:calc(var(--spacing) * 11);width:calc(var(--spacing) * 11);border-radius:calc(var(--radius-base) * 2);background-color:var(--brand-subtle);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--brand-subtle);flex-shrink:0;justify-content:center;align-items:center;display:flex}.login-logo img{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6)}.login-title{gap:var(--spacing);flex-direction:column;display:flex}.login-hostname{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight);color:var(--text)}.login-subtitle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--text)}.login-form{gap:calc(var(--spacing) * 5);flex-direction:column;display:flex}.login-fields{gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}.login-field{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.login-label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-widest);color:var(--text);text-transform:uppercase}.input-wrap{border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline);background-color:var(--control-bg);contain:layout style;position:relative}.input-wrap:after{pointer-events:none;border-radius:calc(var(--radius-2xl) + 1px);border-style:var(--tw-border-style);border-width:2px;border-color:var(--brand);opacity:0;--tw-content:"";content:var(--tw-content);transition:opacity .15s;position:absolute;inset:-1px}.input-wrap:focus-within:after{opacity:1}.login-input{appearance:none;border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));color:var(--text);background-color:#0000;border-width:0;outline-style:none}.login-input::placeholder{color:var(--text-subtle)}.login-error{border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline);background-color:var(--danger-surface);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--danger)}.login-actions{padding-top:calc(var(--spacing) * 4)}.login-submit{cursor:pointer;border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);background-color:var(--brand);width:100%;padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));font-weight:var(--font-weight-medium);color:var(--on-brand);border-width:0}@media (hover:hover){.login-submit:hover{opacity:.9}}.login-submit:active{opacity:.8}.login-footer{right:0;bottom:calc(var(--spacing) * 5);padding-inline:calc(var(--spacing) * 5);text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--text-muted);position:absolute;left:0}.login-footer p{white-space:normal}.login-footer a{color:var(--brand)}@media (hover:hover){.login-footer a:hover{text-decoration-line:underline}}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-content:"";--tw-shadow:0 0 #0000;--tw-inset-shadow:0 0 #0000;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-shadow:0 0 #0000;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-2xl:calc(var(--radius-base) * 2);--animate-spin:spin 1s linear infinite}}@layer base{*,:before,:after{box-sizing:border-box}body,hr,p,button,input{margin:0}button,input{font:inherit}a{text-decoration-line:none}[hidden]{display:none!important}}@layer components,utilities;:root{--bg:#f7fafc;--surface:#fff;--text:#121a22;--brand:#0085b7;--on-brand:#fff;--warning:#643e00;--danger:#8d1925;--text-muted:#5f666d;--text-subtle:#7f858b;--hairline:#121a2221;--brand-subtle:#ddecf5;--warning-surface:#ffedd6;--danger-surface:#ffe8e6;--control-bg:#fff;--app-shadow-md:0 4px 16px #00000014, 0 1px 3px #0000000a;--font-sans:"Lato", ui-sans-serif, system-ui, sans-serif;--spacing:.25rem;--radius-base:.5rem}@supports (color:lab(0% 0 0)){:root{--bg:lab(98.1553% -.760227 -1.25223);--surface:lab(100% 0 0);--text:lab(8.89727% -1.68239 -6.93074);--brand:lab(51.3503% -19.237 -40.2116);--on-brand:lab(100% 0 0);--warning:lab(30.2236% 14.0359 42.8654);--danger:lab(31.1495% 47.9256 25.8661);--text-muted:lab(42.8254% -1.57365 -4.8763);--text-subtle:lab(55.3248% -1.4126 -4.053);--hairline:lab(8.89727% -1.68239 -6.93074/.13);--brand-subtle:lab(92.5794% -3.9458 -6.17858);--warning-surface:lab(94.8007% 4.75499 15.6111);--danger-surface:lab(93.849% 11.2841 5.29771);--control-bg:lab(100.011% -.760317 -1.25225);--app-shadow-md:0 4px 16px lab(0% 0 0/.08), 0 1px 3px lab(0% 0 0/.04)}}[data-darkmode=true]{--bg:#05070e;--surface:#141822;--text:#f9fafb;--brand:#00958e;--on-brand:#fff;--warning:#f0ba59;--danger:#f17070;--text-muted:#909297;--text-subtle:#5d6067;--hairline:#f9fafb1a;--brand-subtle:#061a20;--warning-surface:#45310b;--danger-surface:#541f20;--control-bg:#0a0e17;--app-shadow-md:0 10px 28px #0000006b}@supports (color:lab(0% 0 0)){[data-darkmode=true]{--bg:lab(1.93894% .0855997 -3.22397);--surface:lab(8.24713% -.0405461 -7.31381);--text:lab(98.252% -.0618994 -.756907);--brand:lab(55.0978% -44.5567 -7.62978);--on-brand:lab(100% 0 0);--warning:lab(79.1996% 12.6808 55.9505);--danger:lab(63.5705% 50.8105 25.3639);--text-muted:lab(60.5379% -.225931 -3.05105);--text-subtle:lab(40.6865% -.278354 -4.23647);--hairline:lab(98.252% -.0618994 -.756907/.1);--brand-subtle:lab(7.95734% -6.34954 -6.72844);--warning-surface:lab(22.2973% 6.18184 26.7927);--danger-surface:lab(20.3014% 25.4342 12.827);--control-bg:lab(3.9802% .0812709 -5.64623);--app-shadow-md:0 10px 28px lab(0% 0 0/.42)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-property:none!important;animation:none!important}.spinning:before{animation:var(--animate-spin)!important}}html{background-color:var(--bg);min-height:100dvh;font-family:var(--font-sans);--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}html[data-darkmode=true]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}html body{background-color:var(--bg);min-height:100dvh}.page-bg{pointer-events:none;background-image:var(--page-bg-lqip,var(--page-bg));background-position:50%;background-repeat:no-repeat;background-size:cover;position:fixed;inset:0;transform:translateZ(0)}.page-bg:after{pointer-events:none;background-image:var(--page-bg);opacity:0;--tw-content:"";content:var(--tw-content);background-position:50%;background-repeat:no-repeat;background-size:cover;transition:opacity .5s;position:absolute;inset:0}.page-bg.full-loaded:after{opacity:1}@keyframes divider-in{0%{opacity:0;transform:scaleX(.15)}}.login-screen{min-height:100dvh;padding-inline:calc(var(--spacing) * 5);padding-bottom:calc(var(--spacing) * 14);flex-direction:column;justify-content:center;align-items:center;display:flex;position:relative}.login-shell{width:100%;max-width:var(--container-md)}.page-bg{--page-bg:var(--login-bg);--page-bg-lqip:var(--login-bg-lqip)}body[data-bg] .page-bg:before{pointer-events:none;z-index:1;--tw-content:"";content:var(--tw-content);background:var(--bg);position:absolute;inset:0}@supports (color:color-mix(in lab, red, red)){body[data-bg] .page-bg:before{background:color-mix(in srgb,var(--bg) var(--login-bg-scrim,0%),transparent)}}body[data-bg] .login-card{-webkit-backdrop-filter:blur(var(--login-bg-blur,0px)) saturate(150%);backdrop-filter:blur(var(--login-bg-blur,0px)) saturate(150%);background:var(--surface)}@supports (color:color-mix(in lab, red, red)){body[data-bg] .login-card{background:color-mix(in srgb,var(--surface) var(--login-bg-alpha,100%),transparent)}}@media (prefers-reduced-transparency:reduce){body[data-bg] .login-card{--login-bg-alpha:100%;-webkit-backdrop-filter:none!important;backdrop-filter:none!important}}.login-card{gap:calc(var(--spacing) * 7);border-radius:calc(var(--radius-base) * 4);background-color:var(--surface);padding:calc(var(--spacing) * 14);--tw-shadow:var(--app-shadow-md);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);flex-direction:column;display:flex}@media not all and (width>=48rem){.login-card{border-radius:calc(var(--radius-base) * 3);padding:calc(var(--spacing) * 9)}}.login-brand{align-items:center;gap:calc(var(--spacing) * 3.5);display:flex}.login-divider{border-style:var(--tw-border-style);--tw-gradient-position:to right;border-width:0;width:100%;height:1px;animation:.7s cubic-bezier(.16,1,.3,1) .1s both divider-in}@supports (background-image:linear-gradient(in lab, red, red)){.login-divider{--tw-gradient-position:to right in oklab}}.login-divider{background-image:linear-gradient(var(--tw-gradient-stops));--tw-gradient-from:transparent;--tw-gradient-via:var(--hairline);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));--tw-gradient-to:transparent}.login-logo{height:calc(var(--spacing) * 11);width:calc(var(--spacing) * 11);border-radius:calc(var(--radius-base) * 2);background-color:var(--brand-subtle);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--brand-subtle);flex-shrink:0;justify-content:center;align-items:center;display:flex}.login-logo img{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6)}.login-title{gap:var(--spacing);flex-direction:column;display:flex}.login-hostname{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight);color:var(--text)}.login-subtitle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--text)}.login-form{gap:calc(var(--spacing) * 5);flex-direction:column;display:flex}.login-fields{gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}.login-field{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.login-label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-widest);color:var(--text);text-transform:uppercase}.input-wrap{border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline);background-color:var(--control-bg);contain:layout style;position:relative}.input-wrap:after{pointer-events:none;border-radius:calc(var(--radius-2xl) + 1px);border-style:var(--tw-border-style);border-width:2px;border-color:var(--brand);opacity:0;--tw-content:"";content:var(--tw-content);transition:opacity .15s;position:absolute;inset:-1px}.input-wrap:focus-within:after{opacity:1}.login-input{appearance:none;border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));color:var(--text);background-color:#0000;border-width:0;outline-style:none}.login-input::placeholder{color:var(--text-subtle)}.login-error{border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline);background-color:var(--danger-surface);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--danger)}.login-error.warning{background-color:var(--warning-surface);color:var(--warning)}.login-actions{padding-top:calc(var(--spacing) * 4)}.login-submit{cursor:pointer;border-radius:calc(var(--radius-base) * 2);border-style:var(--tw-border-style);background-color:var(--brand);width:100%;padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));font-weight:var(--font-weight-medium);color:var(--on-brand);border-width:0}@media (hover:hover){.login-submit:hover{opacity:.9}}.login-submit:active{opacity:.8}.login-footer{right:0;bottom:calc(var(--spacing) * 5);padding-inline:calc(var(--spacing) * 5);text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--text-muted);position:absolute;left:0}.login-footer p{white-space:normal}.login-footer a{color:var(--brand)}@media (hover:hover){.login-footer a:hover{text-decoration-line:underline}}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@keyframes spin{to{transform:rotate(360deg)}} \ No newline at end of file diff --git a/luci-theme-aurora/ucode/template/themes/aurora/sysauth.ut b/luci-theme-aurora/ucode/template/themes/aurora/sysauth.ut index 121e4add7..a9ce7af34 100644 --- a/luci-theme-aurora/ucode/template/themes/aurora/sysauth.ut +++ b/luci-theme-aurora/ucode/template/themes/aurora/sysauth.ut @@ -54,12 +54,52 @@ const loginIconVer = themeTokens.icon_cache_version || '0'; + + {% if (auth_fields): %} + {% for (let field in auth_fields): %} + + {% endfor %} + {% endif %} + + {% if (auth_html): %} + + {% endif %} + + {% if (auth_assets): %} + {% for (let asset in auth_assets): %} + {% if (asset.type == 'script'): %} + + {% endif %} + {% endfor %} + {% endif %} - {% if (fuser): %} - + {% if (auth_message || fuser): %} + {% endif %} + + diff --git a/luci-theme-shadcn/.dev/src/media/login.css b/luci-theme-shadcn/.dev/src/media/login.css index 677b74e15..5241e7454 100644 --- a/luci-theme-shadcn/.dev/src/media/login.css +++ b/luci-theme-shadcn/.dev/src/media/login.css @@ -140,8 +140,13 @@ html { /* ── Form elements ── */ -.login-error { +.login-error, +.alert-message { @apply border-destructive-a30 bg-destructive-a8 text-destructive mb-4 rounded-2xl border px-4 py-3 text-sm; + + &.warning { + @apply border-warning-a30 bg-warning-a10 text-warning-soft-fg; + } } .login-field { @@ -162,7 +167,11 @@ html { } input { - @apply text-foreground placeholder:text-muted-foreground w-full bg-transparent py-3.5 pr-10 pl-9 text-sm focus:outline-none; + @apply text-foreground placeholder:text-muted-foreground w-full bg-transparent px-4 py-3.5 text-sm focus:outline-none; + } + + .login-input-icon ~ input { + @apply pl-9; } } diff --git a/luci-theme-shadcn/Makefile b/luci-theme-shadcn/Makefile index e19292a49..a873f32f8 100644 --- a/luci-theme-shadcn/Makefile +++ b/luci-theme-shadcn/Makefile @@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk LUCI_TITLE:=A modern sidebar LuCI theme for OpenWrt, built with shadcn/ui design language. LUCI_DEPENDS:=+luci-base -PKG_VERSION:=0.6.2 -PKG_RELEASE:=10 +PKG_VERSION:=0.6.3 +PKG_RELEASE:=11 PKG_LICENSE:=Apache-2.0 LUCI_MINIFY_CSS:= diff --git a/luci-theme-shadcn/htdocs/luci-static/shadcn/login.css b/luci-theme-shadcn/htdocs/luci-static/shadcn/login.css index 400732418..061633444 100644 --- a/luci-theme-shadcn/htdocs/luci-static/shadcn/login.css +++ b/luci-theme-shadcn/htdocs/luci-static/shadcn/login.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--radius-2xl:1rem;--radius-3xl:1.5rem;}}@layer base{*,:before,:after{box-sizing:border-box}body,p{margin:calc(var(--spacing) * 0)}button,input{margin:calc(var(--spacing) * 0);border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);color:inherit;font:inherit;background-color:#0000;border-width:0}::placeholder{opacity:1}[hidden]{display:none!important}}@layer components,utilities;@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-wght-normal.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+1F??}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-wght-normal.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-vietnamese-wght-normal.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--background:oklch(98.5% .004 275);--foreground:oklch(18% .02 275);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(55% .2 25);--border:oklch(89.65% .0058 275);--muted-foreground:oklch(52.62% .0131 275);--primary-hover:oklch(62.7% .158 275);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(55% .2 25/.08);--destructive-a30:oklch(55% .2 25/.3);--foreground-a5:oklch(18% .02 275/.05);--font-sans:"Inter Variable", ui-sans-serif, system-ui, sans-serif;}[data-darkmode=true]{--background:oklch(15.9% .005 264);--foreground:oklch(90% .006 264);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(63% .16 25);--border:oklch(26.27% .0051 264);--muted-foreground:oklch(58.14% .0056 264);--primary-hover:oklch(62.7% .158 275);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(63% .16 25/.08);--destructive-a30:oklch(63% .16 25/.3);--foreground-a5:oklch(90% .006 264/.05);}html{color-scheme:light}html[data-darkmode=true]{color-scheme:dark}.card-header img,.login-input-icon img,.login-pw-toggle img,img.shadcn-icon{filter:brightness(0)saturate()}html[data-darkmode=true] .card-header img,html[data-darkmode=true] .login-input-icon img,html[data-darkmode=true] .login-pw-toggle img,html[data-darkmode=true] img.shadcn-icon{filter:brightness(0)saturate()invert()}.card-header img,img.shadcn-icon,#sidebar img.shadcn-icon{opacity:.65}html[data-darkmode=true] #sidebar img.shadcn-icon{opacity:.7}.card-header button:hover img,#sidebar .sidebar-nav-item.active img,#sidebar .sidebar-accordion-item.active img,#sidebar .sidebar-nav-parent:hover img{opacity:1}html{background-color:var(--background);font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--foreground)}@keyframes fadeSlideIn{to{opacity:1;transform:translateY(0)}}@keyframes slideRightIn{to{opacity:1;transform:translate(0)}}.animate-element{opacity:0;animation:.35s ease-out forwards fadeSlideIn;transform:translateY(8px)}.animate-slide-right{opacity:0;animation:.4s ease-out forwards slideRightIn;transform:translate(24px)}.animate-delay-100{animation-delay:.1s}.animate-delay-200{animation-delay:.2s}.animate-delay-300{animation-delay:.3s}.animate-delay-400{animation-delay:.4s}@media (prefers-reduced-motion:reduce){.animate-element,.animate-slide-right{opacity:1;animation:none;transform:none}}.login-layout{min-height:100vh;display:flex}.login-form-panel{background-color:var(--background);padding-inline:calc(var(--spacing) * 8);padding-top:calc(var(--spacing) * 12);padding-bottom:calc(var(--spacing) * 16);flex-direction:column;flex:1;justify-content:center;align-items:center;display:flex;position:relative}.login-form-wrap{width:100%;max-width:var(--container-md)}.login-brand{margin-bottom:calc(var(--spacing) * 10);gap:calc(var(--spacing) * 2.5);flex-direction:column;display:flex}.login-logo{height:calc(var(--spacing) * 11);width:calc(var(--spacing) * 11);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--primary-a20);background-color:var(--primary-a10);flex-shrink:0;justify-content:center;align-items:center;display:flex;overflow:hidden}.login-logo img{width:100%;height:100%}.login-logo--topleft{top:calc(var(--spacing) * 8);left:calc(var(--spacing) * 8);position:absolute}.login-hostname{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight);color:var(--foreground)}.login-subtitle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--muted-foreground)}.login-error{margin-bottom:calc(var(--spacing) * 4);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--destructive-a30);background-color:var(--destructive-a8);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--destructive)}.login-field{margin-bottom:calc(var(--spacing) * 6)}.login-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));font-weight:var(--font-weight-medium);color:var(--muted-foreground);display:block}.login-input-glass{border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--foreground-a5);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);transition:color .15s,background-color .15s,border-color .15s;position:relative}.login-input-glass:focus-within{border-color:var(--primary-a70);background-color:var(--primary-a10)}.login-input-glass input{width:100%;padding-block:calc(var(--spacing) * 3.5);padding-right:calc(var(--spacing) * 10);padding-left:calc(var(--spacing) * 9);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--foreground);background-color:#0000}.login-input-glass input::placeholder{color:var(--muted-foreground)}.login-input-glass input:focus{outline-style:none}.login-input-icon{pointer-events:none;inset-block:calc(var(--spacing) * 0);left:calc(var(--spacing) * 3);align-items:center;display:flex;position:absolute}.login-input-icon img{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.4}.login-pw-toggle{inset-block:calc(var(--spacing) * 0);right:calc(var(--spacing) * 0);cursor:pointer;border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);background-color:#0000;border-width:0;align-items:center;display:flex;position:absolute}.login-pw-toggle img{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.4;transition:opacity .15s}.login-pw-toggle:hover img{opacity:.7}.login-submit{margin-top:calc(var(--spacing) * 4);cursor:pointer;border-radius:var(--radius-2xl);border-style:var(--tw-border-style);background-color:var(--primary);width:100%;padding-block:calc(var(--spacing) * 3.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));font-weight:var(--font-weight-medium);color:var(--primary-foreground);border-width:0;transition:color .15s,background-color .15s,border-color .15s}@media (hover:hover){.login-submit:hover{background-color:var(--primary-hover)}}.login-footer{right:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 5);left:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 6);text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--muted-foreground);position:absolute}.login-footer a{color:var(--muted-foreground);text-underline-offset:2px;text-decoration-line:underline;transition:color .15s,background-color .15s,border-color .15s}@media (hover:hover){.login-footer a:hover{color:var(--foreground)}}.login-image-panel{padding:calc(var(--spacing) * 4);display:none;position:relative}@media (width>=64rem){.login-image-panel{flex:1;display:block}}.login-image-inner{inset:calc(var(--spacing) * 4);border-radius:var(--radius-3xl);background-position:50%;background-size:cover;background-image:var(--login-bg-image,linear-gradient(160deg, oklch(18% .12 255) 0%, oklch(10% .04 270) 100%));position:absolute}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid} +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--radius-2xl:1rem;--radius-3xl:1.5rem;}}@layer base{*,:before,:after{box-sizing:border-box}body,p{margin:calc(var(--spacing) * 0)}button,input{margin:calc(var(--spacing) * 0);border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);color:inherit;font:inherit;background-color:#0000;border-width:0}::placeholder{opacity:1}[hidden]{display:none!important}}@layer components,utilities;@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-cyrillic-wght-normal.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+1F??}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-greek-wght-normal.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-vietnamese-wght-normal.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(/luci-static/shadcn/inter-latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--background:oklch(98.5% .004 275);--foreground:oklch(18% .02 275);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(55% .2 25);--border:oklch(89.65% .0058 275);--muted-foreground:oklch(52.62% .0131 275);--primary-hover:oklch(62.7% .158 275);--warning-soft-fg:oklch(42% .12 75);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(55% .2 25/.08);--destructive-a30:oklch(55% .2 25/.3);--foreground-a5:oklch(18% .02 275/.05);--warning-a10:oklch(70% .13 75/.1);--warning-a30:oklch(70% .13 75/.3);--font-sans:"Inter Variable", ui-sans-serif, system-ui, sans-serif;}[data-darkmode=true]{--background:oklch(15.9% .005 264);--foreground:oklch(90% .006 264);--primary:oklch(56.7% .158 275);--primary-foreground:oklch(100% 0 0);--destructive:oklch(63% .16 25);--border:oklch(26.27% .0051 264);--muted-foreground:oklch(58.14% .0056 264);--primary-hover:oklch(62.7% .158 275);--warning-soft-fg:oklch(86% .1 85);--primary-a10:oklch(56.7% .158 275/.1);--primary-a20:oklch(56.7% .158 275/.2);--primary-a70:oklch(56.7% .158 275/.7);--destructive-a8:oklch(63% .16 25/.08);--destructive-a30:oklch(63% .16 25/.3);--foreground-a5:oklch(90% .006 264/.05);--warning-a10:oklch(80% .12 80/.1);--warning-a30:oklch(80% .12 80/.3);}html{color-scheme:light}html[data-darkmode=true]{color-scheme:dark}.card-header img,.login-input-icon img,.login-pw-toggle img,img.shadcn-icon{filter:brightness(0)saturate()}html[data-darkmode=true] .card-header img,html[data-darkmode=true] .login-input-icon img,html[data-darkmode=true] .login-pw-toggle img,html[data-darkmode=true] img.shadcn-icon{filter:brightness(0)saturate()invert()}.card-header img,img.shadcn-icon,#sidebar img.shadcn-icon{opacity:.65}html[data-darkmode=true] #sidebar img.shadcn-icon{opacity:.7}.card-header button:hover img,#sidebar .sidebar-nav-item.active img,#sidebar .sidebar-accordion-item.active img,#sidebar .sidebar-nav-parent:hover img{opacity:1}html{background-color:var(--background);font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--foreground)}@keyframes fadeSlideIn{to{opacity:1;transform:translateY(0)}}@keyframes slideRightIn{to{opacity:1;transform:translate(0)}}.animate-element{opacity:0;animation:.35s ease-out forwards fadeSlideIn;transform:translateY(8px)}.animate-slide-right{opacity:0;animation:.4s ease-out forwards slideRightIn;transform:translate(24px)}.animate-delay-100{animation-delay:.1s}.animate-delay-200{animation-delay:.2s}.animate-delay-300{animation-delay:.3s}.animate-delay-400{animation-delay:.4s}@media (prefers-reduced-motion:reduce){.animate-element,.animate-slide-right{opacity:1;animation:none;transform:none}}.login-layout{min-height:100vh;display:flex}.login-form-panel{background-color:var(--background);padding-inline:calc(var(--spacing) * 8);padding-top:calc(var(--spacing) * 12);padding-bottom:calc(var(--spacing) * 16);flex-direction:column;flex:1;justify-content:center;align-items:center;display:flex;position:relative}.login-form-wrap{width:100%;max-width:var(--container-md)}.login-brand{margin-bottom:calc(var(--spacing) * 10);gap:calc(var(--spacing) * 2.5);flex-direction:column;display:flex}.login-logo{height:calc(var(--spacing) * 11);width:calc(var(--spacing) * 11);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--primary-a20);background-color:var(--primary-a10);flex-shrink:0;justify-content:center;align-items:center;display:flex;overflow:hidden}.login-logo img{width:100%;height:100%}.login-logo--topleft{top:calc(var(--spacing) * 8);left:calc(var(--spacing) * 8);position:absolute}.login-hostname{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight);color:var(--foreground)}.login-subtitle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--muted-foreground)}.login-error,.alert-message{margin-bottom:calc(var(--spacing) * 4);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--destructive-a30);background-color:var(--destructive-a8);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--destructive)}:is(.login-error,.alert-message).warning{border-color:var(--warning-a30);background-color:var(--warning-a10);color:var(--warning-soft-fg)}.login-field{margin-bottom:calc(var(--spacing) * 6)}.login-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));font-weight:var(--font-weight-medium);color:var(--muted-foreground);display:block}.login-input-glass{border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--border);background-color:var(--foreground-a5);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);transition:color .15s,background-color .15s,border-color .15s;position:relative}.login-input-glass:focus-within{border-color:var(--primary-a70);background-color:var(--primary-a10)}.login-input-glass input{width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--foreground);background-color:#0000}.login-input-glass input::placeholder{color:var(--muted-foreground)}.login-input-glass input:focus{outline-style:none}.login-input-glass .login-input-icon~input{padding-left:calc(var(--spacing) * 9)}.login-input-icon{pointer-events:none;inset-block:calc(var(--spacing) * 0);left:calc(var(--spacing) * 3);align-items:center;display:flex;position:absolute}.login-input-icon img{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.4}.login-pw-toggle{inset-block:calc(var(--spacing) * 0);right:calc(var(--spacing) * 0);cursor:pointer;border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);background-color:#0000;border-width:0;align-items:center;display:flex;position:absolute}.login-pw-toggle img{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);opacity:.4;transition:opacity .15s}.login-pw-toggle:hover img{opacity:.7}.login-submit{margin-top:calc(var(--spacing) * 4);cursor:pointer;border-radius:var(--radius-2xl);border-style:var(--tw-border-style);background-color:var(--primary);width:100%;padding-block:calc(var(--spacing) * 3.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));font-weight:var(--font-weight-medium);color:var(--primary-foreground);border-width:0;transition:color .15s,background-color .15s,border-color .15s}@media (hover:hover){.login-submit:hover{background-color:var(--primary-hover)}}.login-footer{right:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 5);left:calc(var(--spacing) * 0);padding-inline:calc(var(--spacing) * 6);text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--muted-foreground);position:absolute}.login-footer a{color:var(--muted-foreground);text-underline-offset:2px;text-decoration-line:underline;transition:color .15s,background-color .15s,border-color .15s}@media (hover:hover){.login-footer a:hover{color:var(--foreground)}}.login-image-panel{padding:calc(var(--spacing) * 4);display:none;position:relative}@media (width>=64rem){.login-image-panel{flex:1;display:block}}.login-image-inner{inset:calc(var(--spacing) * 4);border-radius:var(--radius-3xl);background-position:50%;background-size:cover;background-image:var(--login-bg-image,linear-gradient(160deg, oklch(18% .12 255) 0%, oklch(10% .04 270) 100%));position:absolute}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid} diff --git a/luci-theme-shadcn/ucode/template/themes/shadcn/header.ut b/luci-theme-shadcn/ucode/template/themes/shadcn/header.ut index a4526ef7e..f67d498fb 100644 --- a/luci-theme-shadcn/ucode/template/themes/shadcn/header.ut +++ b/luci-theme-shadcn/ucode/template/themes/shadcn/header.ut @@ -8,8 +8,8 @@ import { getuid, getspnam } from 'luci.core'; import { lsdir } from 'fs'; - const boardinfo = ubus.call('system', 'board') ?? {}; - const rootsp = getspnam('root'); + const boardinfo = prefetched_boardinfo ?? (ubus.call('system', 'board') ?? {}); + const rootsp = !blank_page ? getspnam('root') : null; const root_pw_unset = (rootsp && rootsp.pwdp == ''); const page_segs = length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []); @@ -82,8 +82,10 @@ {% if (css): %} {% endif %} + {% if (!blank_page): %} + {% endif %} diff --git a/luci-theme-shadcn/ucode/template/themes/shadcn/sysauth.ut b/luci-theme-shadcn/ucode/template/themes/shadcn/sysauth.ut index b5aa8610d..a55ba72b2 100644 --- a/luci-theme-shadcn/ucode/template/themes/shadcn/sysauth.ut +++ b/luci-theme-shadcn/ucode/template/themes/shadcn/sysauth.ut @@ -4,8 +4,8 @@ Licensed to the public under the Apache License 2.0. -#} -{%const boardinfo = ubus.call('system', 'board') ?? {};-%} -{% include('header', { blank_page: true }) %} +{% const boardinfo = ubus.call('system', 'board') ?? {}; -%} +{% include('header', { blank_page: true, prefetched_boardinfo: boardinfo }) %}
@@ -24,9 +24,9 @@