🎄 Sync 2026-07-26 03:59:09

This commit is contained in:
github-actions[bot] committed 2026-07-26 03:59:09 +08:00
1 parent 85de1d9922
commit 6efc8c18ce
68 files changed
+1556 -9154

No files matched your search

@@ -68,7 +68,7 @@ return view.extend({
o = s.option(form.ListValue, 'fullcone', _('Enable FullCone NAT')); o = s.option(form.ListValue, 'fullcone', _('Enable FullCone NAT'));
o.value('0', _('Disable')); o.value('0', _('Disable'));
o.value('1', _('FULLCONENAT')); o.value('1', _('FULLCONENAT'));
o.value('2', _('Boardcom Fullcone NAT1')); o.value('2', _('Broadcom Fullcone NAT1'));
addTCPCCAOption(); addTCPCCAOption();
if (fw4) if (fw4)
o = s.option(form.Flag, 'fullcone6', _('Enable FullCone NAT6')); o = s.option(form.Flag, 'fullcone6', _('Enable FullCone NAT6'));
-1
View File
@@ -1 +0,0 @@
.DS_Store
-340
View File
@@ -1,340 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
+1 -4
View File
@@ -1,19 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
# #
# Copyright (C) 2022-2023 ImmortalWrt.org # Copyright (C) 2022-2024 ImmortalWrt.org
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
LUCI_TITLE:=The modern ImmortalWrt proxy platform for ARM64/AMD64 LUCI_TITLE:=The modern ImmortalWrt proxy platform for ARM64/AMD64
LUCI_PKGARCH:=all
LUCI_DEPENDS:= \ LUCI_DEPENDS:= \
+sing-box \ +sing-box \
+firewall4 \ +firewall4 \
+kmod-nft-tproxy \ +kmod-nft-tproxy \
+ucode-mod-digest +ucode-mod-digest
PKG_NAME:=luci-app-homeproxy
define Package/luci-app-homeproxy/conffiles define Package/luci-app-homeproxy/conffiles
/etc/config/homeproxy /etc/config/homeproxy
/etc/homeproxy/certs/ /etc/homeproxy/certs/
-5
View File
@@ -1,5 +0,0 @@
TODO:
- Subscription page slow response with a large number of nodes
- Refactor nft rules
- Move ACL settings to a dedicated page
- Any other improvements
@@ -1543,87 +1543,6 @@ return view.extend({
/* Direct domain list end */ /* Direct domain list end */
/* ACL settings end */ /* ACL settings end */
/* Dashboard settings start */
s.tab('dashboard', _('Dashboard Settings'));
o = s.taboption('dashboard', form.Flag, 'dashboard_enabled', _('Enable Dashboard'),
_('Enable sing-box API and dashboard.<br/><strong style="color:red">Warning: The API will be accessible to all devices on the LAN.</strong>'));
o.default = o.disabled;
o.rmempty = false;
o = s.taboption('dashboard', form.Value, 'dashboard_secret', _('Dashboard Secret'),
_('API authentication secret. Required for security since the API is LAN-accessible.'));
o.password = true;
o.depends('dashboard_enabled', '1');
o.rmempty = false;
o.validate = function(section_id, value) {
let enabled = this.section.formvalue(section_id, 'dashboard_enabled');
if (enabled === '1') {
if (!value || value.length < 6)
return _('Dashboard secret must be at least 6 characters.');
}
return true;
};
o = s.taboption('dashboard', form.Value, 'dashboard_port', _('Dashboard Port'),
_('The listen port for sing-box API and dashboard.'));
o.datatype = 'port';
o.placeholder = '9090';
o.depends('dashboard_enabled', '1');
o = s.taboption('dashboard', form.Value, 'dashboard_url', _('Dashboard URL'),
_('URL to download dashboard files. Leave empty to use Zashboard by default.'));
o.placeholder = 'https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn.zip';
o.depends('dashboard_enabled', '1');
o = s.taboption('dashboard', form.ListValue, 'dashboard_detour', _('Download outbound'),
_('Outbound used to download dashboard files.'));
o.load = function(section_id) {
delete this.keylist;
delete this.vallist;
this.value('direct-out', _('Direct'));
let routing_mode = uci.get('homeproxy', 'config', 'routing_mode');
if (routing_mode !== 'custom') {
let main_node = uci.get('homeproxy', 'config', 'main_node');
if (main_node && main_node !== 'nil')
this.value('main-out', _('Main node (proxy)'));
} else {
uci.sections(data[0], 'routing_node', (res) => {
if (res.enabled === '1')
this.value(res['.name'], res.label);
});
}
return this.super('load', section_id);
};
o.default = 'direct-out';
o.depends('dashboard_enabled', '1');
o = s.taboption('dashboard', form.DummyValue, '_dashboard_open', _('Open Dashboard'));
o.depends('dashboard_enabled', '1');
o.rawhtml = true;
o.cfgvalue = function(section_id) {
let port = uci.get('homeproxy', 'config', 'dashboard_port') || '9090';
let host = window.location.hostname;
let url = 'http://' + host + ':' + port + '/ui/';
return E('div', {}, [
E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': function() {
window.open(url, '_blank');
}
}, [ _('Open Dashboard') ]),
E('div', { 'style': 'margin-top:8px; color:#888; font-size:90%' }, [
E('div', {}, _('Dashboard URL: %s').format(url)),
E('div', { 'style': 'margin-top:4px' },
_('If the dashboard asks for connection settings, fill in: Host = <code>%s</code>, Port = <code>%s</code>, Secret = your configured secret above.').format(host, port)
)
])
]);
};
/* Dashboard settings end */
return m.render(); return m.render();
} }
}); });
@@ -47,7 +47,7 @@ function getConnStat(o, site) {
let ele = o.default.firstElementChild.nextElementSibling; let ele = o.default.firstElementChild.nextElementSibling;
if (ret.result) { if (ret.result) {
ele.style.setProperty('color', 'green'); ele.style.setProperty('color', 'green');
ele.innerHTML = ret.elapsed ? _('passed (%dms)').format(ret.elapsed) : _('passed'); ele.innerHTML = _('passed');
} else { } else {
ele.style.setProperty('color', 'red'); ele.style.setProperty('color', 'red');
ele.innerHTML = _('failed'); ele.innerHTML = _('failed');
@@ -238,24 +238,6 @@ return view.extend({
o = s.option(form.DummyValue, '_check_google', _('Google')); o = s.option(form.DummyValue, '_check_google', _('Google'));
o.cfgvalue = L.bind(getConnStat, this, o, 'google'); o.cfgvalue = L.bind(getConnStat, this, o, 'google');
if (uci.get('homeproxy', 'config', 'dashboard_enabled') === '1') {
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Dashboard'));
s.anonymous = true;
o = s.option(form.DummyValue, '_dashboard_btn', _('Zashboard'));
o.rawhtml = true;
o.cfgvalue = function(section_id) {
let port = uci.get('homeproxy', 'config', 'dashboard_port') || '9090';
return E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': function() {
let url = window.location.protocol + '//' + window.location.hostname + ':' + port + '/ui/';
window.open(url, '_blank');
}
}, [ _('Open Dashboard') ]);
};
}
s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management')); s = m.section(form.NamedSection, 'config', 'homeproxy', _('Resources management'));
s.anonymous = true; s.anonymous = true;
@@ -2958,9 +2958,6 @@ msgstr ""
msgid "passed" msgid "passed"
msgstr "" msgstr ""
msgid "passed (%dms)"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/server.js:791 #: htdocs/luci-static/resources/view/homeproxy/server.js:791
msgid "private key" msgid "private key"
msgstr "" msgstr ""
@@ -3052,57 +3049,3 @@ msgstr ""
#: htdocs/luci-static/resources/homeproxy.js:328 #: htdocs/luci-static/resources/homeproxy.js:328
msgid "valid uuid" msgid "valid uuid"
msgstr "" msgstr ""
msgid "Dashboard Settings"
msgstr ""
msgid "Enable Dashboard"
msgstr ""
msgid "Enable sing-box API and dashboard.<br/><strong style=\"color:red\">Warning: The API will be accessible to all devices on the LAN.</strong>"
msgstr ""
msgid "Open Dashboard"
msgstr ""
msgid "Dashboard Secret"
msgstr ""
msgid "API authentication secret. Required for security since the API is LAN-accessible."
msgstr ""
msgid "Dashboard secret must be at least 6 characters."
msgstr ""
msgid "Dashboard Port"
msgstr ""
msgid "The listen port for sing-box API and dashboard."
msgstr ""
msgid "Dashboard URL"
msgstr ""
msgid "URL to download dashboard files. Leave empty to use Zashboard by default."
msgstr ""
msgid "Download outbound"
msgstr ""
msgid "Outbound used to download dashboard files."
msgstr ""
msgid "Main node (proxy)"
msgstr ""
msgid "Dashboard"
msgstr ""
msgid "Zashboard"
msgstr ""
msgid "Dashboard URL: %s"
msgstr ""
msgid "If the dashboard asks for connection settings, fill in: Host = <code>%s</code>, Port = <code>%s</code>, Secret = your configured secret above."
msgstr ""
@@ -3027,9 +3027,6 @@ msgstr "packet addr (v2ray-core v5+)"
msgid "passed" msgid "passed"
msgstr "通过" msgstr "通过"
msgid "passed (%dms)"
msgstr "通过(%dms)"
#: htdocs/luci-static/resources/view/homeproxy/server.js:791 #: htdocs/luci-static/resources/view/homeproxy/server.js:791
msgid "private key" msgid "private key"
msgstr "私钥" msgstr "私钥"
@@ -3122,57 +3119,3 @@ msgstr "有效端口值"
#: htdocs/luci-static/resources/homeproxy.js:328 #: htdocs/luci-static/resources/homeproxy.js:328
msgid "valid uuid" msgid "valid uuid"
msgstr "有效 uuid" msgstr "有效 uuid"
msgid "Dashboard Settings"
msgstr "面板设置"
msgid "Enable Dashboard"
msgstr "启用面板"
msgid "Enable sing-box API and dashboard.<br/><strong style=\"color:red\">Warning: The API will be accessible to all devices on the LAN.</strong>"
msgstr "启用 sing-box API 和面板。<br/><strong style=\"color:red\">警告:API 将对局域网内所有设备开放访问。</strong>"
msgid "Open Dashboard"
msgstr "打开面板"
msgid "Dashboard Secret"
msgstr "面板密钥"
msgid "API authentication secret. Required for security since the API is LAN-accessible."
msgstr "API 认证密钥。由于 API 可被局域网访问,必须设置此密钥以确保安全。"
msgid "Dashboard secret must be at least 6 characters."
msgstr "面板密钥长度不能少于 6 位。"
msgid "Dashboard Port"
msgstr "面板端口"
msgid "The listen port for sing-box API and dashboard."
msgstr "sing-box API 和面板的监听端口。"
msgid "Dashboard URL"
msgstr "面板下载地址"
msgid "URL to download dashboard files. Leave empty to use Zashboard by default."
msgstr "面板文件的下载地址。留空则默认使用 Zashboard。"
msgid "Download outbound"
msgstr "下载出站"
msgid "Outbound used to download dashboard files."
msgstr "用于下载面板文件的出站节点。"
msgid "Main node (proxy)"
msgstr "主节点(代理)"
msgid "Dashboard"
msgstr "面板"
msgid "Zashboard"
msgstr "Zashboard"
msgid "Dashboard URL: %s"
msgstr "面板地址:%s"
msgid "If the dashboard asks for connection settings, fill in: Host = <code>%s</code>, Port = <code>%s</code>, Secret = your configured secret above."
msgstr "若面板要求填写连接设置,请填入:Host(主机)= <code>%s</code>,Port(端口)= <code>%s</code>,Secret(密钥)= 上方配置的面板密钥。"
@@ -33,9 +33,6 @@ config homeproxy 'config'
option ipv6_support '1' option ipv6_support '1'
option github_token '' option github_token ''
option log_level 'warn' option log_level 'warn'
option dashboard_enabled '0'
option dashboard_port '9090'
option dashboard_secret ''
config homeproxy 'control' config homeproxy 'control'
option lan_proxy_mode 'disabled' option lan_proxy_mode 'disabled'
File diff suppressed because it is too large. Load diff
@@ -1 +1 @@
20260424052559 20260704060757
@@ -1,12 +1,6 @@
2001:250::/30 2001:250::/30
2001:254::/31 2001:254::/31
2001:256:100::/48 2001:256:100::/48
2001:678:120::/48
2001:678:53c::/48
2001:678:10d0::/48
2001:678:1184::/48
2001:67c:ebc::/48
2001:7fa:5::/48
2001:c68::/32 2001:c68::/32
2001:cc0::/32 2001:cc0::/32
2001:da8::/32 2001:da8::/32
@@ -17,30 +11,16 @@
2001:dc7::/32 2001:dc7::/32
2001:dd8:1::/48 2001:dd8:1::/48
2001:dd9::/48 2001:dd9::/48
2001:df0:ac40::/48
2001:df1:da00::/48
2001:df3:3a80::/48
2001:df3:8b80::/48
2001:df5:4740::/48
2001:df6:40::/48 2001:df6:40::/48
2001:df7:1480::/48
2001:4510:400::/40 2001:4510:400::/40
2400:1160::/32 2400:1160::/32
2400:3200::/32 2400:3200::/32
2400:5280:f803::/48 2400:5280:f803::/48
2400:54a0:21c2::/47
2400:5a00::/32 2400:5a00::/32
2400:5a60:2::/48
2400:5f60::/32
2400:6000::/32 2400:6000::/32
2400:6460::/39
2400:6460:300::/40
2400:6460:500::/40
2400:6460:600::/40
2400:6600::/32 2400:6600::/32
2400:6e60:1301::/48 2400:6e60:1301::/48
2400:75aa::/32 2400:75aa::/32
2400:7bc0:20::/43
2400:7fc0::/40 2400:7fc0::/40
2400:7fc0:220::/44 2400:7fc0:220::/44
2400:7fc0:240::/44 2400:7fc0:240::/44
@@ -56,16 +36,10 @@
2400:8200::/32 2400:8200::/32
2400:89c0:1010::/44 2400:89c0:1010::/44
2400:89c0:1050::/46 2400:89c0:1050::/46
2400:89c0:1130::/44
2400:89c0:1150::/48 2400:89c0:1150::/48
2400:89c0:2100::/48
2400:89c0:2200::/48
2400:89c0:3010::/44
2400:89c0:6000::/48 2400:89c0:6000::/48
2400:89c0:6100::/48 2400:8fc0::/32
2400:8fc0:571::/48
2400:9020:f010::/46 2400:9020:f010::/46
2400:9340::/32
2400:9380:8001::/48 2400:9380:8001::/48
2400:9380:8003::/48 2400:9380:8003::/48
2400:9380:8021::/48 2400:9380:8021::/48
@@ -113,10 +87,9 @@
2400:dd00::/28 2400:dd00::/28
2400:ee00:ffec::/46 2400:ee00:ffec::/46
2400:ee00:fff0::/44 2400:ee00:fff0::/44
2400:f6e0::/32
2400:f720::/32 2400:f720::/32
2400:fe00::/32 2400:fe00::/32
2401:20::/40
2401:20:1330::/46
2401:800::/32 2401:800::/32
2401:1160::/32 2401:1160::/32
2401:1320::/32 2401:1320::/32
@@ -128,20 +101,11 @@
2401:3480:3000::/36 2401:3480:3000::/36
2401:34a0::/31 2401:34a0::/31
2401:3800::/32 2401:3800::/32
2401:70e0::/32 2401:5c20:10::/48
2401:71c0::/48 2401:71c0::/48
2401:7660::/48 2401:7660::/48
2401:8a00:11::/48 2401:7e00::/32
2401:8a00:12::/48
2401:8be0::/48 2401:8be0::/48
2401:8d00::/46
2401:8d00:4::/48
2401:8d00:7::/48
2401:8d00:8::/47
2401:8d00:b::/48
2401:8d00:c::/48
2401:8d00:f::/48
2401:8d00:14::/47
2401:9a00::/44 2401:9a00::/44
2401:9a00:10::/46 2401:9a00:10::/46
2401:a140:1::/48 2401:a140:1::/48
@@ -161,49 +125,26 @@
2401:cc00::/32 2401:cc00::/32
2401:ce00::/32 2401:ce00::/32
2401:d0e0:3::/48 2401:d0e0:3::/48
2401:d180::/46
2401:d180:10::/47
2401:d180:111::/48
2401:d180:2120::/48
2401:d920::/48 2401:d920::/48
2401:de00::/32 2401:de00::/32
2401:ec00::/32 2401:ec00::/32
2401:f860:85::/48 2401:fa00:40::/48
2401:f860:87::/48
2401:f860:88::/47
2401:f860:90::/48
2401:f860:92::/47
2401:f860:94::/48
2401:f860:96::/47
2401:f860:100::/40
2401:fa00:40::/43
2402:840:d000::/46 2402:840:d000::/46
2402:840:e000::/46 2402:840:e000::/46
2402:840:f000::/38
2402:c60::/48 2402:c60::/48
2402:1440::/32 2402:1440::/32
2402:1460::/32
2402:18a0::/32
2402:2000::/32 2402:2000::/32
2402:20e0:f000::/48
2402:3180::/46
2402:3180:8000::/33
2402:33e0::/32
2402:36e0::/44 2402:36e0::/44
2402:3c00::/32
2402:3f80:1400::/40
2402:4b80::/32 2402:4b80::/32
2402:4e00::/32 2402:4e00::/32
2402:5ec0::/32 2402:5ec0::/32
2402:6e80::/32 2402:6e80::/32
2402:6f40::/48 2402:6f40::/48
2402:6f40:2::/48 2402:6f40:2::/47
2402:7d80::/48 2402:7d80::/48
2402:7d80:240::/48 2402:7d80:240::/48
2402:7d80:8888::/48 2402:7d80:8888::/48
2402:8bc0::/32 2402:8bc0::/32
2402:8cc0::/40
2402:8cc0:200::/40
2402:92c0::/48 2402:92c0::/48
2402:93c0::/48 2402:93c0::/48
2402:93c0:20::/48 2402:93c0:20::/48
@@ -216,11 +157,9 @@
2402:b8c0:186::/48 2402:b8c0:186::/48
2402:d340::/32 2402:d340::/32
2402:db40:5100::/48 2402:db40:5100::/48
2402:db40:5900::/48
2402:db40:5f00::/46 2402:db40:5f00::/46
2402:dfc0::/44 2402:dfc0::/44
2402:dfc0:50::/44 2402:dfc0:50::/44
2402:e380:100::/40
2402:e480::/32 2402:e480::/32
2402:e740::/32 2402:e740::/32
2402:e7c0::/32 2402:e7c0::/32
@@ -239,26 +178,18 @@
2403:1ec0:1400::/48 2403:1ec0:1400::/48
2403:1ec0:1600::/48 2403:1ec0:1600::/48
2403:1ec0:1610::/48 2403:1ec0:1610::/48
2403:1ec0:1900::/48
2403:1ec0:1910::/44
2403:1ec0:1920::/43
2403:1ec0:1940::/44
2403:2040::/48 2403:2040::/48
2403:2b40::/32 2403:2b40::/32
2403:3140::/32 2403:3140::/32
2403:4240::/32
2403:4280::/47 2403:4280::/47
2403:4300::/32 2403:4300::/32
2403:4c80::/48 2403:4c80::/48
2403:5c80::/48 2403:5c80::/48
2403:6380:40::/46
2403:6380:60::/44
2403:6740::/48 2403:6740::/48
2403:6a00::/32 2403:6a00::/32
2403:7580::/32 2403:7580::/32
2403:8080:101::/48 2403:8080:101::/48
2403:8c00::/32 2403:8c00::/32
2403:9b00:2000::/48
2403:9b00:2400::/48 2403:9b00:2400::/48
2403:9b00:6000::/48 2403:9b00:6000::/48
2403:a100::/48 2403:a100::/48
@@ -266,8 +197,6 @@
2403:a140:100::/40 2403:a140:100::/40
2403:a200::/32 2403:a200::/32
2403:ac00::/32 2403:ac00::/32
2403:ad80:8008::/48
2403:ad80:8047::/48
2403:b400::/32 2403:b400::/32
2403:c980::/32 2403:c980::/32
2403:d400::/32 2403:d400::/32
@@ -321,9 +250,9 @@
2404:c300::/32 2404:c300::/32
2404:c940::/48 2404:c940::/48
2404:e280::/47 2404:e280::/47
2404:e5c0::/32
2404:e8c0::/32 2404:e8c0::/32
2404:f4c0::/32 2405:80:10::/48
2405:80:13::/48
2405:6c0:2::/48 2405:6c0:2::/48
2405:6c0:4::/48 2405:6c0:4::/48
2405:1480:1000::/48 2405:1480:1000::/48
@@ -332,7 +261,7 @@
2405:3140:11::/48 2405:3140:11::/48
2405:3140:21::/48 2405:3140:21::/48
2405:3140:31::/48 2405:3140:31::/48
2405:66c0::/32 2405:3140:3a::/48
2405:68c0:21::/48 2405:68c0:21::/48
2405:6940::/48 2405:6940::/48
2405:6f00:c101::/48 2405:6f00:c101::/48
@@ -343,84 +272,17 @@
2405:78c0:6e00::/43 2405:78c0:6e00::/43
2405:8a40::/32 2405:8a40::/32
2405:a900:ffee::/48 2405:a900:ffee::/48
2405:a900:fffe::/48
2405:ad00::/32 2405:ad00::/32
2405:b7c0::/32 2405:b7c0::/32
2405:d900::/32 2405:d900::/32
2405:e000::/32 2405:e000::/32
2405:f580::/32
2405:f940::/32 2405:f940::/32
2406:280::/32 2406:280::/32
2406:840:9000::/44 2406:840:9680::/44
2406:840:e080::/44 2406:840:f889::/48
2406:840:e0cf::/48
2406:840:e0e0::/46
2406:840:e0e4::/47
2406:840:e0e8::/48
2406:840:e10f::/48
2406:840:e14f::/48
2406:840:e260::/48
2406:840:e620::/48
2406:840:e666::/47
2406:840:e720::/44
2406:840:e80f::/48
2406:840:eb00::/46
2406:840:eb04::/47
2406:840:eb07::/48
2406:840:eb08::/48
2406:840:eb0b::/48
2406:840:eb0f::/48
2406:840:ee40::/47
2406:840:ee44::/48
2406:840:ee4b::/48
2406:840:ee4d::/48
2406:840:eee5::/48
2406:840:f200::/48
2406:840:f203::/48
2406:840:f380::/44
2406:840:f860::/46
2406:840:f868::/47
2406:840:f86c::/48
2406:840:fc80::/45
2406:840:fc88::/48
2406:840:fc8f::/48
2406:840:fcd0::/48
2406:840:fcf0::/46
2406:840:fcf4::/47
2406:840:fd40::/43
2406:840:fd60::/48
2406:840:fd67::/48
2406:840:fd70::/48
2406:840:fd77::/48
2406:840:fd80::/48
2406:840:fd87::/48
2406:840:fd90::/44
2406:840:fda0::/43
2406:840:fdc0::/44
2406:840:fe27::/48
2406:840:fe90::/46
2406:840:fe94::/48
2406:840:fe96::/47
2406:840:fe98::/46
2406:840:feab::/48
2406:840:feac::/48
2406:840:feaf::/48
2406:840:fec0::/48
2406:840:fec4::/48
2406:840:feca::/48
2406:840:fecc::/47
2406:840:fecf::/48
2406:840:fed8::/48
2406:840:fedf::/48
2406:840:fef3::/48
2406:840:fef9::/48
2406:840:fefb::/48
2406:840:fefc::/48
2406:840:feff::/48
2406:1080::/48 2406:1080::/48
2406:2700::/32 2406:2700::/32
2406:3340::/32 2406:3340::/32
2406:3d80::/32
2406:4d00::/48 2406:4d00::/48
2406:52c0::/32 2406:52c0::/32
2406:5340:6666::/48 2406:5340:6666::/48
@@ -447,6 +309,7 @@
2407:6c40:1210::/48 2407:6c40:1210::/48
2407:6c40:1500::/48 2407:6c40:1500::/48
2407:6c40:1600::/40 2407:6c40:1600::/40
2407:7d00:1::/48
2407:8f40:2::/48 2407:8f40:2::/48
2407:9f00::/32 2407:9f00::/32
2407:ad80::/32 2407:ad80::/32
@@ -467,29 +330,19 @@
2408:8000:4000::/34 2408:8000:4000::/34
2408:8000:8000::/33 2408:8000:8000::/33
2408:8001::/32 2408:8001::/32
2408:8020::/30 2408:8024:8000::/33
2408:8024::/31 2408:8025::/32
2408:8026::/32 2408:8026::/32
2408:802a:8000::/33 2408:802a:8000::/33
2408:802c::/32 2408:802c::/32
2408:803e::/32 2408:803e::/32
2408:8056::/32 2408:8056::/32
2408:805c::/30
2408:8060::/33
2408:80c2::/33 2408:80c2::/33
2408:80c5::/33 2408:80c5::/33
2408:80ca::/33 2408:80e0:4000::/36
2408:80ca:8000::/34
2408:80da::/33
2408:80da:8000::/34
2408:80e0:4000::/34
2408:80e0:8000::/33
2408:80e2::/33 2408:80e2::/33
2408:80e9:4000::/34 2408:80e9:4000::/34
2408:80ea:4000::/34 2408:80f0:4000::/36
2408:80ea:8000::/33
2408:80f0:4000::/34
2408:80f0:8000::/33
2408:80f1::/42 2408:80f1::/42
2408:80f1:40::/43 2408:80f1:40::/43
2408:80f1:70::/44 2408:80f1:70::/44
@@ -504,8 +357,6 @@
2408:80f1:200::/40 2408:80f1:200::/40
2408:80f5:4000::/34 2408:80f5:4000::/34
2408:80f9:4000::/34 2408:80f9:4000::/34
2408:80fa:4000::/34
2408:80fa:8000::/33
2408:8120:1::/48 2408:8120:1::/48
2408:8120:2::/48 2408:8120:2::/48
2408:8120:7000::/36 2408:8120:7000::/36
@@ -728,86 +579,103 @@
2408:840c:dd00::/40 2408:840c:dd00::/40
2408:840c:de00::/40 2408:840c:de00::/40
2408:840d::/42 2408:840d::/42
2408:840d:40::/43
2408:840d:200::/42 2408:840d:200::/42
2408:840d:400::/42 2408:840d:400::/42
2408:840d:440::/43 2408:840d:440::/43
2408:840d:600::/42 2408:840d:600::/42
2408:840d:d00::/42 2408:840d:d00::/42
2408:840d:d40::/43
2408:840d:f00::/42 2408:840d:f00::/42
2408:840d:1100::/42 2408:840d:1100::/42
2408:840d:1140::/43 2408:840d:1140::/43
2408:840d:1300::/42 2408:840d:1300::/42
2408:840d:1a00::/42 2408:840d:1a00::/42
2408:840d:1a40::/43
2408:840d:1c00::/42 2408:840d:1c00::/42
2408:840d:1e00::/42 2408:840d:1e00::/42
2408:840d:1e40::/43 2408:840d:1e40::/43
2408:840d:2000::/42 2408:840d:2000::/42
2408:840d:2700::/42 2408:840d:2700::/42
2408:840d:2740::/43
2408:840d:2900::/42 2408:840d:2900::/42
2408:840d:2b00::/42 2408:840d:2b00::/42
2408:840d:2b40::/43 2408:840d:2b40::/43
2408:840d:2d00::/42 2408:840d:2d00::/42
2408:840d:3400::/42 2408:840d:3400::/42
2408:840d:3440::/43
2408:840d:3600::/42 2408:840d:3600::/42
2408:840d:3800::/42 2408:840d:3800::/42
2408:840d:3840::/43 2408:840d:3840::/43
2408:840d:3a00::/42 2408:840d:3a00::/42
2408:840d:4e00::/42 2408:840d:4e00::/42
2408:840d:4e40::/43
2408:840d:5000::/42 2408:840d:5000::/42
2408:840d:5200::/42 2408:840d:5200::/42
2408:840d:5240::/43 2408:840d:5240::/43
2408:840d:5400::/42 2408:840d:5400::/42
2408:840d:5b00::/42 2408:840d:5b00::/42
2408:840d:5b40::/43
2408:840d:5d00::/42 2408:840d:5d00::/42
2408:840d:5f00::/42 2408:840d:5f00::/42
2408:840d:5f40::/43 2408:840d:5f40::/43
2408:840d:6100::/42 2408:840d:6100::/42
2408:840d:6800::/42 2408:840d:6800::/42
2408:840d:6840::/43
2408:840d:6a00::/42 2408:840d:6a00::/42
2408:840d:6c00::/42 2408:840d:6c00::/42
2408:840d:6c40::/43 2408:840d:6c40::/43
2408:840d:6e00::/42 2408:840d:6e00::/42
2408:840d:7500::/42 2408:840d:7500::/42
2408:840d:7540::/43
2408:840d:7700::/42 2408:840d:7700::/42
2408:840d:7900::/42 2408:840d:7900::/42
2408:840d:7940::/43 2408:840d:7940::/43
2408:840d:7b00::/42 2408:840d:7b00::/42
2408:840d:8200::/42 2408:840d:8200::/42
2408:840d:8240::/43
2408:840d:8400::/42 2408:840d:8400::/42
2408:840d:8600::/42 2408:840d:8600::/42
2408:840d:8640::/43 2408:840d:8640::/43
2408:840d:8800::/42 2408:840d:8800::/42
2408:840d:8f00::/42 2408:840d:8f00::/42
2408:840d:8f40::/43
2408:840d:9100::/42 2408:840d:9100::/42
2408:840d:9300::/42 2408:840d:9300::/42
2408:840d:9340::/43 2408:840d:9340::/43
2408:840d:9500::/42 2408:840d:9500::/42
2408:840d:9c00::/42 2408:840d:9c00::/42
2408:840d:9c40::/43
2408:840d:9e00::/42 2408:840d:9e00::/42
2408:840d:a000::/42 2408:840d:a000::/42
2408:840d:a040::/43 2408:840d:a040::/43
2408:840d:a200::/42 2408:840d:a200::/42
2408:840d:a900::/42 2408:840d:a900::/42
2408:840d:a940::/43
2408:840d:ab00::/42 2408:840d:ab00::/42
2408:840d:ad00::/42 2408:840d:ad00::/42
2408:840d:ad40::/43 2408:840d:ad40::/43
2408:840d:af00::/42 2408:840d:af00::/42
2408:840d:b600::/42 2408:840d:b600::/42
2408:840d:b640::/43
2408:840d:b800::/42 2408:840d:b800::/42
2408:840d:ba00::/42 2408:840d:ba00::/42
2408:840d:ba40::/43 2408:840d:ba40::/43
2408:840d:bc00::/42 2408:840d:bc00::/42
2408:840d:c300::/42 2408:840d:c300::/42
2408:840d:c340::/43
2408:840d:c500::/42 2408:840d:c500::/42
2408:840d:c700::/42 2408:840d:c700::/42
2408:840d:c740::/43 2408:840d:c740::/43
2408:840d:c900::/42 2408:840d:c900::/42
2408:840d:d000::/42 2408:840d:d000::/42
2408:840d:d040::/43
2408:840d:d200::/42 2408:840d:d200::/42
2408:840d:d400::/42 2408:840d:d400::/42
2408:840d:d440::/43 2408:840d:d440::/43
2408:840d:d600::/42 2408:840d:d600::/42
2408:840d:dd00::/42 2408:840d:dd00::/42
2408:840d:dd40::/43
2408:840d:de00::/42 2408:840d:de00::/42
2408:840e:dd00::/40 2408:840e:dd00::/40
2408:840e:de00::/40 2408:840e:de00::/40
@@ -949,6 +817,7 @@
2408:8878::/31 2408:8878::/31
2408:887e::/32 2408:887e::/32
2408:8906:20::/44 2408:8906:20::/44
2408:8906:40::/43
2408:8907:9000::/44 2408:8907:9000::/44
2408:890c::/31 2408:890c::/31
2408:8912::/31 2408:8912::/31
@@ -985,13 +854,9 @@
2408:8a05:8000::/36 2408:8a05:8000::/36
2408:8a06::/47 2408:8a06::/47
2408:8a06:100::/47 2408:8a06:100::/47
2408:8a07::/40
2408:8a21:4000::/35 2408:8a21:4000::/35
2408:8a22:9200::/39 2408:8a22::/35
2408:8a22:9400::/38
2408:8a22:9800::/40
2408:8a22:9a00::/39
2408:8a22:9c00::/38
2408:8a22:a000::/37
2408:8a23:4000::/34 2408:8a23:4000::/34
2408:8a24:4000::/34 2408:8a24:4000::/34
2408:8a26:c000::/34 2408:8a26:c000::/34
@@ -1004,6 +869,7 @@
2409:27fb::/48 2409:27fb::/48
2409:27fc::/48 2409:27fc::/48
2409:27fe::/33 2409:27fe::/33
2409:6100::/44
2409:8000::/20 2409:8000::/20
240a:2000::/40 240a:2000::/40
240a:2001:100::/40 240a:2001:100::/40
@@ -1038,6 +904,7 @@
240a:40c1:2000::/43 240a:40c1:2000::/43
240a:40c1:4000::/43 240a:40c1:4000::/43
240a:40c1:6000::/43 240a:40c1:6000::/43
240a:40c1:8000::/43
240a:40c1:a000::/43 240a:40c1:a000::/43
240a:40c1:c000::/43 240a:40c1:c000::/43
240a:40c1:e000::/43 240a:40c1:e000::/43
@@ -1073,32 +940,10 @@
240b:e004::/30 240b:e004::/30
240b:e008::/29 240b:e008::/29
240b:e010::/32 240b:e010::/32
240c:6::/47
240c:6:3::/48
240c:f:1::/48
240c:4000::/22 240c:4000::/22
240c:c000::/20 240c:c000::/20
240d:4000::/21 240d:4000::/21
240e::/20 240e::/20
2602:2e0:ff::/48
2602:f46d:6::/48
2602:f612::/40
2602:f7ee:ee::/48
2602:f92a:1314::/48
2602:f92a:a440::/44
2602:f92a:a46d::/48
2602:f92a:a46f::/48
2602:f92a:a470::/48
2602:f92a:dead::/48
2602:f93b:c00::/38
2602:f9ba:a8::/48
2602:f9f6:450::/48
2602:fab0:11::/48
2602:feda:1bf::/48
2602:feda:1d1::/48
2602:feda:1df::/48
2602:feda:2d0::/44
2602:feda:2f0::/44
2605:9d80:8001::/48 2605:9d80:8001::/48
2605:9d80:8011::/48 2605:9d80:8011::/48
2605:9d80:8021::/48 2605:9d80:8021::/48
@@ -1114,7 +959,6 @@
2605:9d80:9092::/48 2605:9d80:9092::/48
2804:1e48:9001::/48 2804:1e48:9001::/48
2804:1e48:9002::/48 2804:1e48:9002::/48
2a01:f100:1f8::/48
2a04:3e00:1002::/48 2a04:3e00:1002::/48
2a04:f580:8010::/47 2a04:f580:8010::/47
2a04:f580:8090::/48 2a04:f580:8090::/48
@@ -1139,249 +983,26 @@
2a04:f580:9270::/48 2a04:f580:9270::/48
2a04:f580:9280::/48 2a04:f580:9280::/48
2a04:f580:9290::/48 2a04:f580:9290::/48
2a06:3600:e000::/40 2a0a:d685:1ff::/48
2a06:3600:fb00::/40 2a0a:d685:300::/40
2a06:3600:fc00::/38 2a0b:4340:90::/48
2a06:3605::/32 2a0b:4340:97::/48
2a06:3606::/31 2a0d:c7c7:400::/38
2a06:9f81:4600::/43 2a0e:aa06:49f::/48
2a06:9f81:4620::/44
2a06:9f81:4640::/43
2a06:a005:260::/43
2a06:a005:280::/43
2a06:a005:2a0::/44
2a06:a005:a13::/48
2a06:a005:1c40::/44
2a09:b280:ff81::/48
2a09:b280:ff83::/48
2a09:b280:ff84::/47
2a0a:2840::/30
2a0a:2845:aab8::/46
2a0a:2846::/48
2a0a:6040:ec00::/40
2a0a:6044:6600::/40
2a0a:d681:e000::/40
2a0a:d681:fb00::/40
2a0a:d681:fc00::/38
2a0b:2542::/48
2a0b:4e07:b8::/47
2a0c:9a40:8fc1::/48
2a0c:9a40:8fc2::/47
2a0c:9a40:8fc4::/48
2a0c:b641:571::/48
2a0c:b641:722::/47
2a0e:aa01:1fff::/48
2a0e:aa06::/40
2a0e:aa06:490::/44
2a0e:aa06:500::/44
2a0e:aa06:520::/48
2a0e:aa06:525::/48
2a0e:aa06:541::/48
2a0e:aa06:56a::/48
2a0e:aa07:e01a::/47
2a0e:aa07:e021::/48
2a0e:aa07:e030::/48
2a0e:aa07:e035::/48
2a0e:aa07:e039::/48
2a0e:aa07:e151::/48
2a0e:aa07:e155::/48
2a0e:aa07:e15f::/48
2a0e:aa07:e160::/47
2a0e:aa07:e162::/48
2a0e:aa07:e16a::/48
2a0e:aa07:e1a0::/44
2a0e:aa07:e210::/48
2a0e:aa07:e21c::/47
2a0e:aa07:e220::/44
2a0e:aa07:f0d0::/46
2a0e:aa07:f0d4::/47
2a0e:aa07:f0d8::/48
2a0e:aa07:f0de::/47
2a0e:b107:12b::/48
2a0e:b107:272::/48
2a0e:b107:740::/44 2a0e:b107:740::/44
2a0e:b107:c10::/48 2a0e:b107:2440::/44
2a0e:b107:178d::/48 2a0f:1cc5:1600::/44
2a0e:b107:178e::/48 2a0f:1cc5:4700::/40
2a0f:1cc5:20::/44 2a12:cb41:1200::/44
2a0f:1cc5:110::/44 2a12:cb41:1300::/43
2a0f:1cc5:130::/44 2a12:cb41:13f0::/44
2a0f:1cc5:140::/44
2a0f:1cc5:600::/47
2a0f:1cc5:603::/48
2a0f:1cc5:645::/48
2a0f:1cc5:661::/48
2a0f:1cc5:662::/48
2a0f:1cc5:690::/48
2a0f:1cc5:6a0::/48
2a0f:1cc5:900::/40
2a0f:1cc5:c01::/48
2a0f:1cc5:f00::/46
2a0f:1cc5:f04::/47
2a0f:1cc5:f06::/48
2a0f:1cc5:f08::/47
2a0f:1cc5:fff::/48
2a0f:1cc5:1310::/44
2a0f:1cc5:1400::/48
2a0f:1cc5:1c00::/47
2a0f:1cc5:1c02::/48
2a0f:1cc5:1c30::/48
2a0f:1cc5:1cc0::/48
2a0f:1cc5:1d10::/47
2a0f:1cc5:1d14::/48
2a0f:1cc5:2d01::/48
2a0f:1cc5:2d03::/48
2a0f:1cc5:ffff::/48
2a0f:1cc6:b110::/47
2a0f:2706::/32
2a0f:6282:d000::/48
2a0f:6284:300::/40
2a0f:6284:400::/42
2a0f:6284:440::/43
2a0f:6284:3000::/39
2a0f:6284:4c00::/44
2a0f:6284:4c20::/44
2a0f:6284:4c30::/48
2a0f:6284:4c40::/43
2a0f:6284:4c60::/44
2a0f:6284:4c80::/43
2a0f:6284:4ca0::/44
2a0f:6284:4cd0::/44
2a0f:6284:4f00::/40
2a0f:6284:e000::/35
2a0f:7803:f680::/44
2a0f:7803:fa21::/48
2a0f:7803:fa22::/47
2a0f:7803:fa24::/46
2a0f:7803:fe81::/48
2a0f:7803:fe82::/48
2a0f:85c1:ba5::/48
2a0f:85c1:bfe::/48
2a0f:85c1:ce1::/48
2a0f:85c1:cf1::/48
2a0f:9400:6110::/48
2a0f:9400:7700::/48
2a10:ccc6:66cc::/46
2a12:cb46::/36
2a12:cb47:ffff::/48
2a12:f8c3::/36
2a13:1800::/48
2a13:1800:10::/48
2a13:1800:80::/44
2a13:1800:300::/44
2a13:1801:180::/43 2a13:1801:180::/43
2a13:a5c3:ff21::/48 2a13:a5c3:f000::/40
2a13:a5c3:ff50::/44 2a14:7580:d00::/43
2a13:a5c7:1800::/40 2a14:7580:d20::/46
2a13:a5c7:2121::/48 2a14:7580:d41::/48
2a13:a5c7:2801::/48 2a14:7580:d42::/47
2a13:a5c7:3100::/40 2a14:7580:dff::/48
2a13:a5c7:3301::/48
2a13:a5c7:3302::/48
2a13:a5c7:3304::/48
2a13:a5c7:3306::/47
2a13:a5c7:3309::/48
2a13:aac4:f000::/44
2a14:7c0:5103::/48
2a14:4c41::/32
2a14:67c1:70::/47
2a14:67c1:74::/48
2a14:67c1:702::/47
2a14:67c1:a020::/48
2a14:67c1:a023::/48
2a14:67c1:a024::/48
2a14:67c1:a02a::/48
2a14:67c1:a02f::/48
2a14:67c1:a040::/47
2a14:67c1:a100::/43
2a14:67c1:a121::/48
2a14:67c1:a123::/48
2a14:67c1:a125::/48
2a14:67c1:a128::/48
2a14:67c1:a144::/48
2a14:67c1:b000::/48
2a14:67c1:b065::/48
2a14:67c1:b066::/48
2a14:67c1:b068::/47
2a14:67c1:b100::/46
2a14:67c1:b105::/48
2a14:67c1:b107::/48
2a14:67c1:b110::/48
2a14:67c1:b130::/46
2a14:67c1:b134::/48
2a14:67c1:b136::/48
2a14:67c1:b141::/48
2a14:67c1:b142::/47
2a14:67c1:b146::/48
2a14:67c1:b460::/44
2a14:67c1:b4b0::/44
2a14:67c1:b4e0::/44
2a14:67c1:b500::/47
2a14:67c1:b511::/48
2a14:67c1:b514::/48
2a14:67c1:b530::/44
2a14:67c1:b561::/48
2a14:67c1:b563::/48
2a14:67c1:b578::/48
2a14:67c1:b582::/48
2a14:67c1:b586::/47
2a14:67c1:b588::/47
2a14:67c1:b590::/48
2a14:67c1:b599::/48
2a14:67c1:b5e0::/44
2a14:67c1:c300::/40
2a14:67c1:c600::/40
2a14:67c2:800::/40
2a14:67c2:c32::/48
2a14:67c2:c64::/48
2a14:67c3:30::/44
2a14:67c3:c0::/48
2a14:67c3:d0::/44
2a14:67c3:520::/44
2a14:67c3:e622::/48
2a14:7580:730::/44
2a14:7580:740::/44
2a14:7580:777::/48
2a14:7580:d000::/37
2a14:7580:d800::/39
2a14:7580:df00::/40
2a14:7580:fe00::/40
2a14:7580:ffe4::/48
2a14:7580:fff4::/48
2a14:7580:fff7::/48
2a14:7580:fffa::/48
2a14:7581:ffb::/48
2a14:7581:30b6::/48
2a14:7581:3100::/40
2a14:7581:3401::/48
2a14:7583:f224::/48
2a14:7583:f228::/48
2a14:7583:f22c::/48
2a14:7583:f244::/48
2a14:7583:f264::/48
2a14:7583:f268::/48
2a14:7583:f300::/46
2a14:7583:f304::/47
2a14:7583:f306::/48
2a14:7583:f411::/48
2a14:7583:f4f0::/47
2a14:7583:f4f4::/48
2a14:7583:f4fe::/48
2a14:7583:f500::/48
2a14:7583:f701::/48
2a14:7583:f703::/48
2a14:7583:f704::/46
2a14:7583:f708::/46
2a14:7583:f70c::/48
2a14:7583:f743::/48
2a14:7583:f744::/48
2a14:7583:f764::/48
2a14:7586:6100::/47
2a14:7586:6103::/48
2a14:7586:6104::/48
2a14:7586:6107::/48
2a14:7586:6110::/48
2a14:7dc0:506::/48
2c0f:f7a8:8011::/48 2c0f:f7a8:8011::/48
2c0f:f7a8:8050::/48 2c0f:f7a8:8050::/48
2c0f:f7a8:805f::/48 2c0f:f7a8:805f::/48
@@ -1390,5 +1011,4 @@
2c0f:f7a8:8211::/48 2c0f:f7a8:8211::/48
2c0f:f7a8:9020::/48 2c0f:f7a8:9020::/48
2c0f:f7a8:9041::/48 2c0f:f7a8:9041::/48
2c0f:f7a8:9211::/48
2c0f:f7a8:9220::/48 2c0f:f7a8:9220::/48
@@ -1 +1 @@
20260424052559 20260704060757
File diff suppressed because it is too large. Load diff
@@ -1 +1 @@
202604232238 202607052256
@@ -13,6 +13,7 @@
173ng.com 173ng.com
18board.com 18board.com
18comic.org 18comic.org
18mh.net
1984bbs.com 1984bbs.com
1991way.com 1991way.com
1e100.net 1e100.net
@@ -25,6 +26,7 @@
2047.one 2047.one
2049bbs.xyz 2049bbs.xyz
233abc.com 233abc.com
24hbook.com
24hrs.ca 24hrs.ca
32red.com 32red.com
36rain.com 36rain.com
@@ -72,6 +74,7 @@
9cache.com 9cache.com
9gag.com 9gag.com
9news.com.au 9news.com.au
9oju31.launches.appsflyersdk.com
a-normal-day.com a-normal-day.com
a248.e.akamai.net a248.e.akamai.net
aamacau.com aamacau.com
@@ -86,8 +89,10 @@ about.gitlab.com
about.me about.me
abplive.com abplive.com
acast.com acast.com
accountboy.com
accountkit.com accountkit.com
acevpn.com acevpn.com
acg.mn
acg.rip acg.rip
acg18.me acg18.me
acgbox.org acgbox.org
@@ -126,6 +131,7 @@ aiosearch.com
aiph.net aiph.net
airconsole.com airconsole.com
airitilibrary.com airitilibrary.com
airuniversity.af.edu
airvpn.org airvpn.org
ait.org.tw ait.org.tw
aiweiweiblog.com aiweiweiblog.com
@@ -205,13 +211,18 @@ aoxvpn.com
apartmentratings.com apartmentratings.com
apartments.com apartments.com
apat1989.org apat1989.org
api-gl.lbkrs.com
api-secure.recaptcha.net api-secure.recaptcha.net
api-verify.recaptcha.net api-verify.recaptcha.net
api.ai api.ai
api.futunn.com
api.lbkrs.com
api.moomoobull.com
api.palworldgame.com api.palworldgame.com
api.pureapk.com api.pureapk.com
api.recaptcha.net api.recaptcha.net
api.steampowered.com api.steampowered.com
api5.futunn.com
apiary.io apiary.io
apigee.com apigee.com
apk.support apk.support
@@ -257,6 +268,7 @@ arte.tv
artstation.com artstation.com
arvanstorage.ir arvanstorage.ir
arweave.org arweave.org
as.mr
asacp.org asacp.org
asiaharvest.org asiaharvest.org
asianage.com asianage.com
@@ -321,6 +333,7 @@ bangchen.net
bangdream.space bangdream.space
bangkokpost.com bangkokpost.com
bangumi.moe bangumi.moe
bangumi.tv
bangyoulater.com bangyoulater.com
bankmobilevibe.com bankmobilevibe.com
bannedbook.org bannedbook.org
@@ -376,6 +389,7 @@ bettween.com
betvictor.com betvictor.com
bfnn.org bfnn.org
bfsh.hk bfsh.hk
bgm.tv
bgme.me bgme.me
bgvpn.com bgvpn.com
biblesforamerica.org biblesforamerica.org
@@ -386,6 +400,7 @@ bigjapanesesex.com
bignews.org bignews.org
bigone.com bigone.com
bild.de bild.de
bilinovel.com
biliworld.com biliworld.com
binance.com binance.com
binance.org binance.org
@@ -413,6 +428,7 @@ bl-doujinsouko.com
blacked.com blacked.com
blackmagicdesign.com blackmagicdesign.com
blackvpn.com blackvpn.com
bleepingcomputer.com
blinkx.com blinkx.com
blip.tv blip.tv
blockcast.it blockcast.it
@@ -525,6 +541,7 @@ bookdepository.com
books.com.tw books.com.tw
booktopia.com.au booktopia.com.au
bookwalker.com.tw bookwalker.com.tw
boomplay.com
bootstrapcdn.com bootstrapcdn.com
borgenmagazine.com borgenmagazine.com
botanwang.com botanwang.com
@@ -648,6 +665,7 @@ cdjp.org
cdn-images.mailchimp.com cdn-images.mailchimp.com
cdn-telegram.org cdn-telegram.org
cdn.arstechnica.net cdn.arstechnica.net
cdn.futustatic.com
cdn.jwplayer.com cdn.jwplayer.com
cdn.printfriendly.com cdn.printfriendly.com
cdn.seatguru.com cdn.seatguru.com
@@ -671,6 +689,7 @@ centurys.net
certificate-transparency.org certificate-transparency.org
certificate.revocationcheck.com certificate.revocationcheck.com
cfr.org cfr.org
cg.play-analytics.com
cgdepot.org cgdepot.org
change.org change.org
changp.com changp.com
@@ -692,6 +711,7 @@ chenpokongvip.com
chenshan20042005.wordpress.com chenshan20042005.wordpress.com
cherrysave.com cherrysave.com
chhongbi.org chhongbi.org
chii.in
china-mmm.net china-mmm.net
china-review.com.ua china-review.com.ua
china-week.com china-week.com
@@ -736,6 +756,7 @@ chineseupress.com
chingcheong.com chingcheong.com
chinman.net chinman.net
chobit.cc chobit.cc
chonglangtv.org
chrdnet.com chrdnet.com
christianfreedom.org christianfreedom.org
christianstudy.com christianstudy.com
@@ -850,6 +871,7 @@ crazypool.org
crazyshit.com crazyshit.com
crbug.com crbug.com
crchina.org crchina.org
creader.com
creaders.net creaders.net
creativelab5.com creativelab5.com
cristyli.com cristyli.com
@@ -871,6 +893,7 @@ csw.org.uk
ct.org.tw ct.org.tw
ctinets.com ctinets.com
ctinews.com ctinews.com
ctinsider.com
ctitv.com.tw ctitv.com.tw
ctowc.org ctowc.org
cts.com.tw cts.com.tw
@@ -913,6 +936,7 @@ darpa.mil
darrenliuwei.com darrenliuwei.com
dashlane.com dashlane.com
data-vocabulary.org data-vocabulary.org
data18.com
daum.net daum.net
david-kilgour.com david-kilgour.com
dawangidc.com dawangidc.com
@@ -1011,7 +1035,9 @@ doublethinklab.org
douchi.space douchi.space
dougscripts.com dougscripts.com
doujincafe.com doujincafe.com
dowjones.io
download.dappcdn.com download.dappcdn.com
download.futunn.com
dpool.top dpool.top
dpp.org.tw dpp.org.tw
dpr.info dpr.info
@@ -1024,6 +1050,7 @@ dropboxapi.com
dropboxusercontent.com dropboxusercontent.com
dscn.info dscn.info
dstk.dk dstk.dk
dt18.com
dtiblog.com dtiblog.com
dtic.mil dtic.mil
dubox.com dubox.com
@@ -1065,6 +1092,7 @@ ecfa.org.tw
ecimg.tw ecimg.tw
economist.com economist.com
edgecastcdn.net edgecastcdn.net
edigest.hk
edubridge.com edubridge.com
edx-cdn.org edx-cdn.org
eesti.ee eesti.ee
@@ -1135,6 +1163,7 @@ expatshield.com
expecthim.com expecthim.com
expekt.com expekt.com
exploader.net exploader.net
expressnews.com
expressvpn.com expressvpn.com
exrates.me exrates.me
extmatrix.com extmatrix.com
@@ -1143,6 +1172,7 @@ eyny.com
f-droid.org f-droid.org
f2pool.com f2pool.com
f8.com f8.com
f95zone.to
facebook.com facebook.com
facebook.de facebook.de
facebook.design facebook.design
@@ -1324,12 +1354,42 @@ funkyimg.com
furbo.org furbo.org
furinkan.com furinkan.com
furrybar.com furrybar.com
futu.cn
futu.com
futu.link
futu0.com
futu1.com
futu2.com
futu3.com
futu4.com
futu5.com
futu6.com
futu7.com
futu8.com
futu9.com
futuau.com
futubull.cn
futuchain.com
futuesop.com
futufin.com
futuhk.com
futuhk2.com
futuhkapp.com
futuhn.com
futuholdings.com
futuinc.com
futuniuniu.com
futunn.com
futuremessage.org futuremessage.org
futustatic.com
fututrade.com
fututrustee.com
fw.cm fw.cm
fxcm-chinese.com fxcm-chinese.com
fxnetworks.com fxnetworks.com
g-area.org g-area.org
g-queen.com g-queen.com
g.ai
g.co g.co
g0v.social g0v.social
g6hentai.com g6hentai.com
@@ -1346,6 +1406,7 @@ gaoming.net
gaopi.net gaopi.net
gartlive.com gartlive.com
garudalinux.org garudalinux.org
gate.com
gate.io gate.io
gatecoin.com gatecoin.com
gather.com gather.com
@@ -1361,10 +1422,12 @@ gdaily.org
geek-art.net geek-art.net
gekikame.com gekikame.com
gelbooru.com gelbooru.com
gencraft.com
generated.photos generated.photos
genius.com genius.com
geph.io geph.io
get.app get.app
get.dappcdn.com
get.dev get.dev
get.how get.how
get.page get.page
@@ -1722,6 +1785,7 @@ gtv.org
gtv1.org gtv1.org
gu-chu-sum.org gu-chu-sum.org
guaguass.com guaguass.com
guangming.com.my
guishan.org guishan.org
gumroad.com gumroad.com
gunsamerica.com gunsamerica.com
@@ -1775,6 +1839,7 @@ heyuedi.com
hicairo.com hicairo.com
hiccears.com hiccears.com
hidden-advent.org hidden-advent.org
hiddify.com
hide.me hide.me
hideipvpn.com hideipvpn.com
hideman.net hideman.net
@@ -1790,6 +1855,7 @@ hindustantimes.com
hinet.net hinet.net
hitbtc.com hitbtc.com
hitomi.la hitomi.la
hive.blog
hiveon.net hiveon.net
hk-pub.com hk-pub.com
hk.gradconnection.com hk.gradconnection.com
@@ -1810,6 +1876,7 @@ hkgolden.com
hkgpao.com hkgpao.com
hklts.org.hk hklts.org.hk
hkmap.live hkmap.live
hkong.hk
hkopentv.com hkopentv.com
hkpeanut.com hkpeanut.com
hkreporter.com hkreporter.com
@@ -1964,6 +2031,7 @@ indiatoday.in
indiemerch.com indiemerch.com
inews-api.tvb.com inews-api.tvb.com
info-graf.fr info-graf.fr
infosec.exchange
infura.io infura.io
inherit.live inherit.live
initiativesforchina.org initiativesforchina.org
@@ -2023,6 +2091,7 @@ itasoftware.com
itch.io itch.io
itemfix.com itemfix.com
itiger.com itiger.com
itigerup.com
itshidden.com itshidden.com
itweet.net itweet.net
iuhrdf.org iuhrdf.org
@@ -2102,6 +2171,7 @@ kanshifang.com
kantie.org kantie.org
kaotic.com kaotic.com
karayou.com karayou.com
kathmandupost.com
kawaiikawaii.jp kawaiikawaii.jp
kawase.com kawase.com
kb.monitorware.com kb.monitorware.com
@@ -2111,6 +2181,7 @@ kenengba.com
kepard.com kepard.com
kex.com kex.com
keycdn.com keycdn.com
kfor.com
khatrimaza.org khatrimaza.org
kichiku-doujinko.com kichiku-doujinko.com
kik.com kik.com
@@ -2160,12 +2231,15 @@ landofhope.tv
lantern.io lantern.io
laogai.org laogai.org
laogairesearch.org laogairesearch.org
laohu8.com
laqingdan.net laqingdan.net
larsgeorge.com larsgeorge.com
lastcombat.com lastcombat.com
lastfm.es lastfm.es
lausan.hk lausan.hk
lbank.info lbank.info
lbctrl.com
lbkrs.com
ldplayer.net ldplayer.net
ldplayer.tw ldplayer.tw
le-vpn.com le-vpn.com
@@ -2176,7 +2250,9 @@ left21.hk
legra.ph legra.ph
leisurepro.com leisurepro.com
lematin.ch lematin.ch
lemonde.fr
lenwhite.com lenwhite.com
leonardo.ai
lesoir.be lesoir.be
letscorp.net letscorp.net
lexica.art lexica.art
@@ -2188,6 +2264,7 @@ liaowangxizang.net
liberal.org.hk liberal.org.hk
libertysculpturepark.com libertysculpturepark.com
libertytimes.com.tw libertytimes.com.tw
libgen.is
library-access.sk library-access.sk
library.usc.cuhk.edu.hk library.usc.cuhk.edu.hk
libredd.it libredd.it
@@ -2231,6 +2308,15 @@ localdomain.ws
lockestek.com lockestek.com
login.target.com login.target.com
logos.com.hk logos.com.hk
longbridge.cloud
longbridge.com
longbridge.global
longbridge.hk
longbridge.sg
longbridgeapp.com
longbridgehk.com
longportapp.cn
longportapp.com
longtermly.net longtermly.net
longtoes.com longtoes.com
lookpic.com lookpic.com
@@ -2283,6 +2369,7 @@ martau.com
martsangkagyuofficial.org martsangkagyuofficial.org
marxist.net marxist.net
marxists.org marxists.org
mas.to
mash.to mash.to
mastodon.cloud mastodon.cloud
mastodon.host mastodon.host
@@ -2309,6 +2396,7 @@ meansys.com
media.nu.nl media.nu.nl
mediachinese.com mediachinese.com
mediafreakcity.com mediafreakcity.com
mediamatters.org
mediawiki.org mediawiki.org
medium.com medium.com
meee.com.tw meee.com.tw
@@ -2346,6 +2434,7 @@ mfxmedia.com
mgoon.com mgoon.com
mgstage.com mgstage.com
mh4u.org mh4u.org
mhwindow.org
microvpn.com microvpn.com
mihua.org mihua.org
mij.rip mij.rip
@@ -2388,6 +2477,7 @@ mixx.com
mizzmona.com mizzmona.com
mji.rip mji.rip
mjj.rip mjj.rip
mjj.today
mjlsh.usc.cuhk.edu.hk mjlsh.usc.cuhk.edu.hk
mlc.ai mlc.ai
mlzs.work mlzs.work
@@ -2414,6 +2504,9 @@ monitorchina.org
monocloud.me monocloud.me
monster.com monster.com
moodyz.com moodyz.com
moomoo.com
moomooequity.com
moomootrustee.com
moon.fm moon.fm
moonbbs.com moonbbs.com
moonbingo.com moonbingo.com
@@ -2425,6 +2518,7 @@ mosavi.io
motherless.com motherless.com
movements.org movements.org
moviefap.com moviefap.com
mozilla.ai
mpettis.com mpettis.com
mpfinance.com mpfinance.com
mpinews.com mpinews.com
@@ -2437,6 +2531,7 @@ mthruf.com
mubi.com mubi.com
mullvad.net mullvad.net
multiply.com multiply.com
music.amazon.com
musixmatch.com musixmatch.com
muzi.com muzi.com
muzi.net muzi.net
@@ -2487,6 +2582,7 @@ ncn.org
ndi.org ndi.org
nekoslovakia.net nekoslovakia.net
neo-miracle.com neo-miracle.com
neodb.social
neowin.net neowin.net
nephobox.com nephobox.com
netalert.me netalert.me
@@ -2510,10 +2606,12 @@ news1.kr
news18.com news18.com
newsancai.com newsancai.com
newsblur.com newsblur.com
newshub.co.nz
newsmax.com newsmax.com
newstamago.com newstamago.com
newstapa.org newstapa.org
newstatesman.com newstatesman.com
newstimes.com
newsweek.com newsweek.com
newtalk.tw newtalk.tw
newthuhole.com newthuhole.com
@@ -2528,6 +2626,8 @@ nflximg.com
nflximg.net nflximg.net
nflxso.net nflxso.net
nflxvideo.net nflxvideo.net
nfsc.press
nfscofficial.com
nftstorage.link nftstorage.link
nga.mil nga.mil
nhentai.net nhentai.net
@@ -2647,6 +2747,13 @@ ooni.io
ooni.org ooni.org
open.firstory.me open.firstory.me
openai.com openai.com
openapi-quote.longbridge.cn
openapi-quote.longbridge.com
openapi-trade.longbridge.cn
openapi-trade.longbridge.com
openapi.futunn.com
openapi.longbridge.cn
openapi.longbridge.com
openart.ai openart.ai
opencritic.com opencritic.com
opendemocracy.net opendemocracy.net
@@ -2673,6 +2780,7 @@ oursteps.com.au
ourtv.hk ourtv.hk
overcast.fm overcast.fm
overdaily.org overdaily.org
overdrive.com
overplay.net overplay.net
oversea.istarshine.com oversea.istarshine.com
ovpn.com ovpn.com
@@ -2685,7 +2793,6 @@ pachosting.com
pacom.mil pacom.mil
pacopacomama.com pacopacomama.com
page.link page.link
pages.dev
paimon.moe paimon.moe
pancakeswap.finance pancakeswap.finance
pandafan.pub pandafan.pub
@@ -2712,6 +2819,7 @@ pbs.org
pbworks.com pbworks.com
pbxes.com pbxes.com
pbxes.org pbxes.org
pcgamesn.com
pcgamestorrents.com pcgamestorrents.com
pcij.org pcij.org
pct.org.tw pct.org.tw
@@ -2724,11 +2832,13 @@ pekingduck.org
pemulihan.or.id pemulihan.or.id
pen.io pen.io
pendrivelinux.com pendrivelinux.com
penguin.com.au
pentoy.hk pentoy.hk
peoplenews.tw peoplenews.tw
peopo.org peopo.org
perfect-privacy.com perfect-privacy.com
periscope.tv periscope.tv
perma.cc
perplexity.ai perplexity.ai
pewresearch.org pewresearch.org
phayul.com phayul.com
@@ -2900,12 +3010,15 @@ qmp4.com
qoos.com qoos.com
qq.co.za qq.co.za
qstatus.com qstatus.com
qtcard.futunn.com
qtcardfthk.futufin.com
qtrac.eu qtrac.eu
questvisual.com questvisual.com
quitccp.org quitccp.org
quiz.directory quiz.directory
quora.com quora.com
quoracdn.net quoracdn.net
quote.longbridge.global
quran.com quran.com
qz.com qz.com
r-pool.net r-pool.net
@@ -2931,6 +3044,7 @@ raindrop.io
raizoji.or.jp raizoji.or.jp
rakuten.co.jp rakuten.co.jp
ramcity.com.au ramcity.com.au
raphael.app
rapidmoviez.com rapidmoviez.com
rapidvpn.com rapidvpn.com
rarbgprx.org rarbgprx.org
@@ -2973,6 +3087,7 @@ rentry.co
renyurenquan.org renyurenquan.org
resilio.com resilio.com
resistchina.org resistchina.org
restofworld.org
retweetrank.com retweetrank.com
reuters.com reuters.com
reutersmedia.net reutersmedia.net
@@ -3009,6 +3124,8 @@ rthk.org.hk
rti.org.tw rti.org.tw
rti.tw rti.tw
rtm.tnt-ea.com rtm.tnt-ea.com
rts.ch
rule34.us
rule34.xxx rule34.xxx
rule34video.com rule34video.com
rumble.com rumble.com
@@ -3043,10 +3160,11 @@ savetibetstore.org
saveuighur.org saveuighur.org
sbme.me sbme.me
sbti.unun.dev sbti.unun.dev
scache.vzw.com
scache1.vzw.com
scache2.vzw.com
schema.org schema.org
schwab.co.uk
schwab.com
schwab.com.cn
schwab.com.hk
scmp.com scmp.com
scramble.io scramble.io
scratch.mit.edu scratch.mit.edu
@@ -3085,6 +3203,7 @@ sexinsex.net
sf.net sf.net
sfshibao.com sfshibao.com
sftuk.org sftuk.org
sgqt0j.launches.appsflyersdk.com
shadeyouvpn.com shadeyouvpn.com
shadowsocks.be shadowsocks.be
shadowsocks.com.hk shadowsocks.com.hk
@@ -3109,11 +3228,13 @@ shodanhq.com
shooshtime.com shooshtime.com
shopee.tw shopee.tw
shopping.yahoo.co.jp shopping.yahoo.co.jp
shortconn.im.qcloud.com
showwe.tw showwe.tw
shutterstock.com shutterstock.com
shwchurch.org shwchurch.org
shwchurch3.com shwchurch3.com
sidelinesnews.com sidelinesnews.com
sider.ai
signal.org signal.org
silvergatebank.com silvergatebank.com
simbolostwitter.com simbolostwitter.com
@@ -3123,6 +3244,7 @@ simplecd.org
simpleswap.io simpleswap.io
simplex.chat simplex.chat
sina.com.hk sina.com.hk
sinchew.com.my
singaporepools.com.sg singaporepools.com.sg
singlelogin.se singlelogin.se
singtao.com singtao.com
@@ -3131,6 +3253,7 @@ sinoants.com
sinoca.com sinoca.com
sinocast.com sinocast.com
sinoinsider.com sinoinsider.com
sinyalee.com
sipml5.org sipml5.org
sis001.com sis001.com
site.new site.new
@@ -3166,7 +3289,7 @@ socialblade.com
socks-proxy.net socks-proxy.net
sockslist.net sockslist.net
socrec.org socrec.org
softether-download.com softether.co.jp
softether.org softether.org
softfamous.com softfamous.com
softwarebychuck.com softwarebychuck.com
@@ -3187,6 +3310,7 @@ sorting-algorithms.com
sos.org sos.org
sosad.fun sosad.fun
sosreader.com sosreader.com
sotwe.com
soubory.com soubory.com
soulcaliburhentai.net soulcaliburhentai.net
soundcloud.com soundcloud.com
@@ -3199,6 +3323,7 @@ southmongolia.org
southnews.com.tw southnews.com.tw
southpark.cc.com southpark.cc.com
sowers.org.hk sowers.org.hk
spaceforce.mil
spaces.hightail.com spaces.hightail.com
spacex.com spacex.com
spankbang.com spankbang.com
@@ -3225,7 +3350,6 @@ springwood.me
sproutcore.com sproutcore.com
squirrelvpn.com squirrelvpn.com
ss-link.com ss-link.com
ss7.vzw.com
ssglobal.co ssglobal.co
ssl.webpack.de ssl.webpack.de
sspanel.net sspanel.net
@@ -3290,11 +3414,14 @@ supchina.com
superpages.com superpages.com
supervpn.net supervpn.net
superzooi.com superzooi.com
support.futunn.com
supremecourt.gov
suprememastertv.com suprememastertv.com
surfeasy.com surfeasy.com
surfeasy.com.au surfeasy.com.au
surfshark.com surfshark.com
surrenderat20.net surrenderat20.net
suyingtv.com
swagbucks.com swagbucks.com
swapspace.co swapspace.co
swissinfo.ch swissinfo.ch
@@ -3345,6 +3472,7 @@ tbsec.org
tbsn.org tbsn.org
tbssqh.org tbssqh.org
teachparentstech.org teachparentstech.org
techbang.com
technews.tw technews.tw
techviz.net techviz.net
teck.in teck.in
@@ -3375,6 +3503,8 @@ tfhub.dev
tfiflve.com tfiflve.com
tg-me.com tg-me.com
tg.dev tg.dev
tgstat.com
the-sun.com
theatlantic.com theatlantic.com
theatrum-belli.com theatrum-belli.com
thebcomplex.com thebcomplex.com
@@ -3397,6 +3527,7 @@ thehansindia.com
thehindu.com thehindu.com
thehun.net thehun.net
theinitium.com theinitium.com
theintercept.com
thenewslens.com thenewslens.com
thepiratebay.org thepiratebay.org
theporndude.com theporndude.com
@@ -3407,6 +3538,7 @@ thestandnews.com
thetatoken.org thetatoken.org
thetibetpost.com thetibetpost.com
thetvdb.com thetvdb.com
theweek.com
thewgo.org thewgo.org
thewirechina.com thewirechina.com
theync.com theync.com
@@ -3474,6 +3606,12 @@ tibettelegraph.com
tibettimes.net tibettimes.net
tibettruth.com tibettruth.com
tibetwrites.org tibetwrites.org
tigerbbs.cn
tigerbbs.com
tigerbrokers.com
tigerbrokers.net
tigerbrokers.nz
tigerfintech.com
tigervpn.com tigervpn.com
tiktok.com tiktok.com
tiktokcdn-eu.com tiktokcdn-eu.com
@@ -3536,6 +3674,9 @@ totalvpn.com
tou.tv tou.tv
tpi.org.tw tpi.org.tw
tracfone.com tracfone.com
trade.futunn.com
trade.longportapp.com
tradeup.com
tradingview.com tradingview.com
translate.goog translate.goog
transparency.org transparency.org
@@ -3544,6 +3685,7 @@ trendsmap.com
tronscan.org tronscan.org
trouw.nl trouw.nl
trt.net.tr trt.net.tr
trtworld.com
truebuddha-md.org truebuddha-md.org
trustwallet.com trustwallet.com
truthsocial.com truthsocial.com
@@ -3580,6 +3722,7 @@ tv.com
tv.jtbc.joins.com tv.jtbc.joins.com
tvbanywhere.com tvbanywhere.com
tvboxnow.com tvboxnow.com
tvdy1.com
tvunetworks.com tvunetworks.com
tw-blog.com tw-blog.com
tw.jiepang.com tw.jiepang.com
@@ -3638,6 +3781,7 @@ twitturk.com
twitturly.com twitturly.com
twkan.com twkan.com
twreporter.org twreporter.org
twstalker.com
twt.tl twt.tl
twtkr.com twtkr.com
twttr.com twttr.com
@@ -3666,6 +3810,7 @@ ulop.net
ultrasurf.us ultrasurf.us
ultravpn.com ultravpn.com
ultravpn.fr ultravpn.fr
umap.openstreetmap.fr
unblock-us.com unblock-us.com
unblock.cn.com unblock.cn.com
unblockdmm.com unblockdmm.com
@@ -3689,6 +3834,7 @@ upghsbc.com
upmedia.mg upmedia.mg
upornia.com upornia.com
uproxy.org uproxy.org
upsangel.com
uptodown.com uptodown.com
uraban.me uraban.me
urbandictionary.com urbandictionary.com
@@ -3736,6 +3882,7 @@ vaticannews.va
vatn.org vatn.org
vcf-online.org vcf-online.org
vcfbuilder.org vcfbuilder.org
veed.io
vegas.williamhill.com vegas.williamhill.com
vegasred.com vegasred.com
venetianmacao.com venetianmacao.com
@@ -3787,6 +3934,7 @@ vod.wwe.com
voicettank.org voicettank.org
vot.org vot.org
vovo2000.com vovo2000.com
vox.com
voxer.com voxer.com
vpl.bibliocommons.com vpl.bibliocommons.com
vpn.ac vpn.ac
@@ -3848,6 +3996,7 @@ wanz-factory.com
warroom.org warroom.org
waselpro.com waselpro.com
washingtonpost.com washingtonpost.com
washingtontimes.com
watchinese.com watchinese.com
watchmygf.net watchmygf.net
watchout.tw watchout.tw
@@ -3856,6 +4005,7 @@ wav.tv
waveprotocol.org waveprotocol.org
waybig.com waybig.com
waymo.com waymo.com
wbrks.com
wd.bible wd.bible
wealth.com.tw wealth.com.tw
wearn.com wearn.com
@@ -3865,6 +4015,7 @@ webmproject.org
webpkgcache.com webpkgcache.com
webrtc.org webrtc.org
websdr.org websdr.org
webshare.io
website.new website.new
webwarper.net webwarper.net
wechatlawsuit.com wechatlawsuit.com
@@ -3962,12 +4113,14 @@ work2icu.org
workatruna.com workatruna.com
workerempowerment.org workerempowerment.org
workers.dev workers.dev
worldjournal.com
worldvpn.net worldvpn.net
wowgirls.com wowgirls.com
wowhead.com wowhead.com
wowporn.com wowporn.com
woyaolian.org woyaolian.org
wp.com wp.com
wplace.live
wpoforum.com wpoforum.com
writesonic.com writesonic.com
wsj.com wsj.com
@@ -4032,6 +4185,7 @@ xgmyd.com
xhamster.com xhamster.com
xianjian.tw xianjian.tw
xiaohexie.com xiaohexie.com
xiaohu8.com
xiaolan.me xiaolan.me
xiaoma.org xiaoma.org
xiaomi.eu xiaomi.eu
@@ -4056,6 +4210,7 @@ xn--noss43i.com
xn--oiq.cc xn--oiq.cc
xn--p8j9a0d9c9a.xn--q9jyb4c xn--p8j9a0d9c9a.xn--q9jyb4c
xn--u2u927b.com xn--u2u927b.com
xn--vuqv2cf7wzyig79c.com
xnpool.com xnpool.com
xnxx.com xnxx.com
xpud.org xpud.org
@@ -4064,6 +4219,7 @@ xskywalker.com
xt.com xt.com
xt.pub xt.pub
xtube.com xtube.com
xuan.com.my
xuchao.net xuchao.net
xuchao.org xuchao.org
xuehua.us xuehua.us
@@ -4102,7 +4258,6 @@ yeyeclub.com
yfsp.tv yfsp.tv
yhcw.net yhcw.net
yibaochina.com yibaochina.com
yidio.com
yigeni.com yigeni.com
yipub.com yipub.com
yizhihongxing.com yizhihongxing.com
@@ -4165,11 +4320,14 @@ zh.ecdm.wikia.com
zh.pokerstrategy.com zh.pokerstrategy.com
zh.wikiquote.org zh.wikiquote.org
zhangtianliang.com zhangtianliang.com
zhangzhehan.net
zhanlve.org zhanlve.org
zhao.1984.city zhao.1984.city
zhengjian.org zhengjian.org
zhengwunet.org zhengwunet.org
zhenxiang.biz zhenxiang.biz
zhijianfengyi.cn
zhijianfengyi.com
zhizhu.top zhizhu.top
zhongguo.ca zhongguo.ca
zhongguotese.net zhongguotese.net
@@ -1 +1 @@
202604232238 202607052256
@@ -52,12 +52,6 @@ const dns_port = uci.get(uciconfig, uciinfra, 'dns_port') || '5333';
const ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server') || 'time.apple.com'; const ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server') || 'time.apple.com';
const dashboard_enabled = uci.get(uciconfig, ucimain, 'dashboard_enabled') || '0';
const dashboard_port = uci.get(uciconfig, ucimain, 'dashboard_port') || '9090';
const dashboard_secret = uci.get(uciconfig, ucimain, 'dashboard_secret');
const dashboard_url = uci.get(uciconfig, ucimain, 'dashboard_url');
const dashboard_detour = uci.get(uciconfig, ucimain, 'dashboard_detour');
const ipv6_support = uci.get(uciconfig, ucimain, 'ipv6_support') || '0'; const ipv6_support = uci.get(uciconfig, ucimain, 'ipv6_support') || '0';
let main_node, main_udp_node, dedicated_udp_node, default_outbound, default_outbound_dns, let main_node, main_udp_node, dedicated_udp_node, default_outbound, default_outbound_dns,
@@ -127,10 +121,10 @@ if (match(proxy_mode, /redirect/)) {
self_mark = uci.get(uciconfig, 'infra', 'self_mark') || '100'; self_mark = uci.get(uciconfig, 'infra', 'self_mark') || '100';
redirect_port = uci.get(uciconfig, 'infra', 'redirect_port') || '5331'; redirect_port = uci.get(uciconfig, 'infra', 'redirect_port') || '5331';
} }
if (match(proxy_mode), /tproxy/) if (match(proxy_mode, /tproxy/))
if (main_udp_node !== 'nil' || routing_mode === 'custom') if (main_udp_node !== 'nil' || routing_mode === 'custom')
tproxy_port = uci.get(uciconfig, 'infra', 'tproxy_port') || '5332'; tproxy_port = uci.get(uciconfig, 'infra', 'tproxy_port') || '5332';
if (match(proxy_mode), /tun/) { if (match(proxy_mode, /tun/)) {
tun_name = uci.get(uciconfig, uciinfra, 'tun_name') || 'singtun0'; tun_name = uci.get(uciconfig, uciinfra, 'tun_name') || 'singtun0';
tun_addr4 = uci.get(uciconfig, uciinfra, 'tun_addr4') || '172.19.0.1/30'; tun_addr4 = uci.get(uciconfig, uciinfra, 'tun_addr4') || '172.19.0.1/30';
tun_addr6 = uci.get(uciconfig, uciinfra, 'tun_addr6') || 'fdfe:dcba:9876::1/126'; tun_addr6 = uci.get(uciconfig, uciinfra, 'tun_addr6') || 'fdfe:dcba:9876::1/126';
@@ -930,7 +924,6 @@ if (!isEmpty(main_node)) {
user: cfg.user, user: cfg.user,
rule_set: get_ruleset(cfg.rule_set), rule_set: get_ruleset(cfg.rule_set),
rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source), rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source),
rule_set_ip_cidr_accept_empty: strToBool(cfg.rule_set_ip_cidr_accept_empty),
invert: strToBool(cfg.invert), invert: strToBool(cfg.invert),
action: cfg.action, action: cfg.action,
outbound: get_outbound(cfg.outbound), outbound: get_outbound(cfg.outbound),
@@ -976,28 +969,6 @@ if (routing_mode in ['bypass_mainland_china', 'custom']) {
} }
}; };
} }
if (dashboard_enabled === '1') {
if (!config.experimental)
config.experimental = {};
let download_detour = 'direct-out';
if (!isEmpty(dashboard_detour)) {
if (dashboard_detour in ['direct-out', 'main-out'])
download_detour = dashboard_detour;
else
download_detour = get_outbound(dashboard_detour);
}
config.experimental.clash_api = {
external_controller: '0.0.0.0:' + dashboard_port,
secret: !isEmpty(dashboard_secret) ? dashboard_secret : null,
external_ui: RUN_DIR + '/ui',
external_ui_download_url: !isEmpty(dashboard_url) ? dashboard_url :
'https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn.zip',
external_ui_download_detour: download_detour,
};
}
/* Experimental end */ /* Experimental end */
system('mkdir -p ' + RUN_DIR); system('mkdir -p ' + RUN_DIR);
@@ -8,7 +8,7 @@
'use strict'; 'use strict';
import { cursor } from 'uci'; import { cursor } from 'uci';
import { isEmpty, parseURL } from 'homeproxy'; import { isEmpty, parseURL, validation } from 'homeproxy';
const uci = cursor(); const uci = cursor();
@@ -137,12 +137,7 @@ const methods = {
break; break;
} }
let start = clock(true); return { result: (system(`/usr/bin/wget --spider -qT3 ${url} 2>"/dev/null"`, 3100) === 0) };
let ret = system(`/usr/bin/wget --spider -qT3 ${url} 2>"/dev/null"`, 3100);
let end = clock(true);
let elapsed = (end[0] - start[0]) * 1000 + int((end[1] - start[1]) / 1000000);
return { result: (ret === 0), elapsed: elapsed };
} }
}, },
+1 -1
View File
@@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2 PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.7.16 PKG_VERSION:=26.7.16
PKG_RELEASE:=64 PKG_RELEASE:=65
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \ PKG_CONFIG_DEPENDS:= \
@@ -70,6 +70,7 @@ function index()
entry({"admin", "services", appname, "get_now_use_node"}, call("get_now_use_node")).leaf = true entry({"admin", "services", appname, "get_now_use_node"}, call("get_now_use_node")).leaf = true
entry({"admin", "services", appname, "get_redir_log"}, call("get_redir_log")).leaf = true entry({"admin", "services", appname, "get_redir_log"}, call("get_redir_log")).leaf = true
entry({"admin", "services", appname, "get_socks_log"}, call("get_socks_log")).leaf = true entry({"admin", "services", appname, "get_socks_log"}, call("get_socks_log")).leaf = true
entry({"admin", "services", appname, "get_acl_log"}, call("get_acl_log")).leaf = true
entry({"admin", "services", appname, "get_log"}, call("get_log")).leaf = true entry({"admin", "services", appname, "get_log"}, call("get_log")).leaf = true
entry({"admin", "services", appname, "clear_log"}, call("clear_log")).leaf = true entry({"admin", "services", appname, "clear_log"}, call("clear_log")).leaf = true
entry({"admin", "services", appname, "index_status"}, call("index_status")).leaf = true entry({"admin", "services", appname, "index_status"}, call("index_status")).leaf = true
@@ -322,6 +323,18 @@ function get_socks_log()
end end
end end
function get_acl_log()
local id = http.formvalue("id")
local path = "/tmp/log/passwall2_acl_" .. id .. ".log"
if nixio.fs.access(path) then
local content = luci.sys.exec("tail -n 5000 '" .. path .. "'")
content = content:gsub("\n", "<br />")
http.write(content)
else
http.write(string.format("<script>alert('%s');window.close();</script>", i18n.translate("Not enabled log")))
end
end
function get_log() function get_log()
-- luci.sys.exec("[ -f /tmp/log/passwall2.log ] && sed '1!G;h;$!d' /tmp/log/passwall2.log > /tmp/log/passwall2_show.log") -- luci.sys.exec("[ -f /tmp/log/passwall2.log ] && sed '1!G;h;$!d' /tmp/log/passwall2.log > /tmp/log/passwall2_show.log")
http.write(luci.sys.exec("[ -f '/tmp/log/passwall2.log' ] && cat /tmp/log/passwall2.log")) http.write(luci.sys.exec("[ -f '/tmp/log/passwall2.log' ] && cat /tmp/log/passwall2.log"))
@@ -78,6 +78,33 @@ o = s:option(Value, "remarks", translate("Remarks"))
o.default = arg[1] o.default = arg[1]
o.rmempty = false o.rmempty = false
---- Log
o = s:option(Flag, "log", translate("Enable Log"))
o.default = 0
o.rmempty = false
o = s:option(ListValue, "loglevel", translate("Log Level"))
o.default = "warn"
o:value("debug", "Debug")
o:value("info", "Info")
o:value("warn", "Warning")
o:value("error", "Error")
o:depends("log", "1")
o = s:option(DummyValue, "_acl_log", translate("Log File"))
o.rawhtml = true
o.cfgvalue = function(t, n)
local log_path = "/tmp/log/passwall2_acl_" .. arg[1] .. ".log"
local log_url = api.url("get_acl_log") .. "?id=" .. arg[1]
return string.format(
'<code>%s</code>&nbsp;&nbsp;<input class="btn cbi-button cbi-button-apply" type="button" value="%s" onclick="window.open(\'%s\', \'_blank\')" />',
log_path,
translate("View Log"),
log_url
)
end
o:depends("log", "1")
o = s:option(Value, "interface", translate("Source Interface")) o = s:option(Value, "interface", translate("Source Interface"))
o:value("", translate("All")) o:value("", translate("All"))
local iface = api.get_network_devices() local iface = api.get_network_devices()
@@ -1021,11 +1021,15 @@ acl_app() {
dnsmasq_port=${GLOBAL_DNSMASQ_PORT:-11400} dnsmasq_port=${GLOBAL_DNSMASQ_PORT:-11400}
for item in $items; do for item in $items; do
index=$(expr $index + 1) index=$(expr $index + 1)
local enabled sid remarks sources interface tcp_no_redir_ports udp_no_redir_ports node direct_dns_query_strategy remote_dns_protocol remote_dns remote_dns_doh remote_dns_client_ip remote_dns_detour remote_fakedns remote_dns_query_strategy local enabled sid remarks sources interface tcp_no_redir_ports udp_no_redir_ports node direct_dns_query_strategy remote_dns_protocol remote_dns remote_dns_doh remote_dns_client_ip remote_dns_detour remote_fakedns remote_dns_query_strategy log loglevel log_file
local _ip _mac _iprange _ipset _ip_or_mac source_list config_file local _ip _mac _iprange _ipset _ip_or_mac source_list config_file
local sid=$(uci -q show "${CONFIG}.${item}" | grep "=acl_rule" | awk -F '=' '{print $1}' | awk -F '.' '{print $2}') local sid=$(uci -q show "${CONFIG}.${item}" | grep "=acl_rule" | awk -F '=' '{print $1}' | awk -F '.' '{print $2}')
[ "$(config_n_get $sid enabled)" = "1" ] || continue [ "$(config_n_get $sid enabled)" = "1" ] || continue
eval $(uci -q show "${CONFIG}.${item}" | cut -d'.' -sf 3-) eval $(uci -q show "${CONFIG}.${item}" | cut -d'.' -sf 3-)
log=${log:-0}
loglevel=${loglevel:-warn}
log_file="/dev/null"
[ "${log}" = "1" ] && log_file="/tmp/log/passwall2_acl_${sid}.log"
if [ -n "${sources}" ]; then if [ -n "${sources}" ]; then
for s in $sources; do for s in $sources; do
@@ -1121,7 +1125,8 @@ acl_app() {
direct_dns_query_strategy=${direct_dns_query_strategy} \ direct_dns_query_strategy=${direct_dns_query_strategy} \
remote_dns_protocol=${remote_dns_protocol} remote_dns_tcp_server=${remote_dns} remote_dns_udp_server=${remote_dns} remote_dns_doh="${remote_dns}" \ remote_dns_protocol=${remote_dns_protocol} remote_dns_tcp_server=${remote_dns} remote_dns_udp_server=${remote_dns} remote_dns_doh="${remote_dns}" \
remote_dns_client_ip=${remote_dns_client_ip} remote_dns_detour=${remote_dns_detour} remote_fakedns=${remote_fakedns} remote_dns_query_strategy=${remote_dns_query_strategy} \ remote_dns_client_ip=${remote_dns_client_ip} remote_dns_detour=${remote_dns_detour} remote_fakedns=${remote_fakedns} remote_dns_query_strategy=${remote_dns_query_strategy} \
config_file=${config_file} config_file=${config_file} \
log_file="${log_file}" loglevel="${loglevel}"
local status=$? local status=$?
if [ "$status" != 0 ]; then if [ "$status" != 0 ]; then
log_i18n 2 "[%s] process %s error, skip this transparent proxy!" "${remarks}" "${config_file}" log_i18n 2 "[%s] process %s error, skip this transparent proxy!" "${remarks}" "${config_file}"
@@ -194,7 +194,7 @@ return view.extend({
o.value('0', _('Disable')) o.value('0', _('Disable'))
if (features.hasXTFULLCONENAT || features.hasNFTFULLCONENAT) { if (features.hasXTFULLCONENAT || features.hasNFTFULLCONENAT) {
o.value('1', _('FULLCONENAT')); o.value('1', _('FULLCONENAT'));
o.value('2', _('Boardcom Fullcone NAT1')); o.value('2', _('Broadcom Fullcone NAT1'));
} }
o.default = '0'; o.default = '0';
o.rmempty = false; o.rmempty = false;
+2 -2
View File
@@ -5,8 +5,8 @@ msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Acceleration Status" msgid "Acceleration Status"
msgstr "" msgstr ""
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:182 #: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:197
msgid "Boardcom fullcone" msgid "Broadcom Fullcone NAT1"
msgstr "" msgstr ""
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:151 #: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:151
+3 -11
View File
@@ -12,10 +12,6 @@ msgstr ""
msgid "Acceleration Status" msgid "Acceleration Status"
msgstr "加速状态" msgstr "加速状态"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:182
msgid "Boardcom fullcone"
msgstr "Boardcom fullcone"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:151 #: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:151
msgid "Bridge Acceleration" msgid "Bridge Acceleration"
msgstr "桥接加速" msgstr "桥接加速"
@@ -93,9 +89,9 @@ msgstr "全锥形 NAT1"
msgid "FULLCONENAT" msgid "FULLCONENAT"
msgstr "兼容模式全锥形 NAT1" msgstr "兼容模式全锥形 NAT1"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:177 #: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:197
msgid "Boardcom Fullcone NAT1" msgid "Broadcom Fullcone NAT1"
msgstr "高性能 Boardcom 全锥形 NAT1" msgstr "高性能 Broadcom 全锥形 NAT1"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:178 #: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:178
msgid "Full cone NAT (NAT1) can improve gaming performance effectively." msgid "Full cone NAT (NAT1) can improve gaming performance effectively."
@@ -175,7 +171,3 @@ msgstr "Turbo ACC 网络加速设置"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:181 #: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:181
msgid "xt_FULLCONENAT" msgid "xt_FULLCONENAT"
msgstr "兼容模式" msgstr "兼容模式"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:182
msgid "Boardcom fullcone"
msgstr "高性能模式"
@@ -104,9 +104,9 @@ local methods = {
(readfile("/sys/module/nft_fullcone/refcnt") or "0") ~= "0" then (readfile("/sys/module/nft_fullcone/refcnt") or "0") ~= "0" then
fctype = "nft_FULLCONENAT" fctype = "nft_FULLCONENAT"
elseif sys.call("nft list ruleset 2>/dev/null | grep -q 'masquerade fullcone'") == 0 then elseif sys.call("nft list ruleset 2>/dev/null | grep -q 'masquerade fullcone'") == 0 then
fctype = "Boardcom Fullcone NAT1" fctype = "Broadcom Fullcone NAT1"
elseif sys.call("iptables -t nat -L zone_wan_postrouting 2>/dev/null | grep -q fullcone") == 0 then elseif sys.call("iptables -t nat -L zone_wan_postrouting 2>/dev/null | grep -q fullcone") == 0 then
fctype = "Boardcom Fullcone NAT1" fctype = "Broadcom Fullcone NAT1"
end end
print(json.stringify({ type = fctype })) print(json.stringify({ type = fctype }))
end end
@@ -45,7 +45,7 @@ label, fixing a typo, adjusting a translation string).
- **S4** — Memory has a ceiling. → references/server.md - **S4** — Memory has a ceiling. → references/server.md
- **L1** — Kill render-blocking requests. → references/loading.md - **L1** — Kill render-blocking requests. → references/loading.md
- **L2** — Download once, cache forever. → references/loading.md - **L2** — Download once, cache forever. → references/loading.md
- **L3** — Ship compressed. → references/loading.md - **L3** — Make identity-transfer assets compact. → references/loading.md
- **R1** — Animate compositor properties only. → references/runtime.md - **R1** — Animate compositor properties only. → references/runtime.md
- **R2** — JS must not force synchronous layout. → references/runtime.md - **R2** — JS must not force synchronous layout. → references/runtime.md
- **R3** — Accessibility is functionality. → references/runtime.md - **R3** — Accessibility is functionality. → references/runtime.md
@@ -11,8 +11,12 @@ address, so they stay local).
| Metric | Budget | Track | Source | | Metric | Budget | Track | Source |
|---|---|---|---| |---|---|---|---|
| main.css (gzip-transferred) | ≤ 30 KB | size | measured 2026-07 (28 KB) | | main.css (identity/raw) | ≤ 190 KB | size | production build, 2026-07 (183,820 B) |
| Per-page cold transfer (all theme assets, gzip) | ≤ 60 KB | size | sum of current gzip sizes + headroom | | login.css (identity/raw) | ≤ 12 KB | size | production build, 2026-07 (10,935 B, token-pruned) |
| menu-aurora.js (identity/raw) | ≤ 20 KB | size | production build, 2026-07 (19,100 B) |
| Default logo (identity/raw) | ≤ 16 KB | size | production build, 2026-07 (15,057 B) |
| Core admin cold theme assets (identity/raw) | ≤ 250 KB | size | main CSS + menu JS + default font + logo, 2026-07 (241,557 B) |
| Login cold theme assets, excluding configured background (identity/raw) | ≤ 55 KB | size | login CSS + default font + logo, 2026-07 (49,572 B) |
| Blocking requests before first paint | ≤ 4 | count | current waterfall | | Blocking requests before first paint | ≤ 4 | count | current waterfall |
| Repeat-visit asset requests | ≈ 0 | count | target state; package-built CSS/JS URLs are versioned, but long-lived cache headers still need live verification | | Repeat-visit asset requests | ≈ 0 | count | target state; package-built CSS/JS URLs are versioned, but long-lived cache headers still need live verification |
| TTFB, login page (device) | proposed: ≤ 130 ms | latency | local device baseline, 2026-07 | | TTFB, login page (device) | proposed: ≤ 130 ms | latency | local device baseline, 2026-07 |
@@ -25,16 +29,22 @@ Budget revisions require a new baseline entry under `../baselines/`.
## Optimization ledger ## Optimization ledger
### Landed ### Landed
(compositor animation rework; mega-menu idle pre-measurement; on-demand
patches; `font-display: swap`) - Compositor animation rework; mega-menu idle pre-measurement; on-demand
patches; `font-display: swap` and inline `@font-face` CSS.
- Tailwind `source(none)`, login-only Preflight removal, native scrollbar
styling, and local fade animation; unused CSS plugins removed.
- Shared SVG custom properties prevent repeated mask payloads.
- Terser compression/mangling with LuCI loader directives preserved.
- Default logo raster resized inside its compatibility SVG wrapper.
- Login template reuses its board/UCI reads when including `header.ut`.
- Package-root `.DS_Store` metadata removed and covered by a regression test.
- login.css pruned to its reachable custom properties at build time (the
shared token sheet is admin-sized; the login page consumes a fraction).
### Pending ### Pending
| Item | Principle | Estimated gain | | Item | Principle | Estimated gain |
|---|---|---| |---|---|---|
| Precompressed `.gz` assets | S1+L3 | ~260 KB → ~35 KB cold |
| Terser `compress`+`mangle` in `vite.config.ts` | L3 | ~20 KB → ~10 KB |
| Inline `@font-face` + preload woff2 | L1 | −1 blocking RTT |
| SVGO `logo.svg` | L3 | 45 KB → est. < 20 KB |
| Long-lived cache headers for versioned CSS/JS | L2 | after LuCI build-time `?v=$(PKG_VERSION)`, kills per-click 304s if headers permit disk/memory cache reuse | | Long-lived cache headers for versioned CSS/JS | L2 | after LuCI build-time `?v=$(PKG_VERSION)`, kills per-click 304s if headers permit disk/memory cache reuse |
| `defer` head scripts | L1 | needs on-device timing verification | | `defer` head scripts | L1 | needs on-device timing verification |
@@ -50,6 +60,9 @@ patches; `font-display: swap`)
`/luci-static/aurora/login.css?v=1.0.7`. Do not re-propose manual `/luci-static/aurora/login.css?v=1.0.7`. Do not re-propose manual
cache-versioning for these links unless inspecting the installed package cache-versioning for these links unless inspecting the installed package
or live HTML proves the rewrite did not happen. or live HTML proves the rewrite did not happen.
- **No gzip on the target uhttpd** — budgets and the bench harness use raw
identity bytes. Do not add precompressed sidecars unless the deployed HTTP
server is changed and live response headers prove negotiation works.
### Accepted exceptions ### Accepted exceptions
@@ -54,21 +54,23 @@ served from disk/memory cache).
**Quantify.** Repeat-visit request count. **Quantify.** Repeat-visit request count.
## L3 — Ship compressed ## L3 — Make identity-transfer assets compact
**Why.** An uncompressed asset costs both wire time on the slow link (see **Why.** This deployment's stock `uhttpd` serves identity responses even when
The Wire) and CPU on the slow server (see server.md S1) — compression is the the browser advertises gzip. Raw output size is therefore both flash cost and
one change that helps both planes at once. wire cost; minification and dead-code removal must do the work that transfer
compression normally would.
**Do / Don't.** Precompress at build time so `uhttpd` can serve `foo.gz` **Do / Don't.** Disable broad Tailwind content scanning when all utilities are
verbatim (cross-reference server.md S1). Run shipped SVGs through SVGO declared with `@apply`; omit Preflight from isolated entries that only need a
(example: aurora's `logo.svg`, which doubles as the favicon, ships at 45 KB — small reset; remove unused plugins; minify and mangle JS while preserving
an unoptimized SVG is a common place for dead editor metadata to hide). LuCI's loader directives; define repeated SVG data URLs once as custom
Choose sane image formats (WebP/AVIF over unoptimized PNG/JPEG) for anything properties. Resize or re-encode raster assets at their real display needs and
that isn't vector. keep generated metadata out of package roots. Do not ship precompressed
sidecars when the target server cannot negotiate them.
**Verify.** `bench.mjs`'s gzip-negotiated rows; `du` on the build output **Verify.** `bench.mjs`'s identity rows, automated raw-size budgets, and `du`
directory to catch anything that grew. on the build output directory to catch anything that grew.
**Quantify.** Per-page transferred bytes (cold load); individual asset **Quantify.** Per-page transferred bytes (cold load); individual asset
sizes. sizes.
@@ -79,5 +81,5 @@ sizes.
|---|---|---| |---|---|---|
| Blocking requests before first paint | DevTools Network (Slow 4G) | `aurora-budgets.md` | | Blocking requests before first paint | DevTools Network (Slow 4G) | `aurora-budgets.md` |
| Repeat-visit request count | DevTools Network | `aurora-budgets.md` | | Repeat-visit request count | DevTools Network | `aurora-budgets.md` |
| Transferred bytes (cold, gzip) | `bench.mjs` | `aurora-budgets.md` | | Transferred bytes (cold, identity/raw) | `bench.mjs` | `aurora-budgets.md` |
| Individual asset size | `du` / `bench.mjs` | `aurora-budgets.md` | | Individual asset size | `du` / `bench.mjs` | `aurora-budgets.md` |
@@ -23,15 +23,19 @@ or returns an HTTP error status mid-run, it aborts with a one-line message
naming the target that failed — it does not silently skip it. naming the target that failed — it does not silently skip it.
**What it measures.** `BENCH_RUNS` (default 10) samples per target via **What it measures.** `BENCH_RUNS` (default 10) samples per target via
`curl`, reduced to a median, against 5 fixed targets: `curl`, reduced to a median, against 6 fixed targets. All static assets use
identity transfer because the target `uhttpd` cannot serve gzip:
1. `GET /cgi-bin/luci` (the login page) — the honest unauthenticated target: 1. `GET /cgi-bin/luci` (the login page) — the honest unauthenticated target:
it's the one page a bench script can hit without a session, and TTFB it's the one page a bench script can hit without a session, and TTFB
comparisons must compare like with like. comparisons must compare like with like.
2. `main.css`, identity (no `Accept-Encoding`) A 403 is accepted for this target only because some LuCI builds render the
3. `main.css`, gzip-negotiated login form with that status; other error responses still abort the run.
4. `login.css`, gzip-negotiated 2. `main.css`, identity
5. the theme's menu script, gzip-negotiated (example: aurora's `menu-aurora.js`) 3. `login.css`, identity
4. the theme's menu script, identity (example: aurora's `menu-aurora.js`)
5. the default first-paint font, identity
6. the default logo, identity
Per target it reports TTFB, total time, transferred bytes, and HTTP status, Per target it reports TTFB, total time, transferred bytes, and HTTP status,
summarized as a Markdown table on stdout. summarized as a Markdown table on stdout.
@@ -11,20 +11,18 @@ same 1–2 cores and 64–512 MB of RAM as the rest of OpenWrt. Nothing here is
is already slow. Anything that can be computed once, at build time, should is already slow. Anything that can be computed once, at build time, should
never run again inside a request handler. never run again inside a request handler.
**Do / Don't.** Precompress assets at build time and let `uhttpd` serve the **Do / Don't.** Generate and minify derived CSS, JS, config, tokens and image
precompressed file verbatim when the client sends `Accept-Encoding: gzip` — assets at build time, never per-request (example: aurora's generated
this makes the request-time cost *decrease*, since `uhttpd` has no dynamic `_tokens.css`). This deployment uses stock `uhttpd` without gzip support, so
compression of its own. Generate derived CSS/config/tokens at build time, raw file size is the wire size: do not add `.gz` files that the server will
never per-request (example: aurora's generated `_tokens.css`, built from never select. When a genuinely per-request computation is unavoidable,
`.dev/tokens/` and never touched at runtime). When a genuinely per-request record the accepted exception with its rationale in the theme budget sheet
computation is unavoidable, record the accepted exception with its rationale (`aurora-budgets.md`) so a future session doesn't re-litigate it (example:
in the theme budget sheet (`aurora-budgets.md`) so a future session doesn't re-litigate it aurora's per-request `lsdir()` patch discovery in `header.ut` — one cheap
(example: aurora's per-request `lsdir()` patch discovery in `header.ut` — one readdir, measured negligible and accepted as-is).
cheap readdir, measured negligible and accepted as-is).
**Verify.** `top` on the device shows no request-time compression process; **Verify.** A production build is deterministic and already minified; the
`curl -H 'Accept-Encoding: gzip' -w '%{size_download}'` against the asset identity-transfer rows in `bench.mjs` match the files' raw byte sizes.
shows the byte drop versus an uncompressed request.
**Quantify.** Transferred bytes; package/flash size delta. **Quantify.** Transferred bytes; package/flash size delta.
@@ -55,10 +53,10 @@ the branch against the base.
slow link, and it costs it on every page view — this is not "just bandwidth," slow link, and it costs it on every page view — this is not "just bandwidth,"
it's compute on the same constrained cores serving the request. it's compute on the same constrained cores serving the request.
**Do / Don't.** Set (and respect) a per-page transferred-bytes budget. **Do / Don't.** Set (and respect) a per-page transferred-bytes budget. On this
Prefer gzip-negotiated transfer over raw bytes wherever the client supports stock `uhttpd` deployment, measure identity responses and optimize the raw
it (example: aurora's `main.css` is 211,725 B raw but ~28 KB gzip — always files themselves: restrict Tailwind source detection, remove unused framework
measure and budget the negotiated size, not the raw one). layers/plugins, minify JS, deduplicate inlined SVGs, and resize raster assets.
**Verify.** The `bench.mjs` bytes column, compared against the theme's own **Verify.** The `bench.mjs` bytes column, compared against the theme's own
budget table. budget table.
@@ -85,5 +83,5 @@ after the change.
|---|---|---| |---|---|---|
| TTFB | `bench.mjs` | `aurora-budgets.md` | | TTFB | `bench.mjs` | `aurora-budgets.md` |
| ubus/uci/fs call count | diff review | `aurora-budgets.md` | | ubus/uci/fs call count | diff review | `aurora-budgets.md` |
| Transferred bytes (gzip) | `bench.mjs` | `aurora-budgets.md` | | Transferred bytes (identity/raw) | `bench.mjs` | `aurora-budgets.md` |
| uhttpd VmRSS | `bench.mjs` device report | `aurora-budgets.md` | | uhttpd VmRSS | `bench.mjs` device report | `aurora-budgets.md` |
@@ -20,8 +20,12 @@ export function parseCurlMetrics(line) {
}; };
} }
export function assertOkStatus(sample) { export function assertOkStatus(sample, allowedStatuses = []) {
if (!Number.isInteger(sample.status) || sample.status < 200 || sample.status >= 400) { if (
!Number.isInteger(sample.status) ||
(!(sample.status >= 200 && sample.status < 400) &&
!allowedStatuses.includes(sample.status))
) {
throw new Error(`HTTP ${sample.status}`); throw new Error(`HTTP ${sample.status}`);
} }
return sample; return sample;
@@ -44,6 +44,11 @@ test("assertOkStatus: rejects 404 samples", () => {
); );
}); });
test("assertOkStatus: permits an explicitly expected login-page 403", () => {
const sample = parseCurlMetrics("0.010000\t0.020000\t5788\t403\n");
assert.equal(assertOkStatus(sample, [403]), sample);
});
test("summarize: medians of times, first sample's bytes/status, run count", () => { test("summarize: medians of times, first sample's bytes/status, run count", () => {
const s = summarize([ const s = summarize([
{ ttfbMs: 50, totalMs: 200, bytes: 1000, status: 200 }, { ttfbMs: 50, totalMs: 200, bytes: 1000, status: 200 },
@@ -49,11 +49,19 @@ const CURL_FORMAT = "%{time_starttransfer}\\t%{time_total}\\t%{size_download}\\t
// unauthenticated bench can measure, and TTFB comparisons must compare like // unauthenticated bench can measure, and TTFB comparisons must compare like
// with like (spec: error handling / edge cases). // with like (spec: error handling / edge cases).
const TARGETS = [ const TARGETS = [
{ label: "GET /cgi-bin/luci (login page)", path: "/cgi-bin/luci" }, {
label: "GET /cgi-bin/luci (login page)",
path: "/cgi-bin/luci",
allowedStatuses: [403],
},
{ label: "main.css (identity)", path: "/luci-static/aurora/main.css" }, { label: "main.css (identity)", path: "/luci-static/aurora/main.css" },
{ label: "main.css (gzip negotiated)", path: "/luci-static/aurora/main.css", gzip: true }, { label: "login.css (identity)", path: "/luci-static/aurora/login.css" },
{ label: "login.css (gzip negotiated)", path: "/luci-static/aurora/login.css", gzip: true }, { label: "menu-aurora.js (identity)", path: "/luci-static/resources/menu-aurora.js" },
{ label: "menu-aurora.js (gzip negotiated)", path: "/luci-static/resources/menu-aurora.js", gzip: true }, {
label: "Lato regular font (identity)",
path: "/luci-static/aurora/fonts/lato-v24-latin-regular.woff2",
},
{ label: "logo.svg (identity)", path: "/luci-static/aurora/images/logo.svg" },
]; ];
async function measureOnce(target) { async function measureOnce(target) {
@@ -63,10 +71,12 @@ async function measureOnce(target) {
"-w", CURL_FORMAT, "-w", CURL_FORMAT,
"--max-time", "30", "--max-time", "30",
]; ];
if (target.gzip) args.push("-H", "Accept-Encoding: gzip");
args.push(`${HOST}${target.path}`); args.push(`${HOST}${target.path}`);
const { stdout } = await execFileAsync("curl", args); const { stdout } = await execFileAsync("curl", args);
return assertOkStatus(parseCurlMetrics(stdout)); return assertOkStatus(
parseCurlMetrics(stdout),
target.allowedStatuses ?? [],
);
} }
async function measureTarget(target) { async function measureTarget(target) {
+2 -2
View File
@@ -103,8 +103,8 @@ This will be compiled to standard CSS that works in all browsers.
The theme has two independent Tailwind CSS v4 entry points, both sourced from `.dev/src/media/`: The theme has two independent Tailwind CSS v4 entry points, both sourced from `.dev/src/media/`:
- **`main.css`** — the LuCI admin UI. It contains no rules of its own; it's a pure import manifest that pulls in (in order) `_tokens.css` (OKLCH theme tokens, mapped via `@theme inline`), `_base.css`, `_elements.css`, `_layout.css`, every file in `components/` (one partial per UI component — buttons, cards, modals, tables, etc.), and `_utilities.css`. - **`main.css`** — the LuCI admin UI. It is an import manifest that disables Tailwind's automatic source scan (`source(none)`) and pulls in (in order) `_tokens.css` (OKLCH theme tokens, mapped via `@theme inline`), shared `_icons.css`, `_base.css`, `_elements.css`, `_layout.css`, every file in `components/` (one partial per UI component — buttons, cards, modals, tables, etc.), and `_utilities.css`.
- **`login.css`** — the standalone login page (`sysauth.ut`). Self-contained: imports Tailwind and `_tokens.css` directly. - **`login.css`** — the standalone login page (`sysauth.ut`). Self-contained: imports Tailwind theme/utilities with `source(none)`, omits full Preflight in favor of a tiny local reset, and imports `_tokens.css` directly. At build time the `login-css-prune` plugin (`vite.config.ts`) strips every custom property the page's var() chains never reach, so the admin-sized token sheet ships login-sized.
Third-party compatibility patches are **not** bundled into `main.css` — they are split into per-page files under `media/patches/` and loaded on demand (see [On-Demand Third-Party Patches](#on-demand-third-party-patches) below). Third-party compatibility patches are **not** bundled into `main.css` — they are split into per-page files under `media/patches/` and loaded on demand (see [On-Demand Third-Party Patches](#on-demand-third-party-patches) below).
-2
View File
@@ -22,10 +22,8 @@
"lightningcss": "^1.32.0", "lightningcss": "^1.32.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.7.0", "prettier-plugin-tailwindcss": "^0.7.0",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"terser": "^5.43.1", "terser": "^5.43.1",
"tw-animate-css": "^1.4.0",
"vite": "^7.1.1" "vite": "^7.1.1"
}, },
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be" "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
-52
View File
@@ -32,18 +32,12 @@ importers:
prettier-plugin-tailwindcss: prettier-plugin-tailwindcss:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.2(prettier@3.8.1) version: 0.7.2(prettier@3.8.1)
tailwind-scrollbar:
specifier: ^4.0.2
version: 4.0.2(react@19.1.1)(tailwindcss@4.3.2)
tailwindcss: tailwindcss:
specifier: ^4.1.11 specifier: ^4.1.11
version: 4.3.2 version: 4.3.2
terser: terser:
specifier: ^5.43.1 specifier: ^5.43.1
version: 5.46.0 version: 5.46.0
tw-animate-css:
specifier: ^1.4.0
version: 1.4.0
vite: vite:
specifier: ^7.1.1 specifier: ^7.1.1
version: 7.3.1(@types/node@24.10.9)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0) version: 7.3.1(@types/node@24.10.9)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)
@@ -466,9 +460,6 @@ packages:
'@types/node@24.10.9': '@types/node@24.10.9':
resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==}
'@types/prismjs@1.26.5':
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
acorn@8.15.0: acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
@@ -490,10 +481,6 @@ packages:
caniuse-lite@1.0.30001799: caniuse-lite@1.0.30001799:
resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
colorjs.io@0.6.1: colorjs.io@0.6.1:
resolution: {integrity: sha512-8lyR2wHzuIykCpqHKgluGsqQi5iDm3/a2IgP2GBZrasn2sBRkE4NOGsglZxWLs/jZQoNkmA/KM/8NV16rLUdBg==} resolution: {integrity: sha512-8lyR2wHzuIykCpqHKgluGsqQi5iDm3/a2IgP2GBZrasn2sBRkE4NOGsglZxWLs/jZQoNkmA/KM/8NV16rLUdBg==}
@@ -698,15 +685,6 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
prism-react-renderer@2.4.1:
resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
peerDependencies:
react: '>=16.0.0'
react@19.1.1:
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
rollup@4.57.1: rollup@4.57.1:
resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -723,12 +701,6 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
tailwind-scrollbar@4.0.2:
resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
engines: {node: '>=12.13.0'}
peerDependencies:
tailwindcss: 4.x
tailwindcss@4.3.2: tailwindcss@4.3.2:
resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==} resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==}
@@ -745,9 +717,6 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tw-animate-css@1.4.0:
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
undici-types@7.16.0: undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@@ -1054,8 +1023,6 @@ snapshots:
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
'@types/prismjs@1.26.5': {}
acorn@8.15.0: {} acorn@8.15.0: {}
baseline-browser-mapping@2.10.37: {} baseline-browser-mapping@2.10.37: {}
@@ -1072,8 +1039,6 @@ snapshots:
caniuse-lite@1.0.30001799: {} caniuse-lite@1.0.30001799: {}
clsx@2.1.1: {}
colorjs.io@0.6.1: {} colorjs.io@0.6.1: {}
commander@2.20.3: {} commander@2.20.3: {}
@@ -1202,14 +1167,6 @@ snapshots:
prettier@3.8.1: {} prettier@3.8.1: {}
prism-react-renderer@2.4.1(react@19.1.1):
dependencies:
'@types/prismjs': 1.26.5
clsx: 2.1.1
react: 19.1.1
react@19.1.1: {}
rollup@4.57.1: rollup@4.57.1:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.8
@@ -1250,13 +1207,6 @@ snapshots:
source-map@0.6.1: {} source-map@0.6.1: {}
tailwind-scrollbar@4.0.2(react@19.1.1)(tailwindcss@4.3.2):
dependencies:
prism-react-renderer: 2.4.1(react@19.1.1)
tailwindcss: 4.3.2
transitivePeerDependencies:
- react
tailwindcss@4.3.2: {} tailwindcss@4.3.2: {}
tapable@2.3.3: {} tapable@2.3.3: {}
@@ -1273,8 +1223,6 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3 picomatch: 4.0.3
tw-animate-css@1.4.0: {}
undici-types@7.16.0: {} undici-types@7.16.0: {}
update-browserslist-db@1.2.3(browserslist@4.28.2): update-browserslist-db@1.2.3(browserslist@4.28.2):
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760890864667" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18898" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M918.8 83.7c-43.5-30.3-104-19.5-134.6 24.1l-425.8 606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39 36.2-41.1 97.7-4.7 136.6l206.5 220.7c4.7 5 9.8 9.4 15.1 13.1 0.7 0.5 1.4 1 2.2 1.5 43.5 30.3 104 19.5 134.6-24.1l494-703.2c30.7-43.5 20.1-103.9-23.3-134.2z" fill="#0f162b" p-id="18899"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M918.8 83.7c-43.5-30.3-104-19.5-134.6 24.1l-425.8 606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39 36.2-41.1 97.7-4.7 136.6l206.5 220.7c4.7 5 9.8 9.4 15.1 13.1.7.5 1.4 1 2.2 1.5 43.5 30.3 104 19.5 134.6-24.1l494-703.2c30.7-43.5 20.1-103.9-23.3-134.2z"/></svg>

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 330 B

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1758357645269" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7610" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512 276.352 85.333333 512 85.333333z m0 128a298.666667 298.666667 0 1 0 0 597.333334 298.666667 298.666667 0 0 0 0-597.333334z" fill="#000000" fill-opacity=".05" p-id="7611"></path><path d="M813.696 813.696c166.613333-166.613333 166.613333-436.778667 0-603.392-166.613333-166.613333-436.778667-166.613333-603.392 0A64 64 0 0 0 300.8 300.8a298.666667 298.666667 0 1 1 422.4 422.4 64 64 0 0 0 90.496 90.496z" fill="#000000" p-id="7612"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path opacity=".05" d="M512 85.333c235.648 0 426.667 191.019 426.667 426.667S747.648 938.667 512 938.667 85.333 747.648 85.333 512 276.352 85.333 512 85.333zm0 128a298.667 298.667 0 1 0 0 597.334 298.667 298.667 0 0 0 0-597.334z"/><path d="M813.696 813.696c166.613-166.613 166.613-436.779 0-603.392s-436.779-166.613-603.392 0a64 64 0 0 0 90.496 90.496 298.667 298.667 0 1 1 422.4 422.4 64 64 0 0 0 90.496 90.496z"/></svg>

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 486 B

@@ -1,9 +1,9 @@
*:not(html) { *:not(html) {
@apply scrollbar-thumb-rounded-full scrollbar-thin scrollbar-track-transparent scrollbar-thumb-text-muted; @apply [scrollbar-color:var(--text-muted)_transparent] [scrollbar-width:thin];
} }
html { html {
@apply scrollbar-thumb-rounded-full scrollbar-thin scrollbar-track-surface scrollbar-thumb-text-muted; @apply [scrollbar-color:var(--text-muted)_var(--surface)] [scrollbar-width:thin];
body { body {
@apply text-text relative flex min-h-dvh flex-col text-sm leading-relaxed font-normal; @apply text-text relative flex min-h-dvh flex-col text-sm leading-relaxed font-normal;
@@ -0,0 +1,32 @@
/* Shared encoded icon payloads. Referencing these variables from masks keeps
Vite from inlining the same SVG once per selector and vendor declaration. */
:root {
@apply [--icon-activity:url('@assets/icons/activity.svg')];
@apply [--icon-adjustments:url('@assets/icons/adjustments.svg')];
@apply [--icon-apps:url('@assets/icons/apps.svg')];
@apply [--icon-arrow-down:url('@assets/icons/arrow-down.svg')];
@apply [--icon-arrow-down-dark:url('@assets/icons/arrow-down-dark.svg')];
@apply [--icon-arrow-right:url('@assets/icons/arrow-right.svg')];
@apply [--icon-box:url('@assets/icons/box.svg')];
@apply [--icon-category:url('@assets/icons/category.svg')];
@apply [--icon-chart-bar:url('@assets/icons/chart-bar.svg')];
@apply [--icon-check:url('@assets/icons/check.svg')];
@apply [--icon-cpu:url('@assets/icons/cpu.svg')];
@apply [--icon-error:url('@assets/icons/error.svg')];
@apply [--icon-gauge:url('@assets/icons/gauge.svg')];
@apply [--icon-info:url('@assets/icons/info.svg')];
@apply [--icon-package:url('@assets/icons/package.svg')];
@apply [--icon-phone:url('@assets/icons/phone.svg')];
@apply [--icon-radio:url('@assets/icons/radio.svg')];
@apply [--icon-refresh:url('@assets/icons/refresh.svg')];
@apply [--icon-refresh-off:url('@assets/icons/refresh-off.svg')];
@apply [--icon-router:url('@assets/icons/router.svg')];
@apply [--icon-search:url('@assets/icons/search.svg')];
@apply [--icon-server:url('@assets/icons/server.svg')];
@apply [--icon-settings:url('@assets/icons/settings.svg')];
@apply [--icon-shield:url('@assets/icons/shield.svg')];
@apply [--icon-spinning:url('@assets/icons/spinning.svg')];
@apply [--icon-tag:url('@assets/icons/tag.svg')];
@apply [--icon-world:url('@assets/icons/world.svg')];
@apply [--icon-x:url('@assets/icons/x.svg')];
}
+18 -14
View File
@@ -114,9 +114,12 @@ header {
/* First-level icon: mask + currentColor so it follows the theme. /* First-level icon: mask + currentColor so it follows the theme.
--menu-icon comes from the shared name → icon map in --menu-icon comes from the shared name → icon map in
components/_nav.css; unmapped → default. */ components/_nav.css; unmapped → the var() fallback. The default
must live in the fallback, not as a --menu-icon declaration
here: this nested chain compiles to (0,5,1) and would outrank
every (0,2,0) [data-section] mapping. */
&::before { &::before {
@apply size-6 shrink-0 bg-current content-[''] [mask:var(--menu-icon,url('@assets/icons/category.svg'))_center/contain_no-repeat]; @apply size-6 shrink-0 bg-current content-[''] [mask:var(--menu-icon,var(--icon-category))_center/contain_no-repeat];
} }
} }
} }
@@ -165,19 +168,19 @@ header {
} }
& .board-line-host::before { & .board-line-host::before {
@apply [mask:url('@assets/icons/router.svg')_center/contain_no-repeat]; @apply [mask:var(--icon-router)_center/contain_no-repeat];
} }
& .board-line-model::before { & .board-line-model::before {
@apply [mask:url('@assets/icons/cpu.svg')_center/contain_no-repeat]; @apply [mask:var(--icon-cpu)_center/contain_no-repeat];
} }
& .board-line-firmware::before { & .board-line-firmware::before {
@apply [mask:url('@assets/icons/package.svg')_center/contain_no-repeat]; @apply [mask:var(--icon-package)_center/contain_no-repeat];
} }
& .board-line-kernel::before { & .board-line-kernel::before {
@apply [mask:url('@assets/icons/tag.svg')_center/contain_no-repeat]; @apply [mask:var(--icon-tag)_center/contain_no-repeat];
} }
} }
} }
@@ -318,21 +321,21 @@ header {
@apply before:text-text size-5 cursor-pointer text-[0px] transition-transform duration-150 before:absolute before:size-5 before:bg-current hover:scale-105 active:scale-95; @apply before:text-text size-5 cursor-pointer text-[0px] transition-transform duration-150 before:absolute before:size-5 before:bg-current hover:scale-105 active:scale-95;
&[data-indicator="media_error"] { &[data-indicator="media_error"] {
@apply before:[mask:url('@assets/icons/error.svg')_center/cover_no-repeat]; @apply before:[mask:var(--icon-error)_center/cover_no-repeat];
} }
&[data-indicator="poll-status"] { &[data-indicator="poll-status"] {
&[data-style="active"] { &[data-style="active"] {
@apply before:[mask:url('@assets/icons/refresh.svg')_center/cover_no-repeat]; @apply before:[mask:var(--icon-refresh)_center/cover_no-repeat];
} }
&[data-style="inactive"] { &[data-style="inactive"] {
@apply before:[mask:url('@assets/icons/refresh-off.svg')_center/cover_no-repeat]; @apply before:[mask:var(--icon-refresh-off)_center/cover_no-repeat];
} }
} }
&[data-indicator="uci-changes"] { &[data-indicator="uci-changes"] {
@apply relative before:[mask:url('@assets/icons/settings.svg')_center/cover_no-repeat]; @apply relative before:[mask:var(--icon-settings)_center/cover_no-repeat];
&[data-count]:not([data-count="0"]) { &[data-count]:not([data-count="0"]) {
@apply after:bg-danger after:text-on-brand after:absolute after:-top-0.5 after:-right-0.5 after:z-10 after:flex after:min-h-3 after:min-w-3 after:items-center after:justify-center after:rounded-full after:px-0.5 after:text-[8px] after:leading-none after:font-bold after:shadow-sm after:content-[attr(data-count)]; @apply after:bg-danger after:text-on-brand after:absolute after:-top-0.5 after:-right-0.5 after:z-10 after:flex after:min-h-3 after:min-w-3 after:items-center after:justify-center after:rounded-full after:px-0.5 after:text-[8px] after:leading-none after:font-bold after:shadow-sm after:content-[attr(data-count)];
@@ -349,9 +352,9 @@ body[data-nav-type="sidebar"] {
reads as the content area's toolbar, not a page-wide bar. reads as the content area's toolbar, not a page-wide bar.
A CSS grid (not flex-wrap) keeps the header row at its natural height — A CSS grid (not flex-wrap) keeps the header row at its natural height —
so collapsing the sidebar never stretches it — lets the sidebar column so collapsing the sidebar never stretches it — lets the sidebar column
animate to zero, and lets #maincontent fluidly reclaim the freed space. collapse to zero, and lets #maincontent reclaim the freed space.
overflow-x-clip keeps the sliding sidebar out of horizontal overflow. */ overflow-x-clip keeps the sliding sidebar out of horizontal overflow. */
@apply md:grid md:grid-cols-[17rem_minmax(0,1fr)] md:grid-rows-[auto_minmax(0,1fr)] md:overflow-x-clip md:transition-[grid-template-columns] md:duration-[250ms] md:ease-out; @apply md:grid md:grid-cols-[17rem_minmax(0,1fr)] md:grid-rows-[auto_minmax(0,1fr)] md:overflow-x-clip;
&.sidebar-collapsed { &.sidebar-collapsed {
@apply md:grid-cols-[0_minmax(0,1fr)]; @apply md:grid-cols-[0_minmax(0,1fr)];
@@ -396,8 +399,9 @@ body[data-nav-type="sidebar"] {
@apply border-hairline sticky top-0 hidden h-dvh w-full overflow-hidden rounded-none border-r transition-[visibility] duration-[250ms] md:block; @apply border-hairline sticky top-0 hidden h-dvh w-full overflow-hidden rounded-none border-r transition-[visibility] duration-[250ms] md:block;
} }
/* Visibility flips at the end of the slide so collapsed controls leave the /* The grid column snaps closed to avoid a full-page layout animation.
tab order without clipping the animation. */ Visibility removes collapsed controls from the tab order; on expansion,
the inner panel still enters via compositor-only translate + opacity. */
&.sidebar-collapsed .sidebar-panel { &.sidebar-collapsed .sidebar-panel {
@apply invisible; @apply invisible;
} }
@@ -51,8 +51,14 @@
} }
} }
@keyframes aurora-fade-in {
from {
opacity: 0;
}
}
.fade-in { .fade-in {
@apply animate-in fade-in-0 fill-mode-backwards duration-[250ms]; @apply animate-[aurora-fade-in_250ms_ease_both];
} }
.fade-out { .fade-out {
@@ -61,7 +67,7 @@
.spinning { .spinning {
@apply relative inline-block pl-8!; @apply relative inline-block pl-8!;
@apply before:absolute before:top-1/2 before:left-[6px] before:size-5 before:-translate-y-1/2 before:animate-spin before:bg-current before:[mask:url('@assets/icons/spinning.svg')_center/cover_no-repeat]; @apply before:absolute before:top-1/2 before:left-[6px] before:size-5 before:-translate-y-1/2 before:animate-spin before:bg-current before:[mask:var(--icon-spinning)_center/cover_no-repeat];
#view > & { #view > & {
@apply first:mx-auto first:grid first:min-h-48 first:w-fit first:place-items-center first:max-md:min-h-[calc(100dvh-4rem)]; @apply first:mx-auto first:grid first:min-h-48 first:w-fit first:place-items-center first:max-md:min-h-[calc(100dvh-4rem)];
} }
@@ -27,11 +27,11 @@
@apply relative size-5 cursor-pointer text-[0px] before:absolute before:size-5 before:bg-current before:transition-transform before:duration-150 before:ease-out hover:before:scale-110 active:before:scale-95; @apply relative size-5 cursor-pointer text-[0px] before:absolute before:size-5 before:bg-current before:transition-transform before:duration-150 before:ease-out hover:before:scale-110 active:before:scale-95;
&[data-style="active"] { &[data-style="active"] {
@apply before:rotate-0 before:[mask:url('@assets/icons/arrow-right.svg')_center/cover_no-repeat]; @apply before:rotate-0 before:[mask:var(--icon-arrow-right)_center/cover_no-repeat];
} }
&[data-style="inactive"] { &[data-style="inactive"] {
@apply before:rotate-90 before:[mask:url('@assets/icons/arrow-right.svg')_center/cover_no-repeat]; @apply before:rotate-90 before:[mask:var(--icon-arrow-right)_center/cover_no-repeat];
} }
} }
} }
@@ -28,7 +28,7 @@
@apply text-text-muted relative mt-1 pl-4 leading-none wrap-break-word; @apply text-text-muted relative mt-1 pl-4 leading-none wrap-break-word;
&:not(:empty) { &:not(:empty) {
@apply before:bg-info before:absolute before:-left-0.5 before:inline-block before:size-4 before:[mask:url('@assets/icons/info.svg')_center/cover_no-repeat]; @apply before:bg-info before:absolute before:-left-0.5 before:inline-block before:size-4 before:[mask:var(--icon-info)_center/cover_no-repeat];
} }
} }
} }
@@ -27,11 +27,11 @@ input[type="checkbox"] {
} }
input[type="radio"] { input[type="radio"] {
@apply before:rounded-full after:[mask:url('@assets/icons/radio.svg')_center/cover_no-repeat]; @apply before:rounded-full after:[mask:var(--icon-radio)_center/cover_no-repeat];
} }
input[type="checkbox"] { input[type="checkbox"] {
@apply before:rounded after:[mask:url('@assets/icons/check.svg')_center/cover_no-repeat]; @apply before:rounded after:[mask:var(--icon-check)_center/cover_no-repeat];
} }
.cbi-checkbox { .cbi-checkbox {
@@ -11,7 +11,7 @@
/* hover:no-underline: the global `a:hover { underline }` in _elements.css /* hover:no-underline: the global `a:hover { underline }` in _elements.css
outranks a bare class (a:hover is 0,1,1), so nav rows must opt out at outranks a bare class (a:hover is 0,1,1), so nav rows must opt out at
the same specificity tier. */ the same specificity tier. */
@apply text-text hover:bg-hover-faint block rounded-xl px-3 py-1.5 no-underline transition-all duration-150 hover:no-underline; @apply text-text hover:bg-hover-faint block rounded-xl px-3 py-1.5 no-underline transition-colors duration-150 hover:no-underline;
} }
.navigation-direct { .navigation-direct {
@@ -33,7 +33,7 @@
} }
.navigation-group-toggle { .navigation-group-toggle {
@apply after:size-3.5 after:shrink-0 after:bg-current after:opacity-55 after:transition-[transform,opacity] after:duration-[250ms] after:content-[''] after:[mask:url('@assets/icons/arrow-right.svg')_center/cover_no-repeat] hover:after:opacity-100; @apply after:size-3.5 after:shrink-0 after:bg-current after:opacity-55 after:transition-[transform,opacity] after:duration-[250ms] after:content-[''] after:[mask:var(--icon-arrow-right)_center/cover_no-repeat] hover:after:opacity-100;
} }
.nav-category-label { .nav-category-label {
@@ -90,38 +90,38 @@
The only maintenance point: add new first-level sections here; anything The only maintenance point: add new first-level sections here; anything
unmapped falls back to the default icon at the consumer. */ unmapped falls back to the default icon at the consumer. */
.desktop-nav-title[data-section="status"] { .desktop-nav-title[data-section="status"] {
@apply [--menu-icon:url('@assets/icons/activity.svg')]; @apply [--menu-icon:var(--icon-activity)];
} }
.desktop-nav-title[data-section="statistics"] { .desktop-nav-title[data-section="statistics"] {
@apply [--menu-icon:url('@assets/icons/chart-bar.svg')]; @apply [--menu-icon:var(--icon-chart-bar)];
} }
.desktop-nav-title[data-section="nlbw"] { .desktop-nav-title[data-section="nlbw"] {
@apply [--menu-icon:url('@assets/icons/gauge.svg')]; @apply [--menu-icon:var(--icon-gauge)];
} }
.desktop-nav-title[data-section="system"] { .desktop-nav-title[data-section="system"] {
@apply [--menu-icon:url('@assets/icons/settings.svg')]; @apply [--menu-icon:var(--icon-settings)];
} }
.desktop-nav-title[data-section="services"] { .desktop-nav-title[data-section="services"] {
@apply [--menu-icon:url('@assets/icons/apps.svg')]; @apply [--menu-icon:var(--icon-apps)];
} }
.desktop-nav-title[data-section="nas"] { .desktop-nav-title[data-section="nas"] {
@apply [--menu-icon:url('@assets/icons/server.svg')]; @apply [--menu-icon:var(--icon-server)];
} }
.desktop-nav-title[data-section="control"] { .desktop-nav-title[data-section="control"] {
@apply [--menu-icon:url('@assets/icons/adjustments.svg')]; @apply [--menu-icon:var(--icon-adjustments)];
} }
.desktop-nav-title[data-section="network"] { .desktop-nav-title[data-section="network"] {
@apply [--menu-icon:url('@assets/icons/world.svg')]; @apply [--menu-icon:var(--icon-world)];
} }
.desktop-nav-title[data-section="vpn"] { .desktop-nav-title[data-section="vpn"] {
@apply [--menu-icon:url('@assets/icons/shield.svg')]; @apply [--menu-icon:var(--icon-shield)];
} }
.desktop-nav-title[data-section="docker"] { .desktop-nav-title[data-section="docker"] {
@apply [--menu-icon:url('@assets/icons/box.svg')]; @apply [--menu-icon:var(--icon-box)];
} }
/* "asterisk" covers VoIP/PBX apps (e.g. luci-app-asterisk forks) — no /* "asterisk" covers VoIP/PBX apps (e.g. luci-app-asterisk forks) — no
official OpenWrt package registers a verified menu.d node under this official OpenWrt package registers a verified menu.d node under this
name, so this is a best-effort key. */ name, so this is a best-effort key. */
.desktop-nav-title[data-section="asterisk"] { .desktop-nav-title[data-section="asterisk"] {
@apply [--menu-icon:url('@assets/icons/phone.svg')]; @apply [--menu-icon:var(--icon-phone)];
} }
@@ -1,5 +1,7 @@
.mobile-menu-overlay { .mobile-menu-overlay {
@apply max-md:bg-mega-menu-bg max-md:invisible max-md:fixed max-md:inset-0 max-md:z-60 max-md:opacity-0 max-md:backdrop-blur-lg max-md:backdrop-saturate-150 md:hidden; /* The mobile surface is opaque, so filtering pixels behind it has no visual
effect and only adds a full-screen paint/compositing cost. */
@apply max-md:bg-mega-menu-bg max-md:invisible max-md:fixed max-md:inset-0 max-md:z-60 max-md:opacity-0 md:hidden;
/* Transitions live on the OPEN state only: opening fades in, closing cuts /* Transitions live on the OPEN state only: opening fades in, closing cuts
instantly. Link taps close into a full-page navigation, and any close instantly. Link taps close into a full-page navigation, and any close
@@ -30,7 +30,7 @@
search icon carries the same stroke weight as the poll/changes ones search icon carries the same stroke weight as the poll/changes ones
sitting next to it. */ sitting next to it. */
&::before { &::before {
@apply size-5 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat]; @apply size-5 bg-current content-[''] [mask:var(--icon-search)_center/contain_no-repeat];
} }
&:hover { &:hover {
@@ -59,7 +59,7 @@
@apply max-md:border-hairline max-md:bg-surface max-md:border-b max-md:px-4 max-md:py-2.5; @apply max-md:border-hairline max-md:bg-surface max-md:border-b max-md:px-4 max-md:py-2.5;
&::before { &::before {
@apply text-text-subtle size-5 shrink-0 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat]; @apply text-text-subtle size-5 shrink-0 bg-current content-[''] [mask:var(--icon-search)_center/contain_no-repeat];
} }
& .cmdk-input { & .cmdk-input {
@@ -72,7 +72,7 @@
@apply text-text-subtle grid size-5 shrink-0 cursor-pointer place-items-center rounded-md border-0 bg-transparent p-0 shadow-none transition-colors duration-150; @apply text-text-subtle grid size-5 shrink-0 cursor-pointer place-items-center rounded-md border-0 bg-transparent p-0 shadow-none transition-colors duration-150;
&::before { &::before {
@apply size-4 bg-current content-[''] [mask:url('@assets/icons/x.svg')_center/contain_no-repeat]; @apply size-4 bg-current content-[''] [mask:var(--icon-x)_center/contain_no-repeat];
} }
&:hover { &:hover {
@@ -1,6 +1,6 @@
select { select {
@apply text-text border-hairline bg-control-bg focus:border-brand focus:ring-focus-ring min-w-52.5 max-w-full appearance-none md:max-w-100 rounded-2xl border px-3 py-1.5 pr-10 text-sm font-normal shadow-sm transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:outline-none; @apply text-text border-hairline bg-control-bg focus:border-brand focus:ring-focus-ring min-w-52.5 max-w-full appearance-none md:max-w-100 rounded-2xl border px-3 py-1.5 pr-10 text-sm font-normal shadow-sm transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:outline-none;
@apply bg-[url('@assets/icons/arrow-down.svg')] bg-size-[16px] bg-position-[right_0.75rem_center] bg-no-repeat dark:bg-[url('@assets/icons/arrow-down-dark.svg')]; @apply bg-size-[16px] bg-position-[right_0.75rem_center] bg-no-repeat [background-image:var(--icon-arrow-down)] dark:[background-image:var(--icon-arrow-down-dark)];
&[disabled] { &[disabled] {
@apply cursor-not-allowed opacity-(--app-opacity-disabled); @apply cursor-not-allowed opacity-(--app-opacity-disabled);
} }
+37 -7
View File
@@ -4,18 +4,48 @@
* Licensed to the public under the Apache License 2.0. * Licensed to the public under the Apache License 2.0.
*/ */
@import "tailwindcss"; /* Login only needs Tailwind's theme variables and generated utilities. The
full Preflight reset is intentionally omitted from this standalone entry. */
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities) source(none);
@import "./_tokens.css"; @import "./_tokens.css";
@import "./_reduced-motion.css"; @import "./_reduced-motion.css";
@import "./_base.css"; @import "./_base.css";
/* See main.css: keep dev-only tooling out of Tailwind's source detection. */
@source not "../../vite.config.ts";
@source not "../../scripts/*.client.js";
@custom-variant dark (&:where([data-darkmode=true], [data-darkmode=true] *)); @custom-variant dark (&:where([data-darkmode=true], [data-darkmode=true] *));
/* Minimal reset for this template, replacing the much larger Preflight. */
@layer base {
*,
*::before,
*::after {
@apply box-border;
}
body,
hr,
p,
button,
input {
@apply m-0;
}
button,
input {
@apply [font:inherit];
}
a {
@apply no-underline;
}
[hidden] {
@apply hidden!;
}
}
@keyframes divider-in { @keyframes divider-in {
from { from {
opacity: 0; opacity: 0;
@@ -40,7 +70,7 @@
/* Full image fades in on top once loaded (compositor-only opacity change) */ /* Full image fades in on top once loaded (compositor-only opacity change) */
.login-bg::after { .login-bg::after {
@apply pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 [will-change:opacity] content-[''] [transition:opacity_0.5s_ease] [background-image:var(--login-bg)]; @apply pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 content-[''] [transition:opacity_0.5s_ease] [background-image:var(--login-bg)];
} }
.login-bg.full-loaded::after { .login-bg.full-loaded::after {
@@ -102,7 +132,7 @@
/* Focus ring: opacity-only transition runs on compositor thread, not main thread — /* Focus ring: opacity-only transition runs on compositor thread, not main thread —
border-color changes require a repaint (~400 ms INP); opacity changes do not. */ border-color changes require a repaint (~400 ms INP); opacity changes do not. */
.input-wrap::after { .input-wrap::after {
@apply border-brand pointer-events-none absolute -inset-px rounded-[calc(var(--radius-2xl)+1px)] border-2 opacity-0 [will-change:opacity] content-[''] [transition:opacity_150ms_ease]; @apply border-brand pointer-events-none absolute -inset-px rounded-[calc(var(--radius-2xl)+1px)] border-2 opacity-0 content-[''] [transition:opacity_150ms_ease];
} }
.input-wrap:focus-within::after { .input-wrap:focus-within::after {
+5 -14
View File
@@ -3,24 +3,15 @@
* Licensed under the Apache License, Version 2.0. * Licensed under the Apache License, Version 2.0.
*/ */
@import "tailwindcss"; /* Every shipped utility is referenced through @apply in these CSS sources.
Disabling automatic repository scanning prevents class-like words in docs,
/* Dev-only tooling holds prose and plain CSS whose bare words (static, scripts and fixtures from leaking into the OpenWrt payload. */
transition, relative, …) Tailwind's automatic source detection would @import "tailwindcss" source(none);
otherwise read as utility candidates and emit into this shipped stylesheet.
Nothing here renders markup, so none of it belongs in the scan. */
@source not "../../vite.config.ts";
@source not "../../scripts/*.client.js";
@custom-variant dark (&:where([data-darkmode=true], [data-darkmode=true] *)); @custom-variant dark (&:where([data-darkmode=true], [data-darkmode=true] *));
@plugin "tailwind-scrollbar" {
nocompatible: true;
}
@import "tw-animate-css";
@import "./_tokens.css"; @import "./_tokens.css";
@import "./_icons.css";
@import "./_base.css"; @import "./_base.css";
@import "./_elements.css"; @import "./_elements.css";
@@ -0,0 +1,101 @@
import assert from "node:assert/strict";
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, resolve } from "node:path";
import test from "node:test";
const projectRoot = resolve(import.meta.dirname, "../..");
const output = resolve(projectRoot, "htdocs/luci-static");
const asset = (path) => resolve(output, path);
const bytes = (path) => statSync(asset(path)).size;
test("production assets stay within raw-transfer budgets", () => {
const main = bytes("aurora/main.css");
const login = bytes("aurora/login.css");
const menu = bytes("resources/menu-aurora.js");
const font = bytes("aurora/fonts/lato-v24-latin-regular.woff2");
const logo = bytes("aurora/images/logo.svg");
assert.ok(main <= 190_000, "main.css exceeds 190 KB");
assert.ok(login <= 12_000, "login.css exceeds 12 KB");
assert.ok(menu <= 20_000, "menu-aurora.js exceeds 20 KB");
assert.ok(logo <= 16_000, "logo.svg exceeds 16 KB");
assert.ok(main + menu + font + logo <= 250_000, "admin assets exceed 250 KB");
assert.ok(login + font + logo <= 55_000, "login assets exceed 55 KB");
});
test("compressed LuCI JS preserves its loader directives", () => {
const js = readFileSync(asset("resources/menu-aurora.js"), "utf8");
assert.match(js, /["']require baseclass["'];["']require ui["'];/);
const module = new Function("baseclass", "ui", js)(
{ extend: (value) => value },
{},
);
assert.equal(typeof module.__init__, "function");
});
test("compiled CSS does not duplicate SVG payloads for every mask property", () => {
const css = readFileSync(asset("aurora/main.css"), "utf8");
const dataUrls = css.match(/data:image\/svg\+xml,[^"]+/g) ?? [];
const encodedBytes = dataUrls.reduce((sum, value) => sum + value.length, 0);
assert.equal(
dataUrls.length,
new Set(dataUrls).size,
"duplicate SVG data URL",
);
assert.ok(encodedBytes <= 17_000, `SVG data URLs occupy ${encodedBytes} B`);
});
test("pruned login.css keeps every consumed variable resolvable", () => {
const css = readFileSync(asset("aurora/login.css"), "utf8");
const declared = new Set(
[...css.matchAll(/[{;](--[\w-]+):/g)].map((m) => m[1]),
);
const registered = new Set(
[...css.matchAll(/@property\s+(--[\w-]+)/g)].map((m) => m[1]),
);
// header.ut injects these from UCI at render time — consumed here, never
// declared here.
const injected = new Set(["--login-bg", "--login-bg-lqip"]);
const unresolvable = [];
for (const [, name, delim] of css.matchAll(/var\(\s*(--[\w-]+)\s*([,)])/g)) {
const hasFallback = delim === ",";
if (
!declared.has(name) &&
!registered.has(name) &&
!injected.has(name) &&
!hasFallback
) {
unresolvable.push(name);
}
}
assert.deepEqual(unresolvable, []);
// The prune must actually strip admin-only tokens from the shared sheet.
for (const adminOnly of ["--mega-menu-bg", "--icon-", "--sidebar"]) {
assert.ok(!css.includes(adminOnly), `${adminOnly} should be pruned`);
}
// And the login page's own consumed tokens must survive, light and dark.
for (const kept of ["--surface:", "--brand:", "--control-bg:"]) {
const count = css.split(kept).length - 1;
assert.ok(count >= 2, `${kept} should be declared for both modes`);
}
});
test("package roots contain no macOS metadata", () => {
const offenders = [];
const visit = (dir) => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.name === ".DS_Store") offenders.push(path);
else if (entry.isDirectory()) visit(path);
}
};
visit(resolve(projectRoot, "htdocs"));
visit(resolve(projectRoot, "ucode"));
assert.deepEqual(offenders, []);
});
@@ -108,7 +108,7 @@ test("shared navigation styles own accordion animation without a guide rail", ()
"after:transition-[transform,opacity]", "after:transition-[transform,opacity]",
"after:duration-[250ms]", "after:duration-[250ms]",
]); ]);
assert.match(toggle, /arrow-right\.svg/); assert.match(toggle, /var\(--icon-arrow-right\)/);
assertIncludesUtilities(region, [ assertIncludesUtilities(region, [
"grid", "grid",
"grid-rows-[0fr]", "grid-rows-[0fr]",
@@ -218,7 +218,11 @@ test("mega-menu category masks use Tailwind arbitrary utilities", () => {
assert.match( assert.match(
icon, icon,
/@apply[^;]*\[mask:var\(--menu-icon,url\(["']@assets\/icons\/category\.svg["']\)\)_center\/contain_no-repeat\]/, /@apply[^;]*\[mask:var\(--menu-icon,var\(--icon-category\)\)_center\/contain_no-repeat\]/,
); );
// The default lives in the var() fallback only. Declaring --menu-icon on
// the title compiles into a (0,5,1) selector chain that outranks every
// .desktop-nav-title[data-section=…] (0,2,0) mapping in _nav.css.
assert.doesNotMatch(title, /--menu-icon:/);
assert.doesNotMatch(layoutStyles, /^\s*mask\s*:/m); assert.doesNotMatch(layoutStyles, /^\s*mask\s*:/m);
}); });
@@ -0,0 +1,32 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import test from "node:test";
const projectRoot = resolve(import.meta.dirname, "../..");
const source = (path) => readFileSync(resolve(projectRoot, path), "utf8");
test("navigation transitions avoid blanket and full-page layout animation", () => {
const nav = source(".dev/src/media/components/_nav.css");
const layout = source(".dev/src/media/_layout.css");
assert.doesNotMatch(nav, /\btransition-all\b/);
assert.doesNotMatch(layout, /transition-\[grid-template-columns\]/);
});
test("login effects do not reserve compositor layers permanently", () => {
const login = source(".dev/src/media/login.css");
assert.doesNotMatch(login, /\[will-change:opacity\]/);
});
test("login template reuses server data when including the header", () => {
const sysauth = source("ucode/template/themes/aurora/sysauth.ut");
const header = source("ucode/template/themes/aurora/header.ut");
assert.match(
sysauth,
/include\('header',\s*\{[^}]*prefetched_boardinfo:\s*boardinfo[^}]*prefetched_tokens:\s*themeTokens[^}]*\}\)/s,
);
assert.match(header, /prefetched_boardinfo\s*\?\?/);
assert.match(header, /prefetched_tokens\s*\?\?/);
});
@@ -114,7 +114,10 @@ test("mega-menu reveal is compositor-only and the frost never overlaps it", () =
// The curtain carries the blur permanently (Apple's globalnav-curtain) and // The curtain carries the blur permanently (Apple's globalnav-curtain) and
// fades it with opacity/visibility. Close must start that fade immediately; // fades it with opacity/visibility. Close must start that fade immediately;
// delaying the curtain left blur visible after the sheet had collapsed. // delaying the curtain left blur visible after the sheet had collapsed.
assert.ok(overlay.includes("max-md:backdrop-blur-lg"), "mobile overlay blur"); assert.ok(
!overlay.includes("max-md:backdrop-blur-lg"),
"opaque mobile overlay must not blur hidden pixels",
);
const curtain = overlay const curtain = overlay
.split("\n") .split("\n")
.find((l) => l.includes("bg-mega-menu-scrim")); .find((l) => l.includes("bg-mega-menu-scrim"));
+101 -7
View File
@@ -7,9 +7,9 @@ import tailwindcss from "@tailwindcss/vite";
import browserslist from "browserslist"; import browserslist from "browserslist";
import { exec } from "child_process"; import { exec } from "child_process";
import { existsSync, readdirSync, readFileSync, statSync } from "fs"; import { existsSync, readdirSync, readFileSync, statSync } from "fs";
import { mkdir, readdir, readFile, writeFile } from "fs/promises"; import { mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
import type { IncomingMessage, ServerResponse } from "http"; import type { IncomingMessage, ServerResponse } from "http";
import { browserslistToTargets } from "lightningcss"; import { browserslistToTargets, transform as lightningcssTransform } from "lightningcss";
import { basename, dirname, join, relative, resolve, sep } from "path"; import { basename, dirname, join, relative, resolve, sep } from "path";
import { minify as terserMinify } from "terser"; import { minify as terserMinify } from "terser";
import { promisify } from "util"; import { promisify } from "util";
@@ -27,6 +27,10 @@ const CURRENT_DIR = process.cwd();
const PROJECT_ROOT = resolve(CURRENT_DIR, ".."); const PROJECT_ROOT = resolve(CURRENT_DIR, "..");
const BUILD_OUTPUT = resolve(PROJECT_ROOT, "htdocs/luci-static"); const BUILD_OUTPUT = resolve(PROJECT_ROOT, "htdocs/luci-static");
const LIGHTNINGCSS_TARGETS = browserslistToTargets(
browserslist("last 4 versions, Firefox ESR, not dead"),
);
function createLuciJsCompressPlugin(): Plugin { function createLuciJsCompressPlugin(): Plugin {
let outDir: string; let outDir: string;
@@ -48,8 +52,10 @@ function createLuciJsCompressPlugin(): Plugin {
const sourceCode = await readFile(join(srcDir, relPath), "utf-8"); const sourceCode = await readFile(join(srcDir, relPath), "utf-8");
const compressed = await terserMinify(sourceCode, { const compressed = await terserMinify(sourceCode, {
parse: { bare_returns: true }, parse: { bare_returns: true },
compress: false, /* LuCI dependency declarations are string directives. Keep
mangle: false, them while enabling normal compression and local mangling. */
compress: { directives: false, passes: 2 },
mangle: true,
format: { comments: false, beautify: false }, format: { comments: false, beautify: false },
}); });
const outputPath = join(outDir, "resources", normalized); const outputPath = join(outDir, "resources", normalized);
@@ -66,6 +72,94 @@ function createLuciJsCompressPlugin(): Plugin {
}; };
} }
async function removeMacOsMetadata(dir: string): Promise<void> {
if (!existsSync(dir)) return;
for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = join(dir, entry.name);
if (entry.isDirectory()) await removeMacOsMetadata(path);
else if (entry.name === ".DS_Store") await rm(path, { force: true });
}
}
/* login.css imports the full shared token sheet but the login page consumes
only a fraction of it. Prune every custom-property declaration (and --tw-*
@property registration) that no var() reference can reach, following
declaration-value chains (--a: var(--b) keeps --b alive). --login-bg and
--login-bg-lqip stay consumed-without-declaration by design: header.ut
injects them from UCI at render time, so pruning never touches consumers. */
function createLoginCssPrunePlugin(): Plugin {
return {
name: "login-css-prune",
apply: "build",
enforce: "post",
async closeBundle() {
const path = resolve(BUILD_OUTPUT, "aurora/login.css");
if (!existsSync(path)) return;
const css = await readFile(path, "utf-8");
// Roots are vars consumed by normal declarations; custom-property
// declarations only contribute edges to the reachability walk.
const VALUE = `(?:"[^"]*"|'[^']*'|[^;{}"'])*`;
const roots = new Set<string>();
const edges = new Map<string, Set<string>>();
for (const [, name, value] of css.matchAll(
new RegExp(`(?<=[{;])(--[\\w-]+|[a-zA-Z-]+):(${VALUE})`, "g"),
)) {
const refs = [...value.matchAll(/var\(\s*(--[\w-]+)/g)].map((m) => m[1]);
if (!name.startsWith("--")) refs.forEach((r) => roots.add(r));
else {
const deps = edges.get(name) ?? new Set<string>();
refs.forEach((r) => deps.add(r));
edges.set(name, deps);
}
}
const keep = new Set(roots);
const stack = [...roots];
while (stack.length) {
for (const dep of edges.get(stack.pop()!) ?? []) {
if (!keep.has(dep)) {
keep.add(dep);
stack.push(dep);
}
}
}
const pruned = css
.replace(
new RegExp(`(?<=[{;])(--[\\w-]+):${VALUE};?`, "g"),
(decl, name) => (keep.has(name) ? decl : ""),
)
.replace(/@property\s+(--[\w-]+)\{[^{}]*\}/g, (rule, name) =>
keep.has(name) ? rule : "",
);
// Re-minify with the shared targets: validates the edited syntax and
// drops any rule the pruning emptied out.
const { code } = lightningcssTransform({
filename: "login.css",
code: Buffer.from(pruned),
minify: true,
targets: LIGHTNINGCSS_TARGETS,
});
await writeFile(path, code);
},
};
}
function createPackageHygienePlugin(): Plugin {
return {
name: "package-hygiene",
apply: "build",
enforce: "post",
async closeBundle() {
await Promise.all([
removeMacOsMetadata(resolve(PROJECT_ROOT, "htdocs")),
removeMacOsMetadata(resolve(PROJECT_ROOT, "ucode")),
]);
},
};
}
// On-demand third-party patches: serve src/media/patches/<page>.css at // On-demand third-party patches: serve src/media/patches/<page>.css at
// /luci-static/aurora/patches/<page>.css in dev. Without this, header.ut's patch // /luci-static/aurora/patches/<page>.css in dev. Without this, header.ut's patch
// <link> falls through to the OpenWrt proxy (404 / stale router asset) and patch // <link> falls through to the OpenWrt proxy (404 / stale router asset) and patch
@@ -789,12 +883,12 @@ export default defineConfig(({ mode }) => {
createMockPlugin(), createMockPlugin(),
createUtSyncPlugin(OPENWRT_SSH_HOST), createUtSyncPlugin(OPENWRT_SSH_HOST),
createLuciJsCompressPlugin(), createLuciJsCompressPlugin(),
createLoginCssPrunePlugin(),
createPackageHygienePlugin(),
], ],
css: { css: {
lightningcss: { lightningcss: {
targets: browserslistToTargets( targets: LIGHTNINGCSS_TARGETS,
browserslist("last 4 versions, Firefox ESR, not dead"),
),
}, },
}, },
build: { build: {
+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_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
LUCI_DEPENDS:=+luci-base LUCI_DEPENDS:=+luci-base
PKG_VERSION:=1.1.2 PKG_VERSION:=1.1.3
PKG_RELEASE:=55 PKG_RELEASE:=56
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
LUCI_MINIFY_CSS:= LUCI_MINIFY_CSS:=
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -1,2 +1,2 @@
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */ /*! 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight: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-duration:initial;--tw-content:"";--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-tracking:initial}}}@layer theme{: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-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);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--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);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@layer base{*,: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-spacinLine 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;--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)}html body{background-color:var(--bg);min-height:100dvh}@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)}.login-bg{pointer-events:none;background-image:var(--login-bg-lqip,var(--login-bg));background-position:50%;background-repeat:no-repeat;background-size:cover;position:fixed;inset:0;transform:translateZ(0)}.login-bg:after{pointer-events:none;background-image:var(--login-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}.login-bg.full-loaded:after{opacity:1}.login-card{gap:calc(var(--spacing) * 7);border-radius:calc(var(--radius-base) * 4);background-color:var(--surface);padding:calc(var(--spacing) * 14);--tw-shadow:var(--app-shadow-md);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);flex-direction:column;display:flex}@media not all and (width>=48rem){.login-card{border-radius:calc(var(--radius-base) * 3);padding:calc(var(--spacing) * 9)}}.login-brand{align-items:center;gap:calc(var(--spacing) * 3.5);display:flex}.login-divider{border-style:var(--tw-border-style);--tw-gradient-position:to right;borderLine truncated
@@ -1,2 +1,2 @@
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */ /*! 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight: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-duration:initial;--tw-content:"";--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-scrollbar-thumb:#0000;--tw-scrollbar-track:#0000;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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}}}@layer theme{: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-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--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);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@layer base{*,: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-mLine 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-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-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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y: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-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}}}@layer theme{: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;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--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)}}@layer base{*,: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-butLine 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 mobileList=overlay.querySelector("#mobile-nav-list");const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const updateToggleState=expanded=>{const desktopSidebar=isDesktopSidebar();navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded?"true":"false");navigationToggle.setAttribute("aria-controls",desktopSidebar?"sidebar-panel":"mobile-menu-overlay");const label=expanded&&!desktopSidebar?_("Close"):_("Navigation");navigationToggle.setAttribute("title",label);navigationToggle.setAttribute("aria-label",label)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.classList.remove("mobile-navigation-open");document.body.style.overflow="";this.resetNavigationGroups(mobileList)};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.classList.toggle("mobile-navigation-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)this.expandActiveNavigationGroup(mobileList);else this.resetNavigationGroups(mobileList);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 destination=e.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");if(destination&&overlay.contains(destination)){setNavigationExpanded(false)}})},renderMobileMenu(items){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");if(list)list.innerHTML="";if(footerAction)footerAction.innerHTML="";if(!list)return;items.forEach(item=>{if(item.isLogout){if(footerAction){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:item.href},[item.title]))}return}list.appendChild(this.renderNavigationItem(item,"mobile"))});this.bindNavigationAccordion(list)},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,level=0){const container=document.querySelector("#tabmenu");const ul=E("ul",{class:"tabs"});const children=ui.menu.getChildren(tree);let activeNode=null;children.forEach(child=>{const isActive=L.env.dispatchpath[3+level]===child.name;ul.appendChild(E("li",{class:`tabmenu-item-${child.name}${isActive?" active":""}`},[E("a",{href:L.url(url,child.name)},[_(child.title)])]));if(isActive)activeNode=child});if(!ul.children.length)return E([]);container.appendChild(ul);container.style.display="";if(activeNode){this.renderTabMenu(activeNode,`${url}/${activeNode.name}`,level+1)}return ul},renderMainMenu(tree,url,level=0,navigationItems=null){const ul=level?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu");const children=ui.menu.getChildren(tree);if(level>1)return E([]);if(level===0){const navType=document.body?.dataset?.navType||"mega-menu";if(navType==="sidebar"){this.renderSidebLine truncated "use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(e=>this.render(e)),this.initNavigationControls(),this.initUciIndicator()},initUciIndicator(){const e=ui.changes?.setIndicator;e&&(ui.changes.setIndicator=function(t){e.call(this,t),document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",t||0)})},initNavigationControls(){const e=document.querySelector("#mobile-menu-overlay"),t=document.querySelector("#navigation-toggle");if(!t||!e)return;const i=e.querySelector("#mobile-nav-list"),n=window.matchMedia("(min-width: 768px)"),a="aurora.sidebarCollapsed",s=()=>n.matches&&"sidebar"===document.body.dataset.navType,o=e=>{const i=s();t.classList.toggle("is-expanded",e&&!n.matches),t.setAttribute("aria-expanded",e?"true":"false"),t.setAttribute("aria-controls",i?"sidebar-panel":"mobile-menu-overlay");const a=e&&!i?_("Close"):_("Navigation");t.setAttribute("title",a),t.setAttribute("aria-label",a)},l=()=>{e.classList.remove("mobile-menu-open"),document.body.classList.remove("mobile-navigation-open"),document.body.style.overflow="",this.resetNavigationGroups(i)},r=t=>{if(s()){l();const e=!t;return document.body.classList.toggle("sidebar-collapsed",e),localStorage.setItem(a,e),void o(t)}if(n.matches)return l(),void o(!1);e.classList.toggle("mobile-menu-open",t),document.body.classList.toggle("mobile-navigation-open",t),document.body.style.overflow=t?"hidden":"",t?this.expandActiveNavigationGroup(i):this.resetNavigationGroups(i),o(t)},c=()=>{if(l(),s()){const e="true"===localStorage.getItem(a);return document.body.classList.toggle("sidebar-collapsed",e),void o(!e)}o(!1)};t.addEventListener("click",t=>{t.stopPropagation(),r(!(s()?!document.body.classList.contains("sidebar-collapsed"):!n.matches&&e.classList.contains("mobile-menu-open")))}),e.addEventListener("click",t=>{t.target===e&&r(!1)}),document.addEventListener("keydown",t=>{"Escape"===t.key&&e.classList.contains("mobile-menu-open")&&r(!1)}),n.addEventListener("change",c),c(),document.addEventListener("click",t=>{const i=t.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");i&&e.contains(i)&&r(!1)})},renderMobileMenu(e){const t=document.querySelector("#mobile-nav-list"),i=document.querySelector("#mobile-nav-footer-action");t&&(t.innerHTML=""),i&&(i.innerHTML=""),t&&(e.forEach(e=>{e.isLogout?i&&i.appendChild(E("a",{class:"mobile-nav-logout",href:e.href},[e.title])):t.appendChild(this.renderNavigationItem(e,"mobile"))}),this.bindNavigationAccordion(t))},render(e){if(this.renderModeMenu(e),L.env.dispatchpath.length>=3){let t=e,i="";for(let e=0;e<3&&t;e++){const n=L.env.dispatchpath[e];t=t.children?.[n],i+=(i?"/":"")+n}t&&this.renderTabMenu(t,i)}},renderTabMenu(e,t,i=0){const n=document.querySelector("#tabmenu"),a=E("ul",{class:"tabs"}),s=ui.menu.getChildren(e);let o=null;return s.forEach(e=>{const n=L.env.dispatchpath[3+i]===e.name;a.appendChild(E("li",{class:`tabmenu-item-${e.name}${n?" active":""}`},[E("a",{href:L.url(t,e.name)},[_(e.title)])])),n&&(o=e)}),a.children.length?(n.appendChild(a),n.style.display="",o&&this.renderTabMenu(o,`${t}/${o.name}`,i+1),a):E([])},renderMainMenu(e,t,i=0,n=null){const a=i?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu"),s=ui.menu.getChildren(e);if(i>1)return E([]);if(0===i){const e=document.body?.dataset?.navType||"mega-menu";if("sidebar"===e)return this.renderSidebar(n||[]),a||E([]);if(!a||!s.length)return E([]);"mega-menu"===e?this.initMegaMenu(s,t,a):this.initDropdown(s,t,a)}else{if(!s.length)return E([]);s.forEach(i=>{const n=this.isActivePath(e.name,i.name),s={class:n?"is-active-page":"",href:L.url(t,i.name)};n&&(s["aria-current"]="page"),a.appendChild(E("li",{},[E("a",s,[_(i.title)])]))})}return a.style.display="",a},isActivePath:(e,t)=>L.env.dispatchpath[1]===e&&(null==t||L.env.dispatchpath[2]===t),buildNavigationModel(e,t){return e.filter(e=>e?.name).map(e=>{const i=ui.menu.getChildren(e).filter(e=>e?.name).map(i=>({name:i.name,title:_(i.title),href:L.url(t,e.name,i.name),isActivePage:this.isActivePath(e.name,i.name)})),n=i.length>0,a=this.isActivePath(e.name);return{name:e.name,title:_(e.title),href:L.url(t,e.name),hasChildren:n,isLogout:"logout"===e.name,isActiveGroup:n&&a,isActivePage:!n&&a,activePage:i.find(e=>e.isActivePage)||null,pages:i}})},navigationSubmenuId:(e,t)=>`${e}-submenu-${encodeURIComponent(String(t))}`,renderNavigationItem(e,t){const i="mobile"===t,n=i?"mobile-nav-item":"",a=i?"navigation-direct mobile-nav-link":"navigation-direct nav-link";if(!e.hasChildren){const t={class:`${a}${e.isActivePage?" is-active-page":""}`,href:e.href};return e.isActivePage&&(t["aria-current"]="page"),E("li",{class:n},[E("a",t,[e.title])])}const s=this.navigationSubmenuId(t,e.name),o=["navigation-group",i?"mobile-nav-item":"sidebar-group",e.isActiveGroup?"is-active-group":"",e.isActiveGroup?"is-expanded":""].filter(Boolean),l={class:i?"navigation-group-toggle mobile-nav-link mobile-nav-toggle":"navigatLine truncated
@@ -14,10 +14,10 @@
import { cursor } from 'uci'; import { cursor } from 'uci';
import { lsdir, readfile } from 'fs'; import { lsdir, readfile } from 'fs';
const boardinfo = ubus.call('system', 'board') ?? {}; const boardinfo = prefetched_boardinfo ?? (ubus.call('system', 'board') ?? {});
const hostname = striptags(boardinfo.hostname ?? '?'); const hostname = striptags(boardinfo.hostname ?? '?');
const uci = cursor(); const uci = cursor();
const tokens = uci.get_all('aurora', 'theme') || {}; const tokens = prefetched_tokens ?? (uci.get_all('aurora', 'theme') || {});
const page_segs = length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []); const page_segs = length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []);
const page = join('-', page_segs); const page = join('-', page_segs);
@@ -14,7 +14,11 @@ const themeTokens = cursor().get_all('aurora', 'theme') || {};
const loginLogoSvg = themeTokens.logo_svg || 'logo.svg'; const loginLogoSvg = themeTokens.logo_svg || 'logo.svg';
const loginIconVer = themeTokens.icon_cache_version || '0'; const loginIconVer = themeTokens.icon_cache_version || '0';
-%} -%}
{% include('header', { blank_page: true }) %} {% include('header', {
blank_page: true,
prefetched_boardinfo: boardinfo,
prefetched_tokens: themeTokens
}) %}
{% if (themeTokens.struct_login_bg): %} {% if (themeTokens.struct_login_bg): %}
<div class="login-bg" aria-hidden="true"></div> <div class="login-bg" aria-hidden="true"></div>