🍉 Sync 2026-06-12 21:11:08

This commit is contained in:
github-actions[bot] committed 2026-06-12 21:11:08 +08:00
1 parent 6517ee70be
commit a65af00c3f
29 files changed
+646 -185

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=boltbrowser
PKG_RELEASE:=2
PKG_RELEASE:=3
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/br0xen/boltbrowser.git
PKG_SOURCE_DATE:=2023-05-31
PKG_SOURCE_VERSION:=898a1e52a566bd63350c406118f7c6627f5303e9
PKG_SOURCE_VERSION:=6ad3511ddb3c271fbaed2f22e409aa453ed89042
PKG_MIRROR_HASH:=skip
PKG_LICENSE:=GPL-3.0-or-later
+2 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-daede
PKG_VERSION:=1.7
PKG_RELEASE:=2
PKG_VERSION:=1.10
PKG_RELEASE:=3
PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -75,9 +75,12 @@ function detectInstalledBackends() {
}
function detectRunning() {
/* pidof matches the program name exactly and, unlike busybox `pgrep -x`
(which compares the full cmdline `/usr/bin/dae run …` and never hits),
reliably tells dae from daed without substring false positives. */
return Promise.all([
execOk('/usr/bin/pgrep', ['-x', 'dae']),
execOk('/usr/bin/pgrep', ['-x', 'daed'])
execOk('/bin/pidof', ['dae']),
execOk('/bin/pidof', ['daed'])
]).then(function(r) {
return { dae: r[0], daed: r[1] };
});
@@ -114,6 +114,29 @@ function ellipsisCell(section_id) {
return E('span', { 'title': v, 'style': 'font-family:ui-monospace,Menlo,monospace;font-size:12px' }, short);
}
/* Tag is auto-generated and rarely relevant to beginners — render it muted. */
function mutedCell(section_id) {
const v = this.cfgvalue(section_id) || '';
return E('span', { 'class': 'dd-meta', 'style': 'opacity:.55;font-size:11.5px' }, v);
}
/* Fill blank tags of a section type with unique <prefix>_<n> values so dae's
config generator always has a stable identifier. Runs after m.save() wrote
the form values into the uci session, before uci.save(). */
function autofillTags(stype, prefix) {
const secs = uci.sections('dae', stype) || [];
const used = {};
secs.forEach(function(s) { if (s.tag) used[s.tag] = 1; });
let n = 1;
secs.forEach(function(s) {
if (s.tag) return;
while (used[prefix + '_' + n]) n++;
const t = prefix + '_' + n;
uci.set('dae', s['.name'], 'tag', t);
used[t] = 1; n++;
});
}
/* Wrap every form section in a collapsible accordion. openTitles lists the
section titles that should start expanded; the rest start collapsed. */
function accordionizeSections(mapNode, openTitles) {
@@ -156,7 +179,8 @@ function renderDaeForms(ctx) {
s.anonymous = true;
s.sortable = false;
o = s.option(form.Value, 'tag', _('Tag'));
o.placeholder = 'my_sub';
o.placeholder = _('auto-generated');
o.textvalue = mutedCell;
o = s.option(form.Value, 'url', _('Subscription URL'));
o.rmempty = false;
o.placeholder = 'https://example.com/sub';
@@ -169,65 +193,16 @@ function renderDaeForms(ctx) {
o = s.option(form.Flag, 'enabled', _('Enabled'));
o.default = '1';
o.editable = true;
/* add an Update button next to Edit/Delete in each row; dae re-pulls all
subscriptions on reload, so the action is global regardless of the row */
const origRowActions = s.renderRowActions;
s.renderRowActions = function(section_id, more_label) {
const cell = origRowActions.call(this, section_id, more_label);
/* only real subscription links (http/https) are re-fetchable; fixed share
links (vless:// ss:// …) are static. Keep the button on every row for a
consistent layout, but grey it out (disabled) on the fixed ones. */
const subUrl = uci.get('dae', section_id, 'url') || '';
const fetchable = /^https?:\/\//i.test(subUrl);
const btn = E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'margin-right:.25em',
'disabled': fetchable ? null : '',
'title': fetchable ? '' : _('Fixed link — nothing to fetch.')
}, _('Update'));
const upMsg = E('span', { 'class': 'dd-meta', 'style': 'margin-left:8px;display:none' }, '');
function setUpMsg(text, kind) {
upMsg.textContent = text;
upMsg.style.display = '';
upMsg.style.color = (kind === 'err') ? 'var(--error-color, #d33)' : (kind === 'ok') ? 'var(--success-color, #2a8)' : '';
if (kind !== 'err')
setTimeout(function() { if (upMsg.textContent === text) upMsg.style.display = 'none'; }, 5000);
}
if (fetchable) {
btn.addEventListener('click', function(ev) {
ev.preventDefault();
btn.disabled = true;
setUpMsg(_('Updating…'));
backend.detectRunning().then(function(r) {
if (!r || !r.dae)
return setUpMsg(_('dae is stopped; it fetches subscriptions on start.'), 'warn');
return fs.exec(backend.BACKENDS.dae.initd, ['hot_reload']).then(function(res) {
if (res && res.code !== 0)
setUpMsg(_('Update failed: %s').format(res.stderr || res.stdout || ('exit ' + res.code)), 'err');
else
setUpMsg(_('Subscriptions updated (dae reloaded)'), 'ok');
});
}).catch(function(e) {
setUpMsg(_('Update failed: %s').format(e.message || e), 'err');
}).finally(function() { btn.disabled = false; });
});
}
const firstBtn = cell.querySelector('button, a.cbi-button');
if (firstBtn && firstBtn.parentNode) firstBtn.parentNode.insertBefore(btn, firstBtn);
else cell.appendChild(btn);
if (btn.parentNode) btn.parentNode.insertBefore(upMsg, btn.nextSibling);
else cell.appendChild(upMsg);
return cell;
};
/* Manual nodes (share links) */
s = m.section(form.GridSection, 'node', _('Nodes'),
s = m.section(form.GridSection, 'node', _('Manual nodes'),
_('One node share link per line — ss, vmess, vless, trojan, tuic, hysteria2, socks5.'));
s.addremove = true;
s.anonymous = true;
s.sortable = false;
o = s.option(form.Value, 'tag', _('Tag'));
o.placeholder = 'node1';
o.placeholder = _('auto-generated');
o.textvalue = mutedCell;
o = s.option(form.Value, 'link', _('Share Link'));
o.rmempty = false;
o.placeholder = 'vmess://...';
@@ -325,55 +300,98 @@ function renderDaeForms(ctx) {
statusTimer = setTimeout(function() { status.classList.remove('show'); }, hold || 3000);
}
const save = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save and Hot Reload'));
/* one save pipeline, parameterised by intent:
- opts.start: after generate, enable + start dae (stopped → "Save and Start")
- otherwise just save + generate; generate hot-reloads itself when dae runs */
function doSave(opts, btns) {
btns.forEach(function(b) { b.disabled = true; });
flash(_('Saving…'));
/* ensure the singleton sections exist before parsing the form —
on installs where uci-defaults never seeded them, writing an
option to a missing named section fails with ubus NOT_FOUND (4) */
if (!uci.get('dae', 'routing')) uci.add('dae', 'routing', 'routing');
if (!uci.get('dae', 'dns')) uci.add('dae', 'dns', 'dns');
if (!uci.get('dae', 'config')) uci.add('dae', 'dae', 'config');
return m.save(null, true)
.then(function() {
/* assign unique tags to any rows the user left blank */
autofillTags('subscription', 'subscription');
autofillTags('node', 'node');
})
.then(function() { return uci.save(); })
.then(function() {
/* apply flushes the rpc session to /etc/config so the
generator (reads /etc/config/dae) sees the form data;
skip it when nothing changed — apply on an empty
changeset throws ubus NO_DATA (code 5). */
return uci.changes().then(function(ch) {
return (ch && Object.keys(ch).length) ? uci.apply() : null;
});
})
.then(function() { return fs.exec(GEN, ['generate']); })
.then(function(res) {
if (res && res.code !== 0) {
const err = (res.stderr || res.stdout || ('exit ' + res.code)).trim().split('\n')[0];
flash(_('Apply failed: %s').format(err), 'err', 9000);
throw new Error('gen-failed');
}
if (!opts.start) {
/* generate already hot-reloaded if dae was running */
flash(opts.okMsg || _('Saved'), 'ok');
setTimeout(function() { window.location.reload(); }, 900);
return;
}
flash(_('Starting…'));
return fs.exec('/sbin/uci', ['set', 'dae.config.enabled=1'])
.then(function() { return fs.exec('/sbin/uci', ['commit', 'dae']); })
.then(function() { return fs.exec(backend.BACKENDS.dae.initd, ['enable']); })
.then(function() { return fs.exec(backend.BACKENDS.dae.initd, ['start']); })
.then(function(r2) {
if (r2 && r2.code !== 0)
flash(_('Start failed: %s').format(r2.stderr || r2.stdout || ('exit ' + r2.code)), 'err', 9000);
else {
flash(_('Saved · started'), 'ok');
setTimeout(function() { window.location.reload(); }, 900);
}
});
})
.catch(function(e) {
if (e && e.message === 'gen-failed') return;
if (e && e.name === 'CBIValidationError') {
flash(_('Please fix the highlighted fields.'), 'err', 6000);
return;
}
flash(_('Save failed: %s').format(e.message || e), 'err', 9000);
})
.finally(function() { btns.forEach(function(b) { b.disabled = false; }); });
}
return m.render().then(function(mapNode) {
save.addEventListener('click', function(ev) {
ev.preventDefault();
save.disabled = true;
flash(_('Saving…'));
/* ensure the singleton sections exist before parsing the form —
on installs where uci-defaults never seeded them, writing an
option to a missing named section fails with ubus NOT_FOUND (4) */
if (!uci.get('dae', 'routing')) uci.add('dae', 'routing', 'routing');
if (!uci.get('dae', 'dns')) uci.add('dae', 'dns', 'dns');
if (!uci.get('dae', 'config')) uci.add('dae', 'dae', 'config');
/* commit dae UCI (apply flushes the rpc session to /etc/config,
which a CLI `uci commit` cannot see), then regenerate + hot reload */
m.save(null, true)
.then(function() { return uci.save(); })
.then(function() {
/* apply flushes the rpc session to /etc/config so the
generator (reads /etc/config/dae) sees the form data;
skip it when nothing changed — apply on an empty
changeset throws ubus NO_DATA (code 5). */
return uci.changes().then(function(ch) {
return (ch && Object.keys(ch).length) ? uci.apply() : null;
});
})
.then(function() { return fs.exec(GEN, ['generate']); })
.then(function(res) {
if (res && res.code !== 0) {
const err = (res.stderr || res.stdout || ('exit ' + res.code)).trim().split('\n')[0];
flash(_('Apply failed: %s').format(err), 'err', 9000);
return;
}
/* reload to reflect committed state and clear the change badge */
flash(_('Saved · validated · hot-reloaded'), 'ok');
setTimeout(function() { window.location.reload(); }, 900);
})
.catch(function(e) {
if (e && e.name === 'CBIValidationError') {
flash(_('Please fix the highlighted fields.'), 'err', 6000);
return;
}
flash(_('Save failed: %s').format(e.message || e), 'err', 9000);
})
.finally(function() { save.disabled = false; });
});
/* make each form section collapsible; keep the two everyday ones open */
accordionizeSections(mapNode, [ _('Subscriptions'), _('Nodes') ]);
/* beginner default: only Subscriptions starts open; nodes/groups/routing/dns/logging collapse */
accordionizeSections(mapNode, [ _('Subscriptions') ]);
/* fold groups / routing / dns / logging under one "Advanced settings"
collapsible so the everyday view is just subscriptions + nodes */
const advTitles = [ _('Groups'), _('Routing'), _('DNS'), _('Logging') ];
const advWraps = [];
mapNode.querySelectorAll('.dd-adv').forEach(function(w) {
const t = w.querySelector('.dd-adv-bar span');
if (t && advTitles.indexOf(t.textContent.trim()) >= 0) advWraps.push(w);
});
if (advWraps.length) {
const outerBody = E('div', { 'class': 'dd-adv-body' });
const outer = E('div', { 'class': 'dd-adv dd-closed' }, [
E('div', { 'class': 'dd-adv-bar' }, [
E('span', {}, _('Advanced settings')),
E('span', { 'class': 'dd-adv-chevron' }, '›')
]),
outerBody
]);
outer.firstChild.addEventListener('click', function() { outer.classList.toggle('dd-closed'); });
advWraps[0].parentNode.insertBefore(outer, advWraps[0]);
advWraps.forEach(function(w) { outerBody.appendChild(w); });
}
const cardChildren = [
E('h4', { 'class': 'dd-card-title' }, _('dae Configuration')),
@@ -381,9 +399,72 @@ function renderDaeForms(ctx) {
_('Add a subscription or node, then save — it takes effect automatically.'))
];
cardChildren.push(mapNode);
cardChildren.push(E('div', { 'class': 'dd-editor-actions' }, [ save, status ]));
return E('div', { 'class': 'dd-card dd-settings-card' }, cardChildren);
/* state-aware primary action: running → one "Save and Apply"; stopped →
"Save config" (secondary) + "Save and Start" (primary). Starting the
proxy is an explicit choice, never an implicit side effect of saving. */
return backend.detectRunning().then(function(rr) {
const running = rr && rr.dae;
/* "Update all subscriptions" only helps while dae runs (hot reload
re-pulls them). A stopped dae re-fetches every subscription on
start, so there is nothing to update — hide the button then. */
if (running) {
const upBtn = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Update all subscriptions'));
const upMsg = E('span', { 'class': 'dd-meta', 'style': 'margin-left:8px;display:none' }, '');
const setUp = function(text, kind) {
upMsg.textContent = text;
upMsg.style.display = '';
upMsg.style.color = (kind === 'err') ? 'var(--error-color, #d33)' : 'var(--success-color, #2a8)';
if (kind !== 'err')
setTimeout(function() { if (upMsg.textContent === text) upMsg.style.display = 'none'; }, 5000);
};
upBtn.addEventListener('click', function(ev) {
ev.preventDefault();
upBtn.disabled = true;
setUp(_('Updating…'));
fs.exec(backend.BACKENDS.dae.initd, ['hot_reload']).then(function(res) {
if (res && res.code !== 0)
setUp(_('Update failed: %s').format(res.stderr || res.stdout || ('exit ' + res.code)), 'err');
else
setUp(_('Subscriptions updated (dae reloaded)'), 'ok');
}).catch(function(e) {
setUp(_('Update failed: %s').format(e.message || e), 'err');
}).finally(function() { upBtn.disabled = false; });
});
const upWrap = E('div', { 'class': 'dd-actions', 'style': 'margin:0 0 8px' }, [ upBtn, upMsg ]);
const advs = mapNode.querySelectorAll('.dd-adv');
for (let i = 0; i < advs.length; i++) {
const t = advs[i].querySelector('.dd-adv-bar span');
if (t && t.textContent.trim() === _('Subscriptions')) {
const body = advs[i].querySelector('.dd-adv-body');
const tbl = body && body.querySelector('.cbi-section-table');
if (tbl) tbl.parentNode.insertBefore(upWrap, tbl);
else if (body) body.insertBefore(upWrap, body.firstChild);
break;
}
}
}
const actions = [];
let btns;
if (running) {
const apply = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save and Apply'));
btns = [ apply ];
apply.addEventListener('click', function(ev) { ev.preventDefault(); doSave({ okMsg: _('Saved · applied') }, btns); });
actions.push(apply);
} else {
const saveOnly = E('button', { 'class': 'cbi-button' }, _('Save config'));
const saveStart = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save and Start'));
btns = [ saveOnly, saveStart ];
saveOnly.addEventListener('click', function(ev) { ev.preventDefault(); doSave({ okMsg: _('Saved') }, btns); });
saveStart.addEventListener('click', function(ev) { ev.preventDefault(); doSave({ start: true }, btns); });
actions.push(saveOnly, saveStart);
}
actions.push(status);
cardChildren.push(E('div', { 'class': 'dd-editor-actions' }, actions));
return E('div', { 'class': 'dd-card dd-settings-card' }, cardChildren);
});
});
}
@@ -425,12 +506,36 @@ function renderDaeImportBanner() {
}).catch(function() { return null; });
}
/* Lightweight dae-DSL syntax highlighter: tokenises into spans for a <pre>
layer drawn behind a transparent textarea (no editor library needed). */
function highlightDae(src) {
const re = /(#[^\n]*)|('[^'\n]*')|\b(global|subscription|node|group|routing|dns|include|upstream|request|response)\b|\b([A-Za-z_][\w-]*)(?=\s*\()|(->|&&|\|\|)|\b(geoip|geosite)(?=:)/g;
let out = '', last = 0, m;
const esc = function(s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); };
while ((m = re.exec(src)) !== null) {
out += esc(src.slice(last, m.index));
const cls = m[1] ? 'c' : m[2] ? 's' : m[3] ? 'k' : m[4] ? 'f' : m[5] ? 'o' : 'g';
out += '<span class="dh-' + cls + '">' + esc(m[0]) + '</span>';
last = m.index + m[0].length;
}
out += esc(src.slice(last));
return out;
}
function renderDaeEditor() {
const textarea = E('textarea', {
'class': 'dd-editor',
'class': 'dd-editor dd-editor-hl',
'spellcheck': 'false',
'placeholder': _('dae config file is empty. Click "Initialize from example" to start, or paste your config here.')
}, '');
const hlCode = E('code', {});
const hlPre = E('pre', { 'class': 'dd-hl', 'aria-hidden': 'true' }, hlCode);
const editWrap = E('div', { 'class': 'dd-edit-wrap' }, [ hlPre, textarea ]);
function syncHL() {
hlCode.innerHTML = highlightDae(textarea.value) + '\n';
hlPre.scrollTop = textarea.scrollTop;
}
textarea.addEventListener('scroll', function() { hlPre.scrollTop = textarea.scrollTop; });
const save = E('button', { 'class': 'cbi-button cbi-button-positive' }, _('Save manual config'));
const init = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Initialize from example'));
const status = E('span', { 'class': 'dd-editor-status' }, '');
@@ -514,6 +619,7 @@ function renderDaeEditor() {
}
function refreshPlaceholders() {
syncHL();
const hits = detectPlaceholders(textarea.value);
const titleEl = phWarn.querySelector('.dd-ph-warn-title');
const howtoEl = phWarn.querySelector('.dd-ph-howto');
@@ -634,7 +740,7 @@ function renderDaeEditor() {
_('Edit config DSL — subscriptions, nodes, routing, DNS. Load template via Initialize. Replace placeholder URL before saving. Switch to daed for GUI.')
]),
phWarn,
textarea,
editWrap,
footer,
E('div', { 'class': 'dd-editor-actions' }, [ save, init, insertSelect, status ])
])
@@ -17,7 +17,7 @@ const CSS = [
'.dd-log-toolbar label{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;cursor:pointer;margin:0;opacity:.85}',
'.dd-log-toolbar input[type="checkbox"]{margin:0}',
'.dd-log-toolbar input[type="text"]{font-size:11.5px;padding:4px 8px;border-radius:5px;border:1px solid rgba(128,128,128,.28);background:transparent;color:inherit;min-width:160px}',
'.dd-log-toolbar .dd-log-btn{font-size:11.5px;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.28);background:transparent;color:inherit;cursor:pointer}',
'.dd-log-toolbar .dd-log-btn{font-size:11.5px;line-height:1.4;min-height:0;height:auto;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.28);background:transparent;color:inherit;cursor:pointer}',
'.dd-log-toolbar .dd-log-btn:hover{background:rgba(128,128,128,.1)}',
'.dd-log-toolbar .dd-log-meta{margin-left:auto;font-size:10.5px;opacity:.55;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}',
'.dd-log-pane{height:60vh;min-height:360px;overflow:auto;padding:8px 10px;border:1px solid rgba(0,0,0,.08);border-radius:7px;background:#1a1d21;color:#d8dde6;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:11.5px;line-height:1.55;white-space:pre-wrap;word-break:break-all}',
@@ -5,7 +5,7 @@
const CSS = [
'.dd-wrap{padding:4px 0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif}',
'.dd-card{border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:10px 14px;margin-bottom:10px;box-shadow:0 2px 8px rgba(0,0,0,.03);background:rgba(255,255,255,.02)}',
'.dd-card{border:1px solid rgba(0,0,0,.06);border-radius:10px;padding:9px 14px;margin-bottom:7px;box-shadow:0 2px 8px rgba(0,0,0,.03);background:rgba(255,255,255,.02)}',
'.dd-card-title{font-size:11px;font-weight:600;opacity:.55;margin:0 0 8px;letter-spacing:.3px;text-transform:uppercase}',
'.dd-status-row{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-bottom:0}',
'.dd-status-row .dd-grow{flex:1 1 auto}',
@@ -18,7 +18,7 @@ const CSS = [
'.dd-actions{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 0}',
'.dd-actions .cbi-button{font-size:11.5px;padding:4px 12px;border-radius:5px}',
'.dd-actions a.cbi-button{display:inline-flex;align-items:center;gap:4px}',
'.dd-wrap .cbi-button{font-size:11px !important;padding:4px 12px !important;border-radius:5px !important;border:1px solid rgba(128,128,128,.35) !important;background:transparent !important;color:inherit !important;box-shadow:none !important;cursor:pointer;white-space:nowrap}',
'.dd-wrap .cbi-button{font-size:11px !important;line-height:1.4 !important;min-height:0 !important;height:auto !important;padding:4px 12px !important;border-radius:5px !important;border:1px solid rgba(128,128,128,.35) !important;background:transparent !important;color:inherit !important;box-shadow:none !important;cursor:pointer;white-space:nowrap}',
'.dd-wrap .cbi-button:hover{background:rgba(128,128,128,.12) !important}',
'.dd-wrap .cbi-button:disabled{opacity:.45;cursor:not-allowed}',
'.dd-wrap .cbi-button-action,.dd-wrap .cbi-button-positive,.dd-wrap .cbi-button-add,.dd-wrap .cbi-button-edit{border-color:#4aa065 !important;color:#4aa065 !important}',
@@ -56,13 +56,18 @@ const CSS = [
'.dd-settings-card .cbi-value-description,.dd-settings-card .cbi-value-helptext{font-size:11.5px !important;opacity:.6;line-height:1.45;padding-top:3px}',
'.dd-settings-card .cbi-section-remove{background:transparent !important;border:0 !important;box-shadow:none !important}',
'.dd-settings-card .cbi-section-remove .cbi-button{border-color:#d96d6d !important;color:#d96d6d !important}',
'.dd-settings-card input[type="checkbox"]{box-shadow:none !important;background-repeat:no-repeat !important;background-position:center !important;background-size:62% !important}',
'.dd-settings-card input[type="checkbox"]:before{content:none !important;display:none !important;box-shadow:none !important}',
/* themes (esp. Argon) restyle checkboxes into faint, near-invisible boxes;
force native rendering + brand accent so the checkmark is always clear */
'.dd-settings-card input[type="checkbox"]{-webkit-appearance:checkbox !important;appearance:checkbox !important;accent-color:#4aa065;width:16px;height:16px;background:none !important;border:0 !important;box-shadow:none !important;background-image:none !important}',
'.dd-settings-card input[type="checkbox"]:before,.dd-settings-card input[type="checkbox"]:after{content:none !important;display:none !important}',
/* line the Subscriptions and Nodes grid tables up column-for-column. fixed
layout + explicit per-column percentages make the widths authoritative
across themes (Argon's auto layout otherwise sizes the empty Nodes table
by content, and even ignores px cell widths, so they drift apart) */
'.dd-settings-card .cbi-section-table{table-layout:fixed !important;width:100%}',
/* flatten the table header: Argon tints header cells (#f6f9fc), which reads
as an off-colour band on the white card — keep it transparent */
'.dd-settings-card .cbi-section-table-titles,.dd-settings-card .cbi-section-table-titles .cbi-section-table-cell{background:transparent !important;background-image:none !important}',
'.dd-settings-card .cbi-section-table-cell:nth-child(1){width:14% !important}',
'.dd-settings-card .cbi-section-table-cell:nth-child(2){width:50% !important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
'.dd-settings-card .cbi-section-table-cell:nth-child(3){width:12% !important}',
@@ -79,13 +84,37 @@ const CSS = [
'.dd-adv-body{margin-top:8px;padding:2px 4px 4px}',
'.dd-adv.dd-closed .dd-adv-body{display:none}',
'.dd-editor{width:100%;min-height:460px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:12px;line-height:1.5;box-sizing:border-box;resize:vertical;border-radius:6px 6px 0 0}',
/* syntax-highlight overlay: a <pre> sits behind a transparent <textarea>.
both share identical box metrics so glyphs line up; the textarea stays the
real editor (caret, selection, insert-at-cursor, jump-to-line all native) */
'.dd-edit-wrap{position:relative}',
'.dd-edit-wrap .dd-editor,.dd-edit-wrap .dd-hl{margin:0;padding:10px 12px;border-width:1px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace;font-size:12px;line-height:1.5;letter-spacing:0;tab-size:4;white-space:pre-wrap;word-break:break-word;box-sizing:border-box}',
'.dd-edit-wrap .dd-hl{position:absolute;inset:0;margin:0;overflow:hidden;border:1px solid transparent;border-radius:6px 6px 0 0;pointer-events:none;background:transparent;color:#3b4252;z-index:1}',
'.dd-edit-wrap .dd-hl code{font:inherit;white-space:inherit;word-break:inherit;display:block}',
'.dd-edit-wrap .dd-editor-hl{position:relative;z-index:2;color:transparent;background:transparent;caret-color:#2f7288;border:1px solid rgba(128,128,128,.28)}',
'.dd-edit-wrap .dd-editor-hl::placeholder{color:rgba(128,128,128,.55)}',
'.dd-edit-wrap .dd-editor-hl::selection{background:rgba(56,134,161,.25)}',
'.dh-c{color:#8a919a;font-style:italic}',
'.dh-s{color:#2a8a4a}',
'.dh-k{color:#9a3fb5;font-weight:600}',
'.dh-f{color:#2f7288}',
'.dh-o{color:#c2772a}',
'.dh-g{color:#b0226a}',
'html[data-darkmode="true"] .dd-edit-wrap .dd-hl,body.dark .dd-edit-wrap .dd-hl,html[data-bs-theme="dark"] .dd-edit-wrap .dd-hl{color:#c8cdd6}',
'html[data-darkmode="true"] .dd-edit-wrap .dd-editor-hl,body.dark .dd-edit-wrap .dd-editor-hl,html[data-bs-theme="dark"] .dd-edit-wrap .dd-editor-hl{caret-color:#7fd0e8}',
'html[data-darkmode="true"] .dh-c,body.dark .dh-c{color:#6b7280}',
'html[data-darkmode="true"] .dh-s,body.dark .dh-s{color:#7ec699}',
'html[data-darkmode="true"] .dh-k,body.dark .dh-k{color:#c792ea}',
'html[data-darkmode="true"] .dh-f,body.dark .dh-f{color:#82c4dd}',
'html[data-darkmode="true"] .dh-o,body.dark .dh-o{color:#e0a35a}',
'html[data-darkmode="true"] .dh-g,body.dark .dh-g{color:#ec6fa8}',
'.dd-editor-footer{display:flex;flex-wrap:wrap;align-items:center;gap:14px;padding:5px 10px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:10.5px;background:rgba(128,128,128,.06);border:1px solid rgba(0,0,0,.06);border-top:0;border-radius:0 0 6px 6px;color:inherit;opacity:.78}',
'.dd-editor-footer .dd-fb-item{display:inline-flex;align-items:center;gap:5px}',
'.dd-editor-footer .dd-fb-key{opacity:.55}',
'.dd-editor-footer .dd-fb-warn{color:#b07d00;font-weight:600}',
'.dd-editor-footer .dd-fb-ok{color:#3da66a;font-weight:600}',
'body.dark .dd-editor-footer,html[data-theme="dark"] .dd-editor-footer,html[data-bs-theme="dark"] .dd-editor-footer{border-color:rgba(255,255,255,.1);background:rgba(255,255,255,.04)}',
'body.dark .dd-editor-footer .dd-fb-warn,html[data-theme="dark"] .dd-editor-footer .dd-fb-warn,html[data-bs-theme="dark"] .dd-editor-footer .dd-fb-warn{color:#e0b34a}',
'body.dark .dd-editor-footer,html[data-theme="dark"] .dd-editor-footer,html[data-bs-theme="dark"] .dd-editor-footer,html[data-darkmode="true"] .dd-editor-footer{border-color:rgba(255,255,255,.1);background:rgba(255,255,255,.04)}',
'body.dark .dd-editor-footer .dd-fb-warn,html[data-theme="dark"] .dd-editor-footer .dd-fb-warn,html[data-bs-theme="dark"] .dd-editor-footer .dd-fb-warn,html[data-darkmode="true"] .dd-editor-footer .dd-fb-warn{color:#e0b34a}',
'.dd-insert-select{font-size:11.5px;padding:4px 8px;border-radius:5px;border:1px solid rgba(128,128,128,.35);background:transparent;color:inherit;cursor:pointer}',
'.dd-insert-select:hover{border-color:rgba(56,134,161,.55)}',
'.dd-editor-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px}',
@@ -108,7 +137,7 @@ const CSS = [
'.dd-ph-warn{display:none;font-size:12px;line-height:1.6;margin:0 0 8px;padding:10px 12px;border-radius:6px;background:rgba(217,158,0,.08);color:inherit}',
'.dd-ph-warn.show{display:block}',
'.dd-ph-warn-title{font-weight:600;margin-bottom:6px;color:#b07d00;font-size:12.5px}',
'body.dark .dd-ph-warn-title,html[data-theme="dark"] .dd-ph-warn-title,html[data-bs-theme="dark"] .dd-ph-warn-title{color:#e0b34a}',
'body.dark .dd-ph-warn-title,html[data-theme="dark"] .dd-ph-warn-title,html[data-bs-theme="dark"] .dd-ph-warn-title,html[data-darkmode="true"] .dd-ph-warn-title{color:#e0b34a}',
'.dd-ph-howto{margin:4px 0 8px;opacity:.85}',
'.dd-ph-howto code{padding:1px 5px;border-radius:3px;background:rgba(128,128,128,.18);font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}',
'.dd-ph-list{margin:6px 0 0;padding:6px 0 0;list-style:none;border-top:1px dashed rgba(217,158,0,.3)}',
@@ -116,11 +145,21 @@ const CSS = [
'.dd-ph-list li{padding:2px 0;display:flex;gap:8px;align-items:baseline;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px}',
'.dd-ph-list .dd-ph-ln{display:inline-block;min-width:38px;text-align:center;padding:1px 4px;border-radius:3px;background:rgba(217,158,0,.18);color:#8a6300;opacity:.95;cursor:pointer;font-weight:600;text-decoration:none}',
'.dd-ph-list .dd-ph-ln:hover{background:rgba(217,158,0,.32)}',
'body.dark .dd-ph-list .dd-ph-ln,html[data-theme="dark"] .dd-ph-list .dd-ph-ln,html[data-bs-theme="dark"] .dd-ph-list .dd-ph-ln{color:#f0c763;background:rgba(217,158,0,.22)}',
'body.dark .dd-ph-list .dd-ph-ln,html[data-theme="dark"] .dd-ph-list .dd-ph-ln,html[data-bs-theme="dark"] .dd-ph-list .dd-ph-ln,html[data-darkmode="true"] .dd-ph-list .dd-ph-ln{color:#f0c763;background:rgba(217,158,0,.22)}',
'.dd-ph-list .dd-ph-txt{opacity:.7;word-break:break-all}',
'body.dark .dd-card,html[data-theme="dark"] .dd-card,html[data-bs-theme="dark"] .dd-card{border-color:rgba(255,255,255,.08);background:rgba(255,255,255,.02)}',
'body.dark .dd-adv-bar,html[data-theme="dark"] .dd-adv-bar,html[data-bs-theme="dark"] .dd-adv-bar{background:rgba(255,255,255,.04);border-color:rgba(255,255,255,.10)}',
'body.dark .dd-settings-card .cbi-value-field input,body.dark .dd-settings-card .cbi-value-field select,body.dark .dd-settings-card .cbi-value-field textarea,html[data-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-theme="dark"] .dd-settings-card .cbi-value-field textarea,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field textarea{border-color:rgba(255,255,255,.18)}'
'body.dark .dd-card,html[data-theme="dark"] .dd-card,html[data-bs-theme="dark"] .dd-card,html[data-darkmode="true"] .dd-card{border-color:rgba(255,255,255,.08);background:rgba(255,255,255,.02)}',
'body.dark .dd-adv-bar,html[data-theme="dark"] .dd-adv-bar,html[data-bs-theme="dark"] .dd-adv-bar,html[data-darkmode="true"] .dd-adv-bar{background:rgba(255,255,255,.04);border-color:rgba(255,255,255,.10)}',
'body.dark .dd-settings-card .cbi-value-field input,body.dark .dd-settings-card .cbi-value-field select,body.dark .dd-settings-card .cbi-value-field textarea,html[data-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-theme="dark"] .dd-settings-card .cbi-value-field textarea,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field input,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field select,html[data-bs-theme="dark"] .dd-settings-card .cbi-value-field textarea,html[data-darkmode="true"] .dd-settings-card .cbi-value-field input,html[data-darkmode="true"] .dd-settings-card .cbi-value-field select,html[data-darkmode="true"] .dd-settings-card .cbi-value-field textarea{border-color:rgba(255,255,255,.18)}',
/* phone: render each subscription/node row as a self-contained card instead
of a cramped table (LuCI already block-stacks the cells; we add the card
frame, drop the desktop column widths, and shrink the action buttons) */
'@media (max-width:600px){',
'.dd-settings-card .cbi-section-table-cell:nth-child(1),.dd-settings-card .cbi-section-table-cell:nth-child(2),.dd-settings-card .cbi-section-table-cell:nth-child(3),.dd-settings-card .cbi-section-table-cell:nth-child(4){width:auto !important;min-width:0 !important;white-space:normal !important;overflow:visible !important;text-overflow:clip !important}',
'.dd-settings-card .cbi-section-table-row{display:block;border:1px solid rgba(128,128,128,.2);border-radius:8px;padding:6px 10px;margin:0 0 8px;background:rgba(128,128,128,.03)}',
'.dd-settings-card .cbi-section-table-cell{display:block;padding:3px 0 !important}',
'.dd-settings-card .cbi-section-actions{display:flex;flex-wrap:wrap;gap:6px;padding-top:6px !important}',
'.dd-settings-card .cbi-section-actions .cbi-button{width:auto;margin:0;flex:0 0 auto}',
'}'
].join('');
return baseclass.extend({ CSS: CSS });
@@ -31,7 +31,7 @@ const CSS = [
'.dd-up-name{font-weight:600;opacity:.85}',
'.dd-up-meta{opacity:.7;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
'.dd-up-version{font-size:11px;opacity:.55;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;white-space:nowrap}',
'.dd-up-btn{font-size:11px;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.35);background:transparent;color:inherit;cursor:pointer;white-space:nowrap}',
'.dd-up-btn{font-size:11px;line-height:1.4;min-height:0;height:auto;padding:4px 12px;border-radius:5px;border:1px solid rgba(128,128,128,.35);background:transparent;color:inherit;cursor:pointer;white-space:nowrap}',
'.dd-up-btn:hover{background:rgba(128,128,128,.12)}',
'.dd-up-btn:disabled{opacity:.45;cursor:not-allowed}',
'.dd-up-btn-primary{border-color:#4aa065;color:#4aa065}',
@@ -202,7 +202,7 @@ function renderStatusCard(ctx, listenAddr) {
});
actions.push(restart);
}
if (be.name === 'dae') {
if (state.running && be.name === 'dae') {
const hot = E('button', { 'class': 'cbi-button cbi-button-action' }, _('Hot Reload'));
hot.addEventListener('click', function(ev) {
ev.preventDefault();
+48
View File
@@ -376,3 +376,51 @@ msgstr ""
#: dae.js
msgid " are placeholder addresses from dae's template, not real subscriptions. Replace the URL on any one line with your airport subscription link (delete the spare lines) and save."
msgstr ""
#: dae.js
msgid "Manual nodes"
msgstr ""
#: dae.js
msgid "Update all subscriptions"
msgstr ""
#: dae.js
msgid "Advanced settings"
msgstr ""
#: dae.js
msgid "auto-generated"
msgstr ""
#: dae.js
msgid "Save and Apply"
msgstr ""
#: dae.js
msgid "Save config"
msgstr ""
#: dae.js
msgid "Save and Start"
msgstr ""
#: dae.js
msgid "Saved · applied"
msgstr ""
#: dae.js
msgid "Saved"
msgstr ""
#: dae.js
msgid "Saved · started"
msgstr ""
#: dae.js
msgid "Starting…"
msgstr ""
#: dae.js
msgid "Start failed: %s"
msgstr ""
+33
View File
@@ -664,3 +664,36 @@ msgstr "这些 "
msgid " are placeholder addresses from dae's template, not real subscriptions. Replace the URL on any one line with your airport subscription link (delete the spare lines) and save."
msgstr " 都是 dae 官方模板留的假地址,不是真订阅。把其中任意一行的 URL 换成你机场的订阅链接(删掉多余行),保存即可跑通。"
msgid "Manual nodes"
msgstr "手动添加节点"
msgid "Update all subscriptions"
msgstr "更新全部订阅"
msgid "Advanced settings"
msgstr "高级设置"
msgid "auto-generated"
msgstr "自动生成"
msgid "Save config"
msgstr "保存配置"
msgid "Save and Start"
msgstr "保存并启动"
msgid "Saved · applied"
msgstr "已保存并应用"
msgid "Saved"
msgstr "已保存"
msgid "Saved · started"
msgstr "已保存并启动"
msgid "Starting…"
msgstr "启动中…"
msgid "Start failed: %s"
msgstr "启动失败:%s"
+36
View File
@@ -457,3 +457,39 @@ msgstr "这些 "
msgid " are placeholder addresses from dae's template, not real subscriptions. Replace the URL on any one line with your airport subscription link (delete the spare lines) and save."
msgstr " 都是 dae 官方模板留的假地址,不是真订阅。把其中任意一行的 URL 换成你机场的订阅链接(删掉多余行),保存即可跑通。"
msgid "Manual nodes"
msgstr "手动添加节点"
msgid "Update all subscriptions"
msgstr "更新全部订阅"
msgid "Advanced settings"
msgstr "高级设置"
msgid "auto-generated"
msgstr "自动生成"
msgid "Save and Apply"
msgstr "保存并应用"
msgid "Save config"
msgstr "保存配置"
msgid "Save and Start"
msgstr "保存并启动"
msgid "Saved · applied"
msgstr "已保存并应用"
msgid "Saved"
msgstr "已保存"
msgid "Saved · started"
msgstr "已保存并启动"
msgid "Starting…"
msgstr "启动中…"
msgid "Start failed: %s"
msgstr "启动失败:%s"
@@ -27,6 +27,19 @@ sanitize() {
printf '%s' "$1" | tr -d "'" | tr -d '\n\r'
}
# dae subscription/node tags are bareword identifiers: only [A-Za-z0-9_] are
# valid tokens, so a CJK/spaced tag (e.g. 白月光) makes the parser choke. Map
# any such tag to a stable ASCII slug; pure-word tags pass through unchanged.
# Deterministic on the input, so a group's source reference resolves to the
# same slug as the subscription it points at. Empty stays empty (anonymous).
dae_tag() {
[ -n "$1" ] || return 0
case "$1" in
*[!A-Za-z0-9_]*) printf 's_%s' "$(printf '%s' "$1" | md5sum | cut -c1-10)" ;;
*) printf '%s' "$1" ;;
esac
}
# ===================== generate =====================
SUB_BUF=""
@@ -42,7 +55,7 @@ emit_sub() {
[ "$enabled" = "1" ] || return 0
url="$(sanitize "$url")"
[ -n "$url" ] || return 0
tag="$(sanitize "$tag")"
tag="$(dae_tag "$(sanitize "$tag")")"
if [ -n "$tag" ]; then
SUB_BUF="${SUB_BUF} ${tag}: '${url}'
"
@@ -60,7 +73,7 @@ emit_node() {
[ "$enabled" = "1" ] || return 0
link="$(sanitize "$link")"
[ -n "$link" ] || return 0
tag="$(sanitize "$tag")"
tag="$(dae_tag "$(sanitize "$tag")")"
if [ -n "$tag" ]; then
NODE_BUF="${NODE_BUF} ${tag}: '${link}'
"
@@ -125,7 +138,7 @@ KW_ACC=""
SUB_TAGS=""
collect_subtag() {
local t; config_get t "$1" tag ""
t="$(sanitize "$t")"
t="$(dae_tag "$(sanitize "$t")")"
[ -n "$t" ] && SUB_TAGS="${SUB_TAGS} ${t} "
}
is_subtag() {
@@ -153,8 +166,11 @@ kw_arg() {
_collect_source() {
local v="$(sanitize "$1")"
[ -n "$v" ] || return 0
if is_subtag "$v"; then
SUBS_ACC="${SUBS_ACC:+$SUBS_ACC, }$(filter_arg "$v")"
# subscription refs resolve to the (ASCII) dae tag; node-name refs keep the
# original text (matched as a quoted regex against node names)
local dt="$(dae_tag "$v")"
if is_subtag "$dt"; then
SUBS_ACC="${SUBS_ACC:+$SUBS_ACC, }$(filter_arg "$dt")"
else
NODES_ACC="${NODES_ACC:+$NODES_ACC, }$(filter_arg "$v")"
fi
@@ -19,8 +19,8 @@
"/tmp/luci-app-daede.pkg-dae.log": [ "read" ],
"/tmp/luci-app-daede.pkg-daed.log": [ "read" ],
"/tmp/luci-app-daede.pkg-luci-app-daede.log": [ "read" ],
"/usr/bin/pgrep -x dae": [ "exec" ],
"/usr/bin/pgrep -x daed": [ "exec" ],
"/bin/pidof dae": [ "exec" ],
"/bin/pidof daed": [ "exec" ],
"/usr/bin/dae validate -c /tmp/dae-validate.dae": [ "exec" ],
"/usr/bin/dae --version": [ "exec" ],
"/bin/rm -f /tmp/dae-validate.dae": [ "exec" ],
+3 -2
View File
@@ -1,17 +1,18 @@
# Copyright (C) 2024 Openwrt.org
#
# This is free software, licensed under the Apache License, Version 2.0 .
# This is free software, licensed under the GNU Affero General Public License v3.0 only.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-gecoosac
PKG_VERSION:=2.2
PKG_RELEASE:=6
PKG_RELEASE:=7
LUCI_TITLE:=LuCI Support for gecoosac
LUCI_DEPENDS:=+luci-compat +gecoosac
LUCI_PKGARCH:=all
PKG_LICENSE:=AGPL-3.0-only
include $(TOPDIR)/feeds/luci/luci.mk
@@ -4,6 +4,8 @@ local fs = require "nixio.fs"
local sys = require "luci.sys"
local uci = require "luci.model.uci"
local ACL_DEPENDS = { "luci-app-gecoosac" }
local function service_running()
if not fs.access("/etc/init.d/gecoosac") then
return false
@@ -19,15 +21,18 @@ function index()
local page
page = entry({"admin", "services", "gecoosac"}, cbi("gecoosac"), _("Gecoos AC"), 100)
page.dependent = true
page.acl_depends = ACL_DEPENDS
page = entry({"admin", "services", "gecoosac", "status"}, call("act_status"))
page.leaf = true
page.acl_depends = ACL_DEPENDS
end
function act_status()
local cur = uci.cursor()
local enabled = cur:get("gecoosac", "config", "enabled") == "1"
local e = {
running = enabled and service_running()
enabled = enabled,
running = service_running()
}
luci.http.prepare_content("application/json")
luci.http.write_json(e)
+31 -12
View File
@@ -31,22 +31,34 @@ end
local function remove_tree(path)
local stat = fs.lstat and fs.lstat(path) or fs.stat(path)
if not stat then
return
return true
end
if stat.type == "dir" then
local iter = fs.dir(path)
if iter then
for entry in iter do
if entry ~= "." and entry ~= ".." then
remove_tree(path .. "/" .. entry)
if not iter then
return nil, translatef("Unable to read %s", path)
end
for entry in iter do
if entry ~= "." and entry ~= ".." then
local ok, err = remove_tree(path .. "/" .. entry)
if not ok then
return nil, err
end
end
end
fs.rmdir(path)
if not fs.rmdir(path) and fs.stat(path, "type") == "dir" then
return nil, translatef("Unable to remove %s", path)
end
else
fs.unlink(path)
if not fs.unlink(path) and (fs.lstat and fs.lstat(path) or fs.stat(path)) then
return nil, translatef("Unable to remove %s", path)
end
end
return true
end
local function clear_upload_dir(path)
@@ -71,10 +83,15 @@ local function clear_upload_dir(path)
end
local iter = fs.dir(real)
if iter then
for entry in iter do
if entry ~= "." and entry ~= ".." then
remove_tree(real .. "/" .. entry)
if not iter then
return nil, translatef("Unable to read %s", real)
end
for entry in iter do
if entry ~= "." and entry ~= ".." then
local ok, err = remove_tree(real .. "/" .. entry)
if not ok then
return nil, err
end
end
end
@@ -165,13 +182,15 @@ log = s:option(Flag, "log", translate("Enable Log"))
log.default = 0
log.rmempty = false
clear_upload = s:option(Button, "clear_upload", translate("Clear Upload Directory"))
clear_upload = s:option(Button, "clear_upload", translate("Clear Upload Directory"), translate("Only files under the configured Gecoos upload directory will be removed."))
clear_upload.inputstyle = "remove"
clear_upload.write = function(self, section)
local path = upload_dir:formvalue(section) or upload_dir:cfgvalue(section) or DEFAULT_UPLOAD_DIR
local ok, err = clear_upload_dir(path)
if not ok then
self.map.message = err or translate("Upload directory was not cleared")
else
self.map.message = translate("Upload directory cleared")
end
end
+21 -4
View File
@@ -1,5 +1,13 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgstr ""
"Project-Id-Version: luci-app-gecoosac\n"
"PO-Revision-Date: 2026-06-12 00:00+0800\n"
"Last-Translator: Automatically generated\n"
"Language-Team: Chinese\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Default certificate files are generated when HTTPS starts; custom paths must point to readable files."
msgstr "默认证书文件会在 HTTPS 启动时生成;自定义路径必须指向可读取的文件。"
@@ -37,9 +45,6 @@ msgstr "设置管理端口"
msgid "Enable HTTPS service"
msgstr "开启 HTTPS 服务"
msgid "A certificate file must be specified, otherwise it will fail to start."
msgstr "需要指定证书文件,否则开启失败。"
msgid "Specify crt certificate file"
msgstr "指定 crt 证书文件"
@@ -100,6 +105,9 @@ msgstr "收集数据..."
msgid "Clear Upload Directory"
msgstr "清理上传目录"
msgid "Only files under the configured Gecoos upload directory will be removed."
msgstr "只会删除已配置的集客 AC 上传目录中的文件。"
msgid "Expecting an absolute path"
msgstr "请输入绝对路径"
@@ -108,3 +116,12 @@ msgstr "只能清理集客 AC 上传目录"
msgid "Upload directory was not cleared"
msgstr "上传目录未清理"
msgid "Upload directory cleared"
msgstr "上传目录已清理"
msgid "Unable to read %s"
msgstr "无法读取 %s"
msgid "Unable to remove %s"
msgstr "无法删除 %s"
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 12a7.5 7.5 0 0 0 15 0m-15 0a7.5 7.5 0 1 1 15 0m-15 0H3m16.5 0H21m-1.5 0H12m-8.457 3.077l1.41-.513m14.095-5.13l1.41-.513M5.106 17.785l1.15-.964m11.49-9.642l1.149-.964M7.501 19.795l.75-1.3m7.5-12.99l.75-1.3m-6.063 16.658l.26-1.477m2.605-14.772l.26-1.477m0 17.726l-.26-1.477M10.698 4.614l-.26-1.477M16.5 19.794l-.75-1.299M7.5 4.205L12 12m6.894 5.785l-1.149-.964M6.256 7.178l-1.15-.964m15.352 8.864l-1.41-.513M4.954 9.435l-1.41-.514M12.002 12l-3.75 6.495"/></svg>

After

Width:  |  Height:  |  Size: 652 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-15 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Zm.75-12h9v9h-9v-9Z"/></svg>

After

Width:  |  Height:  |  Size: 468 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9"/></svg>

After

Width:  |  Height:  |  Size: 280 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21.75 17.25v-.228a4.5 4.5 0 0 0-.12-1.03l-2.268-9.64a3.375 3.375 0 0 0-3.285-2.602H7.923a3.375 3.375 0 0 0-3.285 2.602l-2.268 9.64a4.5 4.5 0 0 0-.12 1.03v.228m19.5 0a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3m19.5 0a3 3 0 0 0-3-3H5.25a3 3 0 0 0-3 3m16.5 0h.008v.008h-.008v-.008Zm-3 0h.008v.008h-.008v-.008Z"/></svg>

After

Width:  |  Height:  |  Size: 494 B

+84 -13
View File
@@ -7,11 +7,90 @@ header {
[data-nav-type="mega-menu"] & {
& .desktop-menu-container {
@apply bg-header-bg/40 dark:bg-header-bg/90 pointer-events-none absolute inset-x-0 top-0 z-30 h-(--mega-menu-height,0) w-full overflow-hidden opacity-0 backdrop-blur-xl transition-[clip-path,opacity] duration-200 ease-out [clip-path:inset(0_0_100%_0)] [will-change:clip-path,opacity] max-md:hidden;
@apply bg-header-bg/40 dark:bg-header-bg/90 pointer-events-none absolute inset-x-0 top-0 z-30 h-(--mega-menu-height,0) w-full overflow-hidden opacity-0 backdrop-blur-xl transition-[clip-path,opacity] duration-200 ease-out [will-change:clip-path,opacity] [clip-path:inset(0_0_100%_0)] max-md:hidden;
&.active {
@apply pointer-events-auto opacity-100 [clip-path:inset(0)];
}
/* Category panels live inside the container, not the nav items: the
transformed #topmenu would otherwise become the containing block of
a position:fixed panel and shrink the canvas to the menu's width.
Fixed coordinates shared by every category, so switching panels
never shifts anything: link columns start at 50% - 13rem (just
under the centered nav triggers), the anchor column sits at a
fixed slot left of them. The entering panel floats up 6px —
transform + opacity only. */
& .desktop-nav {
@apply pointer-events-none absolute inset-x-0 top-14 min-h-60 translate-y-1.5 pt-7 pb-8 pl-[calc(50%-13rem)] opacity-0 transition-[opacity,transform] duration-200 ease-out;
&.active {
@apply pointer-events-auto translate-y-0 opacity-100;
}
& .desktop-nav-anchor {
@apply absolute top-7 left-[calc(50%-30rem)] w-56 max-lg:hidden;
& .desktop-nav-title {
@apply text-foreground text-2xl font-semibold tracking-tight;
}
}
& .desktop-nav-list {
/* Vertical fill: columns of equal height, no orphan rows. Row
count is set per submenu via --menu-rows in menu-aurora.js.
w-max is load-bearing: without it the block-level list fills
the viewport and justify-content:normal stretches the auto
column tracks across all of it. */
@apply grid w-max grid-flow-col grid-rows-[repeat(var(--menu-rows,6),auto)] content-start gap-x-8 gap-y-1;
& > li {
@apply m-0 list-none;
& > a {
@apply text-foreground block w-fit py-2.5 text-sm whitespace-nowrap no-underline hover:underline hover:decoration-2 hover:underline-offset-4;
}
}
}
}
/* Constant part of the canvas, Apple-flyout style: the device
summary lives once at container level, pinned just below the
fixed category-title slot (header 3.5rem + panel pt 1.75rem +
title line 2rem + 1.25rem gap = top-34). Its position never
depends on an individual panel's height, and switching categories
never re-fades it. */
& .desktop-menu-board {
@apply absolute top-34 left-[calc(50%-30rem)] flex w-56 flex-col gap-2 max-lg:hidden;
& .board-line {
@apply text-muted-foreground/80 flex max-w-full items-center gap-2.5 text-xs;
&::before {
@apply size-4 shrink-0 bg-current content-[''];
}
& .board-text {
@apply min-w-0 truncate;
}
}
& .board-line-host::before {
@apply [mask:url('@assets/icons/server.svg')_center/contain_no-repeat];
}
& .board-line-model::before {
@apply [mask:url('@assets/icons/cpu-chip.svg')_center/contain_no-repeat];
}
& .board-line-firmware::before {
@apply [mask:url('@assets/icons/cube.svg')_center/contain_no-repeat];
}
& .board-line-kernel::before {
@apply [mask:url('@assets/icons/cog.svg')_center/contain_no-repeat];
}
}
}
}
@@ -39,11 +118,10 @@ header {
}
}
/* Boxed-dropdown panels only — mega-menu panels are reparented into
.desktop-menu-container (see above). */
& .desktop-nav {
@apply pointer-events-none left-0 z-40 px-0 pt-0 opacity-0 transition-opacity duration-150;
[data-nav-type="mega-menu"] & {
@apply fixed top-14 left-1/2 -translate-x-1/2 pb-6;
}
[data-nav-type="boxed-dropdown"] & {
@apply absolute top-full mt-2 min-w-48;
}
@@ -52,10 +130,6 @@ header {
}
& .desktop-nav-list {
[data-nav-type="mega-menu"] & {
@apply grid w-max grid-cols-4 gap-x-4 gap-y-2;
}
[data-nav-type="boxed-dropdown"] & {
@apply border-border bg-header-bg/90 flex flex-col gap-y-1 rounded-3xl border py-2 shadow-lg backdrop-blur-md backdrop-saturate-150;
}
@@ -63,13 +137,10 @@ header {
@apply m-0 list-none;
& > a {
@apply text-foreground block px-4 whitespace-nowrap no-underline;
@apply text-foreground block whitespace-nowrap no-underline;
[data-nav-type="mega-menu"] & {
@apply py-3 hover:underline hover:decoration-2 hover:underline-offset-4;
}
[data-nav-type="boxed-dropdown"] & {
@apply hover:bg-header-interactive mx-2 rounded-xl py-2;
@apply hover:bg-header-interactive mx-2 rounded-xl px-4 py-2;
}
}
}
@@ -514,9 +514,24 @@ return baseclass.extend({
let nav = null;
if (hasSubmenu) {
nav = E("div", { class: "desktop-nav" }, [
this.renderMainMenu(child, `${url}/${child.name}`, 1),
]);
const list = this.renderMainMenu(child, `${url}/${child.name}`, 1);
const children = [list];
if (document.body?.dataset?.navType === "mega-menu") {
// Constant canvas: links fill top-to-bottom; capping at 4 columns
// keeps the canvas height stable until a submenu exceeds 24 items.
list.style.setProperty(
"--menu-rows",
Math.max(6, Math.ceil(submenu.length / 4)),
);
children.unshift(
E("div", { class: "desktop-nav-anchor" }, [
E("span", { class: "desktop-nav-title" }, [_(child.title)]),
]),
);
}
nav = E("div", { class: "desktop-nav" }, children);
li.appendChild(nav);
menuLink.addEventListener("click", (e) => e.preventDefault());
}
@@ -545,6 +560,33 @@ return baseclass.extend({
let showTimer = null;
let hideTimer = null;
// Constant canvas: every category opens at the same height — the
// tallest submenu wins. Measured lazily on first open (after fonts
// settle) and cached, so switching categories is a pure cross-fade
// with zero layout work. A resize invalidates the cache.
let canvasHeight = 0;
const applyCanvasHeight = () => {
if (!canvasHeight) {
const headerHeight =
header.querySelector(".header-content")?.offsetHeight || 56;
let maxPanel = 0;
header
.querySelectorAll(".desktop-nav")
.forEach((nav) => (maxPanel = Math.max(maxPanel, nav.scrollHeight)));
canvasHeight = headerHeight + maxPanel;
}
container?.style.setProperty("--mega-menu-height", `${canvasHeight}px`);
};
let resizeTimer = null;
window.addEventListener("resize", () => {
canvasHeight = 0;
if (!container?.classList.contains("active")) return;
clearTimeout(resizeTimer);
resizeTimer = setTimeout(applyCanvasHeight, 150);
});
children.forEach((child) => {
const { li, nav, menuLink, hasSubmenu } = this.buildDropdownItem(
child,
@@ -553,6 +595,13 @@ return baseclass.extend({
);
if (!hasSubmenu) return;
// Reparent the panel into the canvas container: the transformed
// #topmenu would otherwise become the containing block of the
// absolutely-positioned panel and shrink the canvas to the menu's
// width. Inside the container the panel is also clipped by the same
// clip-path reveal as the rest of the canvas.
if (container) container.appendChild(nav);
li.addEventListener("mouseenter", () => {
if (hideTimer) {
clearTimeout(hideTimer);
@@ -567,16 +616,9 @@ return baseclass.extend({
if (wasActive) return;
menuLink.classList.add("menu-active");
const navHeight = nav.scrollHeight;
const headerHeight =
header.querySelector(".header-content")?.offsetHeight || 56;
const totalHeight = headerHeight + navHeight;
if (container) {
container.style.setProperty(
"--mega-menu-height",
`${totalHeight}px`,
);
applyCanvasHeight();
container.classList.add("active");
overlay.classList.add("active");
}
@@ -668,10 +710,10 @@ return baseclass.extend({
const wasActive = container.classList.contains("active");
container.classList.remove("active");
// --mega-menu-height is set per-submenu in initMegaMenu and otherwise
// never cleared. Drop it back to the h-0 fallback once the container is
// fully hidden, so a closed menu doesn't leave invisible scrollable
// space below the header on pages shorter than the last submenu.
// --mega-menu-height is set on open in initMegaMenu and otherwise never
// cleared. Drop it back to the h-0 fallback once the container is fully
// hidden, so a closed menu doesn't leave invisible scrollable space
// below the header on pages shorter than the canvas.
const resetHeight = () => {
if (!container.classList.contains("active")) {
container.style.removeProperty("--mega-menu-height");
+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:=0.12.6
PKG_RELEASE:=24
PKG_VERSION:=0.12.7
PKG_RELEASE:=25
PKG_LICENSE:=Apache-2.0
LUCI_MINIFY_CSS:=
@@ -1,2 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-content:"";--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%;--tw-font-weight:initial;--tw-tracking:initial}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--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;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkitLine truncated
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-content:"";--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%;--tw-font-weight:initial;--tw-tracking:initial}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--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;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkitLine truncated
@@ -1,2 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:"";--tw-space-y-reverse:0;--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%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--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-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-2xl:calc(var(--radius-base)*2);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}Line truncated
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-content:"";--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--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%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--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-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-2xl:calc(var(--radius-base)*2);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}Line truncated
@@ -1 +1 @@
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const resetMobileSubmenus=()=>{document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(item=>item.classList.remove("submenu-expanded"))};const expandActiveMobileGroup=()=>{document.querySelector(".mobile-nav-item.has-active")?.classList.add("submenu-expanded")};const updateToggleState=expanded=>{navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.style.overflow="";resetMobileSubmenus()};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)expandActiveMobileGroup();else resetMobileSubmenus();updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const link=e.target.closest(".mobile-nav-link");if(!link)return;const item=link.closest(".mobile-nav-item");const submenu=item?.querySelector(".mobile-nav-submenu");if(submenu){e.preventDefault();e.stopPropagation();const isExpanded=item.classList.contains("submenu-expanded");document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(i=>{if(i!==item)i.classList.remove("submenu-expanded")});item.classList.toggle("submenu-expanded",!isExpanded)}})},renderMobileMenu(tree,url){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");const children=ui.menu.getChildren(tree);if(!list||!footerAction||!children.length)return;list.innerHTML="";footerAction.innerHTML="";children.forEach(child=>{const submenu=ui.menu.getChildren(child);const hasSubmenu=submenu.length>0;const isActive=this.isActivePath(child.name);if(child.name==="logout"){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:L.url(url,child.name)},[_(child.title)]));return}const itemClasses=["mobile-nav-item",hasSubmenu?"has-submenu":"",isActive&&hasSubmenu?"has-active":""].filter(Boolean);const li=E("li",{class:itemClasses.join(" ")},[E("a",{class:`mobile-nav-link${isActive&&!hasSubmenu?" nav-link-active":""}`,href:hasSubmenu?"#":L.url(url,child.name)},[_(child.title)])]);if(hasSubmenu){li.appendChild(this.buildCategoryPreview(child.name,submenu));const wrap=E("div",{class:"mobile-nav-submenu"});const ul=E("ul",{class:"mobile-nav-submenu-list"});submenu.forEach(item=>{const subActive=this.isActivePath(child.name,item.name);ul.appendChild(E("li",{class:"mobile-nav-subitem"},[E("a",{class:`mobile-nav-sublink${subActive?" nav-link-active":""}`,href:L.url(url,child.name,item.name)},[_(item.title)])]))});wrap.appendChild(ul);li.appendChild(wrap)}list.appendChild(li)})},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,Line truncated
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const resetMobileSubmenus=()=>{document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(item=>item.classList.remove("submenu-expanded"))};const expandActiveMobileGroup=()=>{document.querySelector(".mobile-nav-item.has-active")?.classList.add("submenu-expanded")};const updateToggleState=expanded=>{navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.style.overflow="";resetMobileSubmenus()};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)expandActiveMobileGroup();else resetMobileSubmenus();updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const link=e.target.closest(".mobile-nav-link");if(!link)return;const item=link.closest(".mobile-nav-item");const submenu=item?.querySelector(".mobile-nav-submenu");if(submenu){e.preventDefault();e.stopPropagation();const isExpanded=item.classList.contains("submenu-expanded");document.querySelectorAll(".mobile-nav-item.submenu-expanded").forEach(i=>{if(i!==item)i.classList.remove("submenu-expanded")});item.classList.toggle("submenu-expanded",!isExpanded)}})},renderMobileMenu(tree,url){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");const children=ui.menu.getChildren(tree);if(!list||!footerAction||!children.length)return;list.innerHTML="";footerAction.innerHTML="";children.forEach(child=>{const submenu=ui.menu.getChildren(child);const hasSubmenu=submenu.length>0;const isActive=this.isActivePath(child.name);if(child.name==="logout"){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:L.url(url,child.name)},[_(child.title)]));return}const itemClasses=["mobile-nav-item",hasSubmenu?"has-submenu":"",isActive&&hasSubmenu?"has-active":""].filter(Boolean);const li=E("li",{class:itemClasses.join(" ")},[E("a",{class:`mobile-nav-link${isActive&&!hasSubmenu?" nav-link-active":""}`,href:hasSubmenu?"#":L.url(url,child.name)},[_(child.title)])]);if(hasSubmenu){li.appendChild(this.buildCategoryPreview(child.name,submenu));const wrap=E("div",{class:"mobile-nav-submenu"});const ul=E("ul",{class:"mobile-nav-submenu-list"});submenu.forEach(item=>{const subActive=this.isActivePath(child.name,item.name);ul.appendChild(E("li",{class:"mobile-nav-subitem"},[E("a",{class:`mobile-nav-sublink${subActive?" nav-link-active":""}`,href:L.url(url,child.name,item.name)},[_(item.title)])]))});wrap.appendChild(ul);li.appendChild(wrap)}list.appendChild(li)})},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,Line truncated
@@ -202,7 +202,28 @@
</div>
{% if (nav_submenu_type == 'mega-menu'): %}
<div class="desktop-menu-container"></div>
{%
/* boardinfo fields are not guaranteed on every platform; the
summary tolerates a missing model/firmware line (hostname is
always set by the kernel). */
const board_model = boardinfo.model ?? boardinfo.system ?? boardinfo.board_name;
const board_firmware = boardinfo.release?.description ??
trim(`${boardinfo.release?.distribution ?? ''} ${boardinfo.release?.version ?? ''}`);
-%}
<div class="desktop-menu-container">
<div class="desktop-menu-board" aria-hidden="true">
<span class="board-line board-line-host" title="{{ entityencode(striptags(boardinfo.hostname ?? '?'), true) }}"><span class="board-text">{{ striptags(boardinfo.hostname ?? '?') }}</span></span>
{% if (board_model): %}
<span class="board-line board-line-model" title="{{ entityencode(striptags(board_model), true) }}"><span class="board-text">{{ striptags(board_model) }}</span></span>
{% endif %}
{% if (board_firmware): %}
<span class="board-line board-line-firmware" title="{{ entityencode(striptags(board_firmware), true) }}"><span class="board-text">{{ striptags(board_firmware) }}</span></span>
{% endif %}
{% if (boardinfo.kernel): %}
<span class="board-line board-line-kernel" title="{{ entityencode(striptags(boardinfo.kernel), true) }}"><span class="board-text">{{ striptags(boardinfo.kernel) }}</span></span>
{% endif %}
</div>
</div>
</header>
<div class="desktop-menu-overlay"></div>
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=opera-proxy
PKG_VERSION:=1.23.0
PKG_RELEASE:=2
PKG_VERSION:=1.24.0
PKG_RELEASE:=3
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
PKG_LICENSE:=MIT