🎈 Sync 2026-11-05 03:32:18

This commit is contained in:
github-actions[bot] committed 2026-11-05 03:32:18 +08:00
1 parent fd75287766
commit 254dca6210
31 files changed
+509 -160

No files matched your search

+3 -3
View File
@@ -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)
+29
View File
@@ -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
@@ -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;
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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
@@ -35,8 +35,6 @@ const DANGEROUS_RW_PATHS = ['/', '/etc', '/root', '/usr', '/bin', '/sbin', '/lib
const POLL_INTERVAL = 5;
const RUNNING_SPAN = `<span style="color: green; font-weight: bold">${_('Running')}</span>`;
const NOT_RUNNING_SPAN = `<span style="color: red; font-weight: bold">${_('Not running')}</span>`;
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 <a> 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)
: '<span style="color: var(--error-color-high); font-weight: bold;">' + esc(_('Not installed')) + '</span>';
};
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 = '<span style="color: var(--error-color-high); font-weight: bold;">' + esc(_('Core binary not found. Enable the service to trigger an automatic download.')) + '</span>';
}
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) {
+14 -11
View File
@@ -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);
+
@@ -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 <code>with_gvisor</code>. 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 <code>geosite:cn</code>. Rule mode: entries are converted to rule syntax automatically, such as <code>RULE-SET,xxx,real-ip</code> / <code>DOMAIN-SUFFIX,xxx,real-ip</code>; usually add <code>MATCH,fake-ip</code> at the end as fallback. <code>geosite:cn</code> becomes <code>RULE-SET,cn_domain,real-ip</code> via built-in cn.mrs acceleration (other <code>geosite:xxx</code> becomes <code>GEOSITE,xxx,real-ip</code>). 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 — <code>+.lan</code>, <code>rule-set:cn_domain</code>, <code>geosite:cn</code>.<br>
Allowlist: only listed domains get fake-IP.<br>
Rule mode: every entry needs a <code>fake-ip</code> / <code>real-ip</code> action; Clashoo adds <code>real-ip</code> 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"));
@@ -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';
+25 -4
View File
@@ -213,8 +213,8 @@ msgstr "黑名单"
msgid "Blocklist (listed items use real IP, default)"
msgstr "黑名单(列入走真 IP,默认)"
msgid "Blocklist mode: enter domains or shorthand like <code>geosite:cn</code>. Rule mode: entries are converted to rule syntax automatically, such as <code>RULE-SET,xxx,real-ip</code> / <code>DOMAIN-SUFFIX,xxx,real-ip</code>; usually add <code>MATCH,fake-ip</code> at the end as fallback. <code>geosite:cn</code> becomes <code>RULE-SET,cn_domain,real-ip</code> via built-in cn.mrs acceleration (other <code>geosite:xxx</code> becomes <code>GEOSITE,xxx,real-ip</code>). Allowlist mode drops the shipped defaults (they mean the opposite there), so enter your own domains."
msgstr "黑名单模式:填域名或 <code>geosite:cn</code> 简写。规则模式:条目会自动转成 <code>RULE-SET,xxx,real-ip</code> / <code>DOMAIN-SUFFIX,xxx,real-ip</code> 这种与 rules 同语法的写法,末尾通常加一条 <code>MATCH,fake-ip</code> 兜底。<code>geosite:cn</code> 会转成 <code>RULE-SET,cn_domain,real-ip</code>,走内置 cn.mrs 加速(其它 <code>geosite:xxx</code> 则转成 <code>GEOSITE,xxx,real-ip</code>)。白名单模式下随包自带的默认项含义相反,会被跳过,请自行填写需要 Fake-IP 的域名。"
msgid "Blocklist (default): listed domains get a real IP — <code>+.lan</code>, <code>rule-set:cn_domain</code>, <code>geosite:cn</code>.<br>\nAllowlist: only listed domains get fake-IP.<br>\nRule mode: every entry needs a <code>fake-ip</code> / <code>real-ip</code> action; Clashoo adds <code>real-ip</code> for you."
msgstr "黑名单(默认):列出的域名返回真实 IP,可写 +.lan、rule-set:cn_domain、geosite:cn。<br>白名单:只有列出的域名走 Fake-IP。<br>规则模式:每条必须带 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 <code>with_gvisor</code>. 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:用户态栈,内核需要带 <code>with_gvisor</code> 编译。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 的转发规则"
@@ -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 {
@@ -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;
@@ -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,
+2 -2
View File
@@ -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
@@ -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 {
@@ -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.
+2 -2
View File
@@ -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:=
@@ -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-filLine truncated
@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:Line truncated
@@ -54,12 +54,52 @@ const loginIconVer = themeTokens.icon_cache_version || '0';
<input name="luci_password" id="luci_password" type="password" class="login-input" autocomplete="current-password">
</div>
</div>
{% if (auth_fields): %}
{% for (let field in auth_fields): %}
<div class="login-field">
<label class="login-label" for="{{ field.name }}">{{ _(field.label ?? field.name) }}</label>
<div class="input-wrap">
<input
name="{{ field.name }}"
id="{{ field.name }}"
type="{{ field.type ?? 'text' }}"
class="login-input"
{% if (field.placeholder): %}placeholder="{{ field.placeholder }}"{% endif %}
{% if (field.inputmode): %}inputmode="{{ field.inputmode }}"{% endif %}
{% if (field.pattern): %}pattern="{{ field.pattern }}"{% endif %}
{% if (field.maxlength): %}maxlength="{{ field.maxlength }}"{% endif %}
{% if (field.autocomplete): %}autocomplete="{{ field.autocomplete }}"{% endif %}
{% if (field.required): %}required{% endif %}
>
</div>
</div>
{% endfor %}
{% endif %}
{% if (auth_html): %}
<div class="login-field">
{{ auth_html }}
</div>
{% endif %}
{% if (auth_assets): %}
{% for (let asset in auth_assets): %}
{% if (asset.type == 'script'): %}
<script src="{{ asset.src }}"></script>
{% endif %}
{% endfor %}
{% endif %}
</div>
{% if (fuser): %}
<div class="login-error" role="alert">{{ _('Invalid username and/or password! Please try again.') }}</div>
{% if (auth_message || fuser): %}
<div class="login-error{% if (auth_plugin): %} warning{% endif %}" role="alert">
{{ auth_message || _('Invalid username and/or password! Please try again.') }}
</div>
{% endif %}
<input type="hidden" name="luci_magic" value="{{ luci_magic }}">
<div class="login-actions">
<button type="submit" class="login-submit">{{ _('Log in') }}</button>
</div>
+11 -2
View File
@@ -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;
}
}
+2 -2
View File
@@ -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:=
@@ -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;transfLine truncated
@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);Line truncated
@@ -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): %}
<style data-luci-shell>{{ css }}</style>
{% endif %}
{% if (!blank_page): %}
<script src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}"></script>
<script src="{{ resource }}/cbi.js"></script>
{% endif %}
</head>
<body class="lang_{{ dispatcher.lang }}" data-page="{{ entityencode(page, true) }}" data-asset-version="{# PKG_VERSION #}"{% if (!blank_page): %} data-nav-stamp="{{ entityencode(join(':', [dispatcher.lang, ctx?.authuser ?? '', ctx?.authsession ?? '']), true) }}:{# PKG_VERSION #}"{% endif %}{% if (length(patch_all)): %} data-patches="{{ entityencode(join(' ', patch_all), true) }}"{% endif %}>
@@ -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 }) %}
<style>:root{--login-bg-image:url('{{ media }}/images/login-bg.jpeg')}</style>
<main class="login-layout">
@@ -24,9 +24,9 @@
</div>
<form method="post" class="login-form">
{% if (fuser): %}
<div class="login-error animate-element animate-delay-200">
{{ _('Invalid username and/or password! Please try again.') }}
{% if (auth_message || fuser): %}
<div class="login-error{% if (auth_plugin): %} warning{% endif %} animate-element animate-delay-200">
{{ auth_message || _('Invalid username and/or password! Please try again.') }}
</div>
{% endif %}
@@ -42,7 +42,8 @@
id="luci_username"
placeholder="{{ _('Username') }}"
autocomplete="username"
{% if (duser): %}value="{{ entityencode(duser, true) }}"{% else %}value=""{% endif %}
value="{{ entityencode(duser ?? '', true) }}"
{% if (!duser): %}autofocus{% endif %}
>
</div>
</div>
@@ -59,7 +60,7 @@
id="luci_password"
placeholder="{{ _('Password') }}"
autocomplete="current-password"
autofocus
{% if (duser): %}autofocus{% endif %}
>
<button type="button" class="login-pw-toggle" id="pw-toggle" aria-label="{{ _('Reveal/hide password') }}">
<img src="{{ media }}/icons/eye.svg" id="pw-toggle-icon" width="16" height="16" alt="">
@@ -67,6 +68,39 @@
</div>
</div>
{% if (auth_fields): %}
{% for (let field in auth_fields): %}
<div class="login-field animate-element animate-delay-300">
<label class="login-label" for="{{ field.name }}">{{ _(field.label ?? field.name) }}</label>
<div class="login-input-glass">
<input
name="{{ field.name }}"
id="{{ field.name }}"
type="{{ field.type ?? 'text' }}"
{% if (field.placeholder): %}placeholder="{{ field.placeholder }}"{% endif %}
{% if (field.inputmode): %}inputmode="{{ field.inputmode }}"{% endif %}
{% if (field.pattern): %}pattern="{{ field.pattern }}"{% endif %}
{% if (field.maxlength): %}maxlength="{{ field.maxlength }}"{% endif %}
{% if (field.autocomplete): %}autocomplete="{{ field.autocomplete }}"{% endif %}
{% if (field.required): %}required{% endif %}
>
</div>
</div>
{% endfor %}
{% endif %}
{% if (auth_html): %}
<div class="login-field animate-element animate-delay-300">
{{ auth_html }}
</div>
{% endif %}
{% if (auth_assets): %}
{% for (let asset in auth_assets): %}
{% if (asset.type == 'script'): %}
<script src="{{ asset.src }}"></script>
{% endif %}
{% endfor %}
{% endif %}
<input type="hidden" name="luci_magic" value="{{ luci_magic }}">
<button type="submit" class="login-submit animate-element animate-delay-400">
+1 -1
View File
@@ -11,7 +11,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-theme-vantage
PKG_VERSION:=1.0.14
PKG_RELEASE:=29
PKG_RELEASE:=30
PKG_LICENSE:=GPL-3.0-or-later
LUCI_TITLE:=Vantage theme
@@ -3189,6 +3189,19 @@ select[id^="widget.cbid.firewall."] > option { color: var(--text); font-weight:
}
[id^="cbi-firewall-"] .table:has(.cbi-section-table-titles.named[data-title]) .cbi-section-table-row[data-title]:has(input[data-widget-id$=".enabled"]:not(:checked))::before { color: var(--text-3); }
/* File Manager (luci-app-filemanager) injects a light palette of its own: a
#f9f9f9 status bar, #f0f0f0 row hover and editor gutter, #ccc rules. On the
dark scheme that is a white band under the theme's light ink; repaint those
surfaces with the theme's, and let the table's own hover show through the
app's !important row fill. */
body[data-page="admin-system-filemanager"] #status-bar { background: var(--s1); border-color: var(--line); }
body[data-page="admin-system-filemanager"] #status-bar .cbi-progressbar { background: none; }
body[data-page="admin-system-filemanager"] :is(#file-list-container, #content-help) { border-color: var(--line); }
body[data-page="admin-system-filemanager"] #file-table :is(th, td) { border-right-color: var(--line); }
body[data-page="admin-system-filemanager"] #file-table tr:hover { background: none !important; }
body[data-page="admin-system-filemanager"] .line-numbers { background: var(--s3); border-right-color: var(--line); }
body[data-page="admin-system-filemanager"] .sort-button { border-color: var(--line-2); }
/* ----------------------------------------------------- responsive */
@media (max-width: 1100px) {
+2 -2
View File
@@ -14,8 +14,8 @@ MISE_HASH_x86_64:=3832f39c325e343f81fe3d92b2447c5d1a5eea1bc85092bb7b6c2580622264
MISE_HASH_aarch64:=06186cfbfe947049b21d58575fb0ea800cc26ed1375f20f4b678cb3a9d679437
PKG_NAME:=mise
PKG_VERSION:=2026.11.0
PKG_RELEASE:=49
PKG_VERSION:=2026.11.1
PKG_RELEASE:=50
PKG_SOURCE:=$(PKG_NAME)-v$(PKG_VERSION)-linux-$(MISE_ARCH)-musl.tar.gz
PKG_SOURCE_URL:=https://github.com/jdx/mise/releases/download/v$(PKG_VERSION)/
PKG_HASH:=skip
+1 -1
View File
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=mptcp
PKG_VERSION:=6.1
PKG_RELEASE:=30
PKG_RELEASE:=31
PKG_MAINTAINER:=Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+18 -8
View File
@@ -27,13 +27,21 @@ valid_subnet4() {
esac
}
# An MPTCP limit as the kernel takes it: a number from 0 to 8, $2 when $1
# is not a number
# Most subflows and accepted ADD_ADDR per connection: x86_64 routers
# aggregate up to 16 WANs, the others 8
case "$(uname -m)" in
x86_64) MPTCP_LIMIT_MAX=16 ;;
*) MPTCP_LIMIT_MAX=8 ;;
esac
# An MPTCP limit as the kernel takes it: a number from 0 to $3 (default
# MPTCP_LIMIT_MAX), $2 when $1 is not a number
_mptcp_limit() {
local max="${3:-${MPTCP_LIMIT_MAX:-8}}"
case "$1" in
''|*[!0-9]*) echo "$2" ;;
?|??) if [ "$1" -gt 8 ]; then echo 8; else echo "$1"; fi ;;
*) echo 8 ;;
?|??) if [ "$1" -gt "$max" ]; then echo "$max"; else echo "$1"; fi ;;
*) echo "$max" ;;
esac
}
@@ -182,12 +190,14 @@ global_multipath_settings() {
elif [ -f /proc/sys/net/mptcp/enabled ]; then
sysctl -qew net.mptcp.enabled="$multipath_status"
_sysctl_conf_set net.mptcp.enabled "$multipath_status"
# The kernel takes at most 8 for each limit (MPTCP_PM_ADDR_MAX) and
# refuses the whole command when one is above, so a single value too
# high left both limits at whatever they were
# The kernel refuses the whole command when one limit is above its
# maximum, so a single value too high left both limits at whatever
# they were. That maximum is 8 on a kernel without the 64 limits
# backport: retry within it there.
mptcp_add_addr_accepted="$(_mptcp_limit "$mptcp_add_addr_accepted" 1)"
mptcp_subflows="$(_mptcp_limit "$mptcp_subflows" 3)"
ip mptcp limits set add_addr_accepted $mptcp_add_addr_accepted subflows $mptcp_subflows >/dev/null 2>&1
ip mptcp limits set add_addr_accepted $mptcp_add_addr_accepted subflows $mptcp_subflows >/dev/null 2>&1 || \
ip mptcp limits set add_addr_accepted "$(_mptcp_limit "$mptcp_add_addr_accepted" 1 8)" subflows "$(_mptcp_limit "$mptcp_subflows" 3 8)" >/dev/null 2>&1
# The subflow limit decides how many socket buffers one MPTCP connection
# can hold open at once, and they all draw on the same net.ipv4.tcp_mem
# pool, so re-run the sizing now that the limit is known. Cheap and
+2 -2
View File
@@ -8,8 +8,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=netdata
PKG_VERSION:=2.12.2
PKG_RELEASE:=23
PKG_VERSION:=2.12.3
PKG_RELEASE:=24
PKG_MAINTAINER:=Josef Schlehofer <pepe.schlehofer@gmail.com>, Daniel Engberg <daniel.engberg.lists@pyret.net>
PKG_LICENSE:=GPL-3.0-or-later