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

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

View File

@ -68,7 +68,7 @@ return view.extend({
o = s.option(form.ListValue, 'fullcone', _('Enable FullCone NAT'));
o.value('0', _('Disable'));
o.value('1', _('FULLCONENAT'));
o.value('2', _('Boardcom Fullcone NAT1'));
o.value('2', _('Broadcom Fullcone NAT1'));
addTCPCCAOption();
if (fw4)
o = s.option(form.Flag, 'fullcone6', _('Enable FullCone NAT6'));

View File

@ -1 +0,0 @@
.DS_Store

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.

View File

@ -1,19 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2022-2023 ImmortalWrt.org
# Copyright (C) 2022-2024 ImmortalWrt.org
include $(TOPDIR)/rules.mk
LUCI_TITLE:=The modern ImmortalWrt proxy platform for ARM64/AMD64
LUCI_PKGARCH:=all
LUCI_DEPENDS:= \
+sing-box \
+firewall4 \
+kmod-nft-tproxy \
+ucode-mod-digest
PKG_NAME:=luci-app-homeproxy
define Package/luci-app-homeproxy/conffiles
/etc/config/homeproxy
/etc/homeproxy/certs/

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

View File

@ -1543,87 +1543,6 @@ return view.extend({
/* Direct domain list 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();
}
});

View File

@ -47,7 +47,7 @@ function getConnStat(o, site) {
let ele = o.default.firstElementChild.nextElementSibling;
if (ret.result) {
ele.style.setProperty('color', 'green');
ele.innerHTML = ret.elapsed ? _('passed (%dms)').format(ret.elapsed) : _('passed');
ele.innerHTML = _('passed');
} else {
ele.style.setProperty('color', 'red');
ele.innerHTML = _('failed');
@ -238,24 +238,6 @@ return view.extend({
o = s.option(form.DummyValue, '_check_google', _('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.anonymous = true;

View File

@ -2958,9 +2958,6 @@ msgstr ""
msgid "passed"
msgstr ""
msgid "passed (%dms)"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy/server.js:791
msgid "private key"
msgstr ""
@ -3052,57 +3049,3 @@ msgstr ""
#: htdocs/luci-static/resources/homeproxy.js:328
msgid "valid uuid"
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 ""

View File

@ -3027,9 +3027,6 @@ msgstr "packet addr (v2ray-core v5+)"
msgid "passed"
msgstr "通过"
msgid "passed (%dms)"
msgstr "通过(%dms"
#: htdocs/luci-static/resources/view/homeproxy/server.js:791
msgid "private key"
msgstr "私钥"
@ -3122,57 +3119,3 @@ msgstr "有效端口值"
#: htdocs/luci-static/resources/homeproxy.js:328
msgid "valid 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密钥= 上方配置的面板密钥。"

View File

@ -33,9 +33,6 @@ config homeproxy 'config'
option ipv6_support '1'
option github_token ''
option log_level 'warn'
option dashboard_enabled '0'
option dashboard_port '9090'
option dashboard_secret ''
config homeproxy 'control'
option lan_proxy_mode 'disabled'

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
20260424052559
20260704060757

View File

@ -1,12 +1,6 @@
2001:250::/30
2001:254::/31
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:cc0::/32
2001:da8::/32
@ -17,30 +11,16 @@
2001:dc7::/32
2001:dd8:1::/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:df7:1480::/48
2001:4510:400::/40
2400:1160::/32
2400:3200::/32
2400:5280:f803::/48
2400:54a0:21c2::/47
2400:5a00::/32
2400:5a60:2::/48
2400:5f60::/32
2400:6000::/32
2400:6460::/39
2400:6460:300::/40
2400:6460:500::/40
2400:6460:600::/40
2400:6600::/32
2400:6e60:1301::/48
2400:75aa::/32
2400:7bc0:20::/43
2400:7fc0::/40
2400:7fc0:220::/44
2400:7fc0:240::/44
@ -56,16 +36,10 @@
2400:8200::/32
2400:89c0:1010::/44
2400:89c0:1050::/46
2400:89c0:1130::/44
2400:89c0:1150::/48
2400:89c0:2100::/48
2400:89c0:2200::/48
2400:89c0:3010::/44
2400:89c0:6000::/48
2400:89c0:6100::/48
2400:8fc0:571::/48
2400:8fc0::/32
2400:9020:f010::/46
2400:9340::/32
2400:9380:8001::/48
2400:9380:8003::/48
2400:9380:8021::/48
@ -113,10 +87,9 @@
2400:dd00::/28
2400:ee00:ffec::/46
2400:ee00:fff0::/44
2400:f6e0::/32
2400:f720::/32
2400:fe00::/32
2401:20::/40
2401:20:1330::/46
2401:800::/32
2401:1160::/32
2401:1320::/32
@ -128,20 +101,11 @@
2401:3480:3000::/36
2401:34a0::/31
2401:3800::/32
2401:70e0::/32
2401:5c20:10::/48
2401:71c0::/48
2401:7660::/48
2401:8a00:11::/48
2401:8a00:12::/48
2401:7e00::/32
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:10::/46
2401:a140:1::/48
@ -161,49 +125,26 @@
2401:cc00::/32
2401:ce00::/32
2401:d0e0:3::/48
2401:d180::/46
2401:d180:10::/47
2401:d180:111::/48
2401:d180:2120::/48
2401:d920::/48
2401:de00::/32
2401:ec00::/32
2401:f860:85::/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
2401:fa00:40::/48
2402:840:d000::/46
2402:840:e000::/46
2402:840:f000::/38
2402:c60::/48
2402:1440::/32
2402:1460::/32
2402:18a0::/32
2402:2000::/32
2402:20e0:f000::/48
2402:3180::/46
2402:3180:8000::/33
2402:33e0::/32
2402:36e0::/44
2402:3c00::/32
2402:3f80:1400::/40
2402:4b80::/32
2402:4e00::/32
2402:5ec0::/32
2402:6e80::/32
2402:6f40::/48
2402:6f40:2::/48
2402:6f40:2::/47
2402:7d80::/48
2402:7d80:240::/48
2402:7d80:8888::/48
2402:8bc0::/32
2402:8cc0::/40
2402:8cc0:200::/40
2402:92c0::/48
2402:93c0::/48
2402:93c0:20::/48
@ -216,11 +157,9 @@
2402:b8c0:186::/48
2402:d340::/32
2402:db40:5100::/48
2402:db40:5900::/48
2402:db40:5f00::/46
2402:dfc0::/44
2402:dfc0:50::/44
2402:e380:100::/40
2402:e480::/32
2402:e740::/32
2402:e7c0::/32
@ -239,26 +178,18 @@
2403:1ec0:1400::/48
2403:1ec0:1600::/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:2b40::/32
2403:3140::/32
2403:4240::/32
2403:4280::/47
2403:4300::/32
2403:4c80::/48
2403:5c80::/48
2403:6380:40::/46
2403:6380:60::/44
2403:6740::/48
2403:6a00::/32
2403:7580::/32
2403:8080:101::/48
2403:8c00::/32
2403:9b00:2000::/48
2403:9b00:2400::/48
2403:9b00:6000::/48
2403:a100::/48
@ -266,8 +197,6 @@
2403:a140:100::/40
2403:a200::/32
2403:ac00::/32
2403:ad80:8008::/48
2403:ad80:8047::/48
2403:b400::/32
2403:c980::/32
2403:d400::/32
@ -321,9 +250,9 @@
2404:c300::/32
2404:c940::/48
2404:e280::/47
2404:e5c0::/32
2404:e8c0::/32
2404:f4c0::/32
2405:80:10::/48
2405:80:13::/48
2405:6c0:2::/48
2405:6c0:4::/48
2405:1480:1000::/48
@ -332,7 +261,7 @@
2405:3140:11::/48
2405:3140:21::/48
2405:3140:31::/48
2405:66c0::/32
2405:3140:3a::/48
2405:68c0:21::/48
2405:6940::/48
2405:6f00:c101::/48
@ -343,84 +272,17 @@
2405:78c0:6e00::/43
2405:8a40::/32
2405:a900:ffee::/48
2405:a900:fffe::/48
2405:ad00::/32
2405:b7c0::/32
2405:d900::/32
2405:e000::/32
2405:f580::/32
2405:f940::/32
2406:280::/32
2406:840:9000::/44
2406:840:e080::/44
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:840:9680::/44
2406:840:f889::/48
2406:1080::/48
2406:2700::/32
2406:3340::/32
2406:3d80::/32
2406:4d00::/48
2406:52c0::/32
2406:5340:6666::/48
@ -447,6 +309,7 @@
2407:6c40:1210::/48
2407:6c40:1500::/48
2407:6c40:1600::/40
2407:7d00:1::/48
2407:8f40:2::/48
2407:9f00::/32
2407:ad80::/32
@ -467,29 +330,19 @@
2408:8000:4000::/34
2408:8000:8000::/33
2408:8001::/32
2408:8020::/30
2408:8024::/31
2408:8024:8000::/33
2408:8025::/32
2408:8026::/32
2408:802a:8000::/33
2408:802c::/32
2408:803e::/32
2408:8056::/32
2408:805c::/30
2408:8060::/33
2408:80c2::/33
2408:80c5::/33
2408:80ca::/33
2408:80ca:8000::/34
2408:80da::/33
2408:80da:8000::/34
2408:80e0:4000::/34
2408:80e0:8000::/33
2408:80e0:4000::/36
2408:80e2::/33
2408:80e9:4000::/34
2408:80ea:4000::/34
2408:80ea:8000::/33
2408:80f0:4000::/34
2408:80f0:8000::/33
2408:80f0:4000::/36
2408:80f1::/42
2408:80f1:40::/43
2408:80f1:70::/44
@ -504,8 +357,6 @@
2408:80f1:200::/40
2408:80f5:4000::/34
2408:80f9:4000::/34
2408:80fa:4000::/34
2408:80fa:8000::/33
2408:8120:1::/48
2408:8120:2::/48
2408:8120:7000::/36
@ -728,86 +579,103 @@
2408:840c:dd00::/40
2408:840c:de00::/40
2408:840d::/42
2408:840d:40::/43
2408:840d:200::/42
2408:840d:400::/42
2408:840d:440::/43
2408:840d:600::/42
2408:840d:d00::/42
2408:840d:d40::/43
2408:840d:f00::/42
2408:840d:1100::/42
2408:840d:1140::/43
2408:840d:1300::/42
2408:840d:1a00::/42
2408:840d:1a40::/43
2408:840d:1c00::/42
2408:840d:1e00::/42
2408:840d:1e40::/43
2408:840d:2000::/42
2408:840d:2700::/42
2408:840d:2740::/43
2408:840d:2900::/42
2408:840d:2b00::/42
2408:840d:2b40::/43
2408:840d:2d00::/42
2408:840d:3400::/42
2408:840d:3440::/43
2408:840d:3600::/42
2408:840d:3800::/42
2408:840d:3840::/43
2408:840d:3a00::/42
2408:840d:4e00::/42
2408:840d:4e40::/43
2408:840d:5000::/42
2408:840d:5200::/42
2408:840d:5240::/43
2408:840d:5400::/42
2408:840d:5b00::/42
2408:840d:5b40::/43
2408:840d:5d00::/42
2408:840d:5f00::/42
2408:840d:5f40::/43
2408:840d:6100::/42
2408:840d:6800::/42
2408:840d:6840::/43
2408:840d:6a00::/42
2408:840d:6c00::/42
2408:840d:6c40::/43
2408:840d:6e00::/42
2408:840d:7500::/42
2408:840d:7540::/43
2408:840d:7700::/42
2408:840d:7900::/42
2408:840d:7940::/43
2408:840d:7b00::/42
2408:840d:8200::/42
2408:840d:8240::/43
2408:840d:8400::/42
2408:840d:8600::/42
2408:840d:8640::/43
2408:840d:8800::/42
2408:840d:8f00::/42
2408:840d:8f40::/43
2408:840d:9100::/42
2408:840d:9300::/42
2408:840d:9340::/43
2408:840d:9500::/42
2408:840d:9c00::/42
2408:840d:9c40::/43
2408:840d:9e00::/42
2408:840d:a000::/42
2408:840d:a040::/43
2408:840d:a200::/42
2408:840d:a900::/42
2408:840d:a940::/43
2408:840d:ab00::/42
2408:840d:ad00::/42
2408:840d:ad40::/43
2408:840d:af00::/42
2408:840d:b600::/42
2408:840d:b640::/43
2408:840d:b800::/42
2408:840d:ba00::/42
2408:840d:ba40::/43
2408:840d:bc00::/42
2408:840d:c300::/42
2408:840d:c340::/43
2408:840d:c500::/42
2408:840d:c700::/42
2408:840d:c740::/43
2408:840d:c900::/42
2408:840d:d000::/42
2408:840d:d040::/43
2408:840d:d200::/42
2408:840d:d400::/42
2408:840d:d440::/43
2408:840d:d600::/42
2408:840d:dd00::/42
2408:840d:dd40::/43
2408:840d:de00::/42
2408:840e:dd00::/40
2408:840e:de00::/40
@ -949,6 +817,7 @@
2408:8878::/31
2408:887e::/32
2408:8906:20::/44
2408:8906:40::/43
2408:8907:9000::/44
2408:890c::/31
2408:8912::/31
@ -985,13 +854,9 @@
2408:8a05:8000::/36
2408:8a06::/47
2408:8a06:100::/47
2408:8a07::/40
2408:8a21:4000::/35
2408:8a22:9200::/39
2408:8a22:9400::/38
2408:8a22:9800::/40
2408:8a22:9a00::/39
2408:8a22:9c00::/38
2408:8a22:a000::/37
2408:8a22::/35
2408:8a23:4000::/34
2408:8a24:4000::/34
2408:8a26:c000::/34
@ -1004,6 +869,7 @@
2409:27fb::/48
2409:27fc::/48
2409:27fe::/33
2409:6100::/44
2409:8000::/20
240a:2000::/40
240a:2001:100::/40
@ -1038,6 +904,7 @@
240a:40c1:2000::/43
240a:40c1:4000::/43
240a:40c1:6000::/43
240a:40c1:8000::/43
240a:40c1:a000::/43
240a:40c1:c000::/43
240a:40c1:e000::/43
@ -1073,32 +940,10 @@
240b:e004::/30
240b:e008::/29
240b:e010::/32
240c:6::/47
240c:6:3::/48
240c:f:1::/48
240c:4000::/22
240c:c000::/20
240d:4000::/21
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:8011::/48
2605:9d80:8021::/48
@ -1114,7 +959,6 @@
2605:9d80:9092::/48
2804:1e48:9001::/48
2804:1e48:9002::/48
2a01:f100:1f8::/48
2a04:3e00:1002::/48
2a04:f580:8010::/47
2a04:f580:8090::/48
@ -1139,249 +983,26 @@
2a04:f580:9270::/48
2a04:f580:9280::/48
2a04:f580:9290::/48
2a06:3600:e000::/40
2a06:3600:fb00::/40
2a06:3600:fc00::/38
2a06:3605::/32
2a06:3606::/31
2a06:9f81:4600::/43
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
2a0a:d685:1ff::/48
2a0a:d685:300::/40
2a0b:4340:90::/48
2a0b:4340:97::/48
2a0d:c7c7:400::/38
2a0e:aa06:49f::/48
2a0e:b107:740::/44
2a0e:b107:c10::/48
2a0e:b107:178d::/48
2a0e:b107:178e::/48
2a0f:1cc5:20::/44
2a0f:1cc5:110::/44
2a0f:1cc5:130::/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
2a0e:b107:2440::/44
2a0f:1cc5:1600::/44
2a0f:1cc5:4700::/40
2a12:cb41:1200::/44
2a12:cb41:1300::/43
2a12:cb41:13f0::/44
2a13:1801:180::/43
2a13:a5c3:ff21::/48
2a13:a5c3:ff50::/44
2a13:a5c7:1800::/40
2a13:a5c7:2121::/48
2a13:a5c7:2801::/48
2a13:a5c7:3100::/40
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
2a13:a5c3:f000::/40
2a14:7580:d00::/43
2a14:7580:d20::/46
2a14:7580:d41::/48
2a14:7580:d42::/47
2a14:7580:dff::/48
2c0f:f7a8:8011::/48
2c0f:f7a8:8050::/48
2c0f:f7a8:805f::/48
@ -1390,5 +1011,4 @@
2c0f:f7a8:8211::/48
2c0f:f7a8:9020::/48
2c0f:f7a8:9041::/48
2c0f:f7a8:9211::/48
2c0f:f7a8:9220::/48

View File

@ -1 +1 @@
20260424052559
20260704060757

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
202604232238
202607052256

View File

@ -13,6 +13,7 @@
173ng.com
18board.com
18comic.org
18mh.net
1984bbs.com
1991way.com
1e100.net
@ -25,6 +26,7 @@
2047.one
2049bbs.xyz
233abc.com
24hbook.com
24hrs.ca
32red.com
36rain.com
@ -72,6 +74,7 @@
9cache.com
9gag.com
9news.com.au
9oju31.launches.appsflyersdk.com
a-normal-day.com
a248.e.akamai.net
aamacau.com
@ -86,8 +89,10 @@ about.gitlab.com
about.me
abplive.com
acast.com
accountboy.com
accountkit.com
acevpn.com
acg.mn
acg.rip
acg18.me
acgbox.org
@ -126,6 +131,7 @@ aiosearch.com
aiph.net
airconsole.com
airitilibrary.com
airuniversity.af.edu
airvpn.org
ait.org.tw
aiweiweiblog.com
@ -205,13 +211,18 @@ aoxvpn.com
apartmentratings.com
apartments.com
apat1989.org
api-gl.lbkrs.com
api-secure.recaptcha.net
api-verify.recaptcha.net
api.ai
api.futunn.com
api.lbkrs.com
api.moomoobull.com
api.palworldgame.com
api.pureapk.com
api.recaptcha.net
api.steampowered.com
api5.futunn.com
apiary.io
apigee.com
apk.support
@ -257,6 +268,7 @@ arte.tv
artstation.com
arvanstorage.ir
arweave.org
as.mr
asacp.org
asiaharvest.org
asianage.com
@ -321,6 +333,7 @@ bangchen.net
bangdream.space
bangkokpost.com
bangumi.moe
bangumi.tv
bangyoulater.com
bankmobilevibe.com
bannedbook.org
@ -376,6 +389,7 @@ bettween.com
betvictor.com
bfnn.org
bfsh.hk
bgm.tv
bgme.me
bgvpn.com
biblesforamerica.org
@ -386,6 +400,7 @@ bigjapanesesex.com
bignews.org
bigone.com
bild.de
bilinovel.com
biliworld.com
binance.com
binance.org
@ -413,6 +428,7 @@ bl-doujinsouko.com
blacked.com
blackmagicdesign.com
blackvpn.com
bleepingcomputer.com
blinkx.com
blip.tv
blockcast.it
@ -525,6 +541,7 @@ bookdepository.com
books.com.tw
booktopia.com.au
bookwalker.com.tw
boomplay.com
bootstrapcdn.com
borgenmagazine.com
botanwang.com
@ -648,6 +665,7 @@ cdjp.org
cdn-images.mailchimp.com
cdn-telegram.org
cdn.arstechnica.net
cdn.futustatic.com
cdn.jwplayer.com
cdn.printfriendly.com
cdn.seatguru.com
@ -671,6 +689,7 @@ centurys.net
certificate-transparency.org
certificate.revocationcheck.com
cfr.org
cg.play-analytics.com
cgdepot.org
change.org
changp.com
@ -692,6 +711,7 @@ chenpokongvip.com
chenshan20042005.wordpress.com
cherrysave.com
chhongbi.org
chii.in
china-mmm.net
china-review.com.ua
china-week.com
@ -736,6 +756,7 @@ chineseupress.com
chingcheong.com
chinman.net
chobit.cc
chonglangtv.org
chrdnet.com
christianfreedom.org
christianstudy.com
@ -850,6 +871,7 @@ crazypool.org
crazyshit.com
crbug.com
crchina.org
creader.com
creaders.net
creativelab5.com
cristyli.com
@ -871,6 +893,7 @@ csw.org.uk
ct.org.tw
ctinets.com
ctinews.com
ctinsider.com
ctitv.com.tw
ctowc.org
cts.com.tw
@ -913,6 +936,7 @@ darpa.mil
darrenliuwei.com
dashlane.com
data-vocabulary.org
data18.com
daum.net
david-kilgour.com
dawangidc.com
@ -1011,7 +1035,9 @@ doublethinklab.org
douchi.space
dougscripts.com
doujincafe.com
dowjones.io
download.dappcdn.com
download.futunn.com
dpool.top
dpp.org.tw
dpr.info
@ -1024,6 +1050,7 @@ dropboxapi.com
dropboxusercontent.com
dscn.info
dstk.dk
dt18.com
dtiblog.com
dtic.mil
dubox.com
@ -1065,6 +1092,7 @@ ecfa.org.tw
ecimg.tw
economist.com
edgecastcdn.net
edigest.hk
edubridge.com
edx-cdn.org
eesti.ee
@ -1135,6 +1163,7 @@ expatshield.com
expecthim.com
expekt.com
exploader.net
expressnews.com
expressvpn.com
exrates.me
extmatrix.com
@ -1143,6 +1172,7 @@ eyny.com
f-droid.org
f2pool.com
f8.com
f95zone.to
facebook.com
facebook.de
facebook.design
@ -1324,12 +1354,42 @@ funkyimg.com
furbo.org
furinkan.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
futustatic.com
fututrade.com
fututrustee.com
fw.cm
fxcm-chinese.com
fxnetworks.com
g-area.org
g-queen.com
g.ai
g.co
g0v.social
g6hentai.com
@ -1346,6 +1406,7 @@ gaoming.net
gaopi.net
gartlive.com
garudalinux.org
gate.com
gate.io
gatecoin.com
gather.com
@ -1361,10 +1422,12 @@ gdaily.org
geek-art.net
gekikame.com
gelbooru.com
gencraft.com
generated.photos
genius.com
geph.io
get.app
get.dappcdn.com
get.dev
get.how
get.page
@ -1722,6 +1785,7 @@ gtv.org
gtv1.org
gu-chu-sum.org
guaguass.com
guangming.com.my
guishan.org
gumroad.com
gunsamerica.com
@ -1775,6 +1839,7 @@ heyuedi.com
hicairo.com
hiccears.com
hidden-advent.org
hiddify.com
hide.me
hideipvpn.com
hideman.net
@ -1790,6 +1855,7 @@ hindustantimes.com
hinet.net
hitbtc.com
hitomi.la
hive.blog
hiveon.net
hk-pub.com
hk.gradconnection.com
@ -1810,6 +1876,7 @@ hkgolden.com
hkgpao.com
hklts.org.hk
hkmap.live
hkong.hk
hkopentv.com
hkpeanut.com
hkreporter.com
@ -1964,6 +2031,7 @@ indiatoday.in
indiemerch.com
inews-api.tvb.com
info-graf.fr
infosec.exchange
infura.io
inherit.live
initiativesforchina.org
@ -2023,6 +2091,7 @@ itasoftware.com
itch.io
itemfix.com
itiger.com
itigerup.com
itshidden.com
itweet.net
iuhrdf.org
@ -2102,6 +2171,7 @@ kanshifang.com
kantie.org
kaotic.com
karayou.com
kathmandupost.com
kawaiikawaii.jp
kawase.com
kb.monitorware.com
@ -2111,6 +2181,7 @@ kenengba.com
kepard.com
kex.com
keycdn.com
kfor.com
khatrimaza.org
kichiku-doujinko.com
kik.com
@ -2160,12 +2231,15 @@ landofhope.tv
lantern.io
laogai.org
laogairesearch.org
laohu8.com
laqingdan.net
larsgeorge.com
lastcombat.com
lastfm.es
lausan.hk
lbank.info
lbctrl.com
lbkrs.com
ldplayer.net
ldplayer.tw
le-vpn.com
@ -2176,7 +2250,9 @@ left21.hk
legra.ph
leisurepro.com
lematin.ch
lemonde.fr
lenwhite.com
leonardo.ai
lesoir.be
letscorp.net
lexica.art
@ -2188,6 +2264,7 @@ liaowangxizang.net
liberal.org.hk
libertysculpturepark.com
libertytimes.com.tw
libgen.is
library-access.sk
library.usc.cuhk.edu.hk
libredd.it
@ -2231,6 +2308,15 @@ localdomain.ws
lockestek.com
login.target.com
logos.com.hk
longbridge.cloud
longbridge.com
longbridge.global
longbridge.hk
longbridge.sg
longbridgeapp.com
longbridgehk.com
longportapp.cn
longportapp.com
longtermly.net
longtoes.com
lookpic.com
@ -2283,6 +2369,7 @@ martau.com
martsangkagyuofficial.org
marxist.net
marxists.org
mas.to
mash.to
mastodon.cloud
mastodon.host
@ -2309,6 +2396,7 @@ meansys.com
media.nu.nl
mediachinese.com
mediafreakcity.com
mediamatters.org
mediawiki.org
medium.com
meee.com.tw
@ -2346,6 +2434,7 @@ mfxmedia.com
mgoon.com
mgstage.com
mh4u.org
mhwindow.org
microvpn.com
mihua.org
mij.rip
@ -2388,6 +2477,7 @@ mixx.com
mizzmona.com
mji.rip
mjj.rip
mjj.today
mjlsh.usc.cuhk.edu.hk
mlc.ai
mlzs.work
@ -2414,6 +2504,9 @@ monitorchina.org
monocloud.me
monster.com
moodyz.com
moomoo.com
moomooequity.com
moomootrustee.com
moon.fm
moonbbs.com
moonbingo.com
@ -2425,6 +2518,7 @@ mosavi.io
motherless.com
movements.org
moviefap.com
mozilla.ai
mpettis.com
mpfinance.com
mpinews.com
@ -2437,6 +2531,7 @@ mthruf.com
mubi.com
mullvad.net
multiply.com
music.amazon.com
musixmatch.com
muzi.com
muzi.net
@ -2487,6 +2582,7 @@ ncn.org
ndi.org
nekoslovakia.net
neo-miracle.com
neodb.social
neowin.net
nephobox.com
netalert.me
@ -2510,10 +2606,12 @@ news1.kr
news18.com
newsancai.com
newsblur.com
newshub.co.nz
newsmax.com
newstamago.com
newstapa.org
newstatesman.com
newstimes.com
newsweek.com
newtalk.tw
newthuhole.com
@ -2528,6 +2626,8 @@ nflximg.com
nflximg.net
nflxso.net
nflxvideo.net
nfsc.press
nfscofficial.com
nftstorage.link
nga.mil
nhentai.net
@ -2647,6 +2747,13 @@ ooni.io
ooni.org
open.firstory.me
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
opencritic.com
opendemocracy.net
@ -2673,6 +2780,7 @@ oursteps.com.au
ourtv.hk
overcast.fm
overdaily.org
overdrive.com
overplay.net
oversea.istarshine.com
ovpn.com
@ -2685,7 +2793,6 @@ pachosting.com
pacom.mil
pacopacomama.com
page.link
pages.dev
paimon.moe
pancakeswap.finance
pandafan.pub
@ -2712,6 +2819,7 @@ pbs.org
pbworks.com
pbxes.com
pbxes.org
pcgamesn.com
pcgamestorrents.com
pcij.org
pct.org.tw
@ -2724,11 +2832,13 @@ pekingduck.org
pemulihan.or.id
pen.io
pendrivelinux.com
penguin.com.au
pentoy.hk
peoplenews.tw
peopo.org
perfect-privacy.com
periscope.tv
perma.cc
perplexity.ai
pewresearch.org
phayul.com
@ -2900,12 +3010,15 @@ qmp4.com
qoos.com
qq.co.za
qstatus.com
qtcard.futunn.com
qtcardfthk.futufin.com
qtrac.eu
questvisual.com
quitccp.org
quiz.directory
quora.com
quoracdn.net
quote.longbridge.global
quran.com
qz.com
r-pool.net
@ -2931,6 +3044,7 @@ raindrop.io
raizoji.or.jp
rakuten.co.jp
ramcity.com.au
raphael.app
rapidmoviez.com
rapidvpn.com
rarbgprx.org
@ -2973,6 +3087,7 @@ rentry.co
renyurenquan.org
resilio.com
resistchina.org
restofworld.org
retweetrank.com
reuters.com
reutersmedia.net
@ -3009,6 +3124,8 @@ rthk.org.hk
rti.org.tw
rti.tw
rtm.tnt-ea.com
rts.ch
rule34.us
rule34.xxx
rule34video.com
rumble.com
@ -3043,10 +3160,11 @@ savetibetstore.org
saveuighur.org
sbme.me
sbti.unun.dev
scache.vzw.com
scache1.vzw.com
scache2.vzw.com
schema.org
schwab.co.uk
schwab.com
schwab.com.cn
schwab.com.hk
scmp.com
scramble.io
scratch.mit.edu
@ -3085,6 +3203,7 @@ sexinsex.net
sf.net
sfshibao.com
sftuk.org
sgqt0j.launches.appsflyersdk.com
shadeyouvpn.com
shadowsocks.be
shadowsocks.com.hk
@ -3109,11 +3228,13 @@ shodanhq.com
shooshtime.com
shopee.tw
shopping.yahoo.co.jp
shortconn.im.qcloud.com
showwe.tw
shutterstock.com
shwchurch.org
shwchurch3.com
sidelinesnews.com
sider.ai
signal.org
silvergatebank.com
simbolostwitter.com
@ -3123,6 +3244,7 @@ simplecd.org
simpleswap.io
simplex.chat
sina.com.hk
sinchew.com.my
singaporepools.com.sg
singlelogin.se
singtao.com
@ -3131,6 +3253,7 @@ sinoants.com
sinoca.com
sinocast.com
sinoinsider.com
sinyalee.com
sipml5.org
sis001.com
site.new
@ -3166,7 +3289,7 @@ socialblade.com
socks-proxy.net
sockslist.net
socrec.org
softether-download.com
softether.co.jp
softether.org
softfamous.com
softwarebychuck.com
@ -3187,6 +3310,7 @@ sorting-algorithms.com
sos.org
sosad.fun
sosreader.com
sotwe.com
soubory.com
soulcaliburhentai.net
soundcloud.com
@ -3199,6 +3323,7 @@ southmongolia.org
southnews.com.tw
southpark.cc.com
sowers.org.hk
spaceforce.mil
spaces.hightail.com
spacex.com
spankbang.com
@ -3225,7 +3350,6 @@ springwood.me
sproutcore.com
squirrelvpn.com
ss-link.com
ss7.vzw.com
ssglobal.co
ssl.webpack.de
sspanel.net
@ -3290,11 +3414,14 @@ supchina.com
superpages.com
supervpn.net
superzooi.com
support.futunn.com
supremecourt.gov
suprememastertv.com
surfeasy.com
surfeasy.com.au
surfshark.com
surrenderat20.net
suyingtv.com
swagbucks.com
swapspace.co
swissinfo.ch
@ -3345,6 +3472,7 @@ tbsec.org
tbsn.org
tbssqh.org
teachparentstech.org
techbang.com
technews.tw
techviz.net
teck.in
@ -3375,6 +3503,8 @@ tfhub.dev
tfiflve.com
tg-me.com
tg.dev
tgstat.com
the-sun.com
theatlantic.com
theatrum-belli.com
thebcomplex.com
@ -3397,6 +3527,7 @@ thehansindia.com
thehindu.com
thehun.net
theinitium.com
theintercept.com
thenewslens.com
thepiratebay.org
theporndude.com
@ -3407,6 +3538,7 @@ thestandnews.com
thetatoken.org
thetibetpost.com
thetvdb.com
theweek.com
thewgo.org
thewirechina.com
theync.com
@ -3474,6 +3606,12 @@ tibettelegraph.com
tibettimes.net
tibettruth.com
tibetwrites.org
tigerbbs.cn
tigerbbs.com
tigerbrokers.com
tigerbrokers.net
tigerbrokers.nz
tigerfintech.com
tigervpn.com
tiktok.com
tiktokcdn-eu.com
@ -3536,6 +3674,9 @@ totalvpn.com
tou.tv
tpi.org.tw
tracfone.com
trade.futunn.com
trade.longportapp.com
tradeup.com
tradingview.com
translate.goog
transparency.org
@ -3544,6 +3685,7 @@ trendsmap.com
tronscan.org
trouw.nl
trt.net.tr
trtworld.com
truebuddha-md.org
trustwallet.com
truthsocial.com
@ -3580,6 +3722,7 @@ tv.com
tv.jtbc.joins.com
tvbanywhere.com
tvboxnow.com
tvdy1.com
tvunetworks.com
tw-blog.com
tw.jiepang.com
@ -3638,6 +3781,7 @@ twitturk.com
twitturly.com
twkan.com
twreporter.org
twstalker.com
twt.tl
twtkr.com
twttr.com
@ -3666,6 +3810,7 @@ ulop.net
ultrasurf.us
ultravpn.com
ultravpn.fr
umap.openstreetmap.fr
unblock-us.com
unblock.cn.com
unblockdmm.com
@ -3689,6 +3834,7 @@ upghsbc.com
upmedia.mg
upornia.com
uproxy.org
upsangel.com
uptodown.com
uraban.me
urbandictionary.com
@ -3736,6 +3882,7 @@ vaticannews.va
vatn.org
vcf-online.org
vcfbuilder.org
veed.io
vegas.williamhill.com
vegasred.com
venetianmacao.com
@ -3787,6 +3934,7 @@ vod.wwe.com
voicettank.org
vot.org
vovo2000.com
vox.com
voxer.com
vpl.bibliocommons.com
vpn.ac
@ -3848,6 +3996,7 @@ wanz-factory.com
warroom.org
waselpro.com
washingtonpost.com
washingtontimes.com
watchinese.com
watchmygf.net
watchout.tw
@ -3856,6 +4005,7 @@ wav.tv
waveprotocol.org
waybig.com
waymo.com
wbrks.com
wd.bible
wealth.com.tw
wearn.com
@ -3865,6 +4015,7 @@ webmproject.org
webpkgcache.com
webrtc.org
websdr.org
webshare.io
website.new
webwarper.net
wechatlawsuit.com
@ -3962,12 +4113,14 @@ work2icu.org
workatruna.com
workerempowerment.org
workers.dev
worldjournal.com
worldvpn.net
wowgirls.com
wowhead.com
wowporn.com
woyaolian.org
wp.com
wplace.live
wpoforum.com
writesonic.com
wsj.com
@ -4032,6 +4185,7 @@ xgmyd.com
xhamster.com
xianjian.tw
xiaohexie.com
xiaohu8.com
xiaolan.me
xiaoma.org
xiaomi.eu
@ -4056,6 +4210,7 @@ xn--noss43i.com
xn--oiq.cc
xn--p8j9a0d9c9a.xn--q9jyb4c
xn--u2u927b.com
xn--vuqv2cf7wzyig79c.com
xnpool.com
xnxx.com
xpud.org
@ -4064,6 +4219,7 @@ xskywalker.com
xt.com
xt.pub
xtube.com
xuan.com.my
xuchao.net
xuchao.org
xuehua.us
@ -4102,7 +4258,6 @@ yeyeclub.com
yfsp.tv
yhcw.net
yibaochina.com
yidio.com
yigeni.com
yipub.com
yizhihongxing.com
@ -4165,11 +4320,14 @@ zh.ecdm.wikia.com
zh.pokerstrategy.com
zh.wikiquote.org
zhangtianliang.com
zhangzhehan.net
zhanlve.org
zhao.1984.city
zhengjian.org
zhengwunet.org
zhenxiang.biz
zhijianfengyi.cn
zhijianfengyi.com
zhizhu.top
zhongguo.ca
zhongguotese.net

View File

@ -1 +1 @@
202604232238
202607052256

View File

@ -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 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';
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';
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')
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_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';
@ -930,7 +924,6 @@ if (!isEmpty(main_node)) {
user: cfg.user,
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_accept_empty: strToBool(cfg.rule_set_ip_cidr_accept_empty),
invert: strToBool(cfg.invert),
action: cfg.action,
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 */
system('mkdir -p ' + RUN_DIR);

View File

@ -8,7 +8,7 @@
'use strict';
import { cursor } from 'uci';
import { isEmpty, parseURL } from 'homeproxy';
import { isEmpty, parseURL, validation } from 'homeproxy';
const uci = cursor();

View File

@ -137,12 +137,7 @@ const methods = {
break;
}
let start = clock(true);
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 };
return { result: (system(`/usr/bin/wget --spider -qT3 ${url} 2>"/dev/null"`, 3100) === 0) };
}
},

View File

@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.7.16
PKG_RELEASE:=64
PKG_RELEASE:=65
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \

View File

@ -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_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_acl_log"}, call("get_acl_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, "index_status"}, call("index_status")).leaf = true
@ -322,6 +323,18 @@ function get_socks_log()
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()
-- 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"))

View File

@ -78,6 +78,33 @@ o = s:option(Value, "remarks", translate("Remarks"))
o.default = arg[1]
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:value("", translate("All"))
local iface = api.get_network_devices()

View File

@ -1021,11 +1021,15 @@ acl_app() {
dnsmasq_port=${GLOBAL_DNSMASQ_PORT:-11400}
for item in $items; do
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 sid=$(uci -q show "${CONFIG}.${item}" | grep "=acl_rule" | awk -F '=' '{print $1}' | awk -F '.' '{print $2}')
[ "$(config_n_get $sid enabled)" = "1" ] || continue
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
for s in $sources; do
@ -1121,7 +1125,8 @@ acl_app() {
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_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=$?
if [ "$status" != 0 ]; then
log_i18n 2 "[%s] process %s error, skip this transparent proxy!" "${remarks}" "${config_file}"

View File

@ -194,7 +194,7 @@ return view.extend({
o.value('0', _('Disable'))
if (features.hasXTFULLCONENAT || features.hasNFTFULLCONENAT) {
o.value('1', _('FULLCONENAT'));
o.value('2', _('Boardcom Fullcone NAT1'));
o.value('2', _('Broadcom Fullcone NAT1'));
}
o.default = '0';
o.rmempty = false;

View File

@ -5,8 +5,8 @@ msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "Acceleration Status"
msgstr ""
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:182
msgid "Boardcom fullcone"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:197
msgid "Broadcom Fullcone NAT1"
msgstr ""
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:151

View File

@ -12,10 +12,6 @@ msgstr ""
msgid "Acceleration Status"
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
msgid "Bridge Acceleration"
msgstr "桥接加速"
@ -93,9 +89,9 @@ msgstr "全锥形 NAT1"
msgid "FULLCONENAT"
msgstr "兼容模式全锥形 NAT1"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:177
msgid "Boardcom Fullcone NAT1"
msgstr "高性能 Boardcom 全锥形 NAT1"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:197
msgid "Broadcom Fullcone NAT1"
msgstr "高性能 Broadcom 全锥形 NAT1"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:178
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
msgid "xt_FULLCONENAT"
msgstr "兼容模式"
#: applications/luci-app-turboacc/htdocs/luci-static/resources/view/turboacc.js:182
msgid "Boardcom fullcone"
msgstr "高性能模式"

View File

@ -104,9 +104,9 @@ local methods = {
(readfile("/sys/module/nft_fullcone/refcnt") or "0") ~= "0" then
fctype = "nft_FULLCONENAT"
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
fctype = "Boardcom Fullcone NAT1"
fctype = "Broadcom Fullcone NAT1"
end
print(json.stringify({ type = fctype }))
end

View File

@ -45,7 +45,7 @@ label, fixing a typo, adjusting a translation string).
- **S4** — Memory has a ceiling. → references/server.md
- **L1** — Kill render-blocking requests. → 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
- **R2** — JS must not force synchronous layout. → references/runtime.md
- **R3** — Accessibility is functionality. → references/runtime.md

View File

@ -11,8 +11,12 @@ address, so they stay local).
| Metric | Budget | Track | Source |
|---|---|---|---|
| main.css (gzip-transferred) | ≤ 30 KB | size | measured 2026-07 (28 KB) |
| Per-page cold transfer (all theme assets, gzip) | ≤ 60 KB | size | sum of current gzip sizes + headroom |
| main.css (identity/raw) | ≤ 190 KB | size | production build, 2026-07 (183,820 B) |
| 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 |
| 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 |
@ -25,16 +29,22 @@ Budget revisions require a new baseline entry under `../baselines/`.
## Optimization ledger
### 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
| 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 |
| `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
cache-versioning for these links unless inspecting the installed package
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

View File

@ -54,21 +54,23 @@ served from disk/memory cache).
**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
The Wire) and CPU on the slow server (see server.md S1) — compression is the
one change that helps both planes at once.
**Why.** This deployment's stock `uhttpd` serves identity responses even when
the browser advertises gzip. Raw output size is therefore both flash cost and
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`
verbatim (cross-reference server.md S1). Run shipped SVGs through SVGO
(example: aurora's `logo.svg`, which doubles as the favicon, ships at 45 KB —
an unoptimized SVG is a common place for dead editor metadata to hide).
Choose sane image formats (WebP/AVIF over unoptimized PNG/JPEG) for anything
that isn't vector.
**Do / Don't.** Disable broad Tailwind content scanning when all utilities are
declared with `@apply`; omit Preflight from isolated entries that only need a
small reset; remove unused plugins; minify and mangle JS while preserving
LuCI's loader directives; define repeated SVG data URLs once as custom
properties. Resize or re-encode raster assets at their real display needs and
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
directory to catch anything that grew.
**Verify.** `bench.mjs`'s identity rows, automated raw-size budgets, and `du`
on the build output directory to catch anything that grew.
**Quantify.** Per-page transferred bytes (cold load); individual asset
sizes.
@ -79,5 +81,5 @@ sizes.
|---|---|---|
| Blocking requests before first paint | DevTools Network (Slow 4G) | `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` |

View File

@ -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.
**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:
it's the one page a bench script can hit without a session, and TTFB
comparisons must compare like with like.
2. `main.css`, identity (no `Accept-Encoding`)
3. `main.css`, gzip-negotiated
4. `login.css`, gzip-negotiated
5. the theme's menu script, gzip-negotiated (example: aurora's `menu-aurora.js`)
A 403 is accepted for this target only because some LuCI builds render the
login form with that status; other error responses still abort the run.
2. `main.css`, identity
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,
summarized as a Markdown table on stdout.

View File

@ -11,20 +11,18 @@ same 12 cores and 64512 MB of RAM as the rest of OpenWrt. Nothing here is
is already slow. Anything that can be computed once, at build time, should
never run again inside a request handler.
**Do / Don't.** Precompress assets at build time and let `uhttpd` serve the
precompressed file verbatim when the client sends `Accept-Encoding: gzip`
this makes the request-time cost *decrease*, since `uhttpd` has no dynamic
compression of its own. Generate derived CSS/config/tokens at build time,
never per-request (example: aurora's generated `_tokens.css`, built from
`.dev/tokens/` and never touched at runtime). When a genuinely per-request
computation is unavoidable, record the accepted exception with its rationale
in the theme budget sheet (`aurora-budgets.md`) so a future session doesn't re-litigate it
(example: aurora's per-request `lsdir()` patch discovery in `header.ut` — one
cheap readdir, measured negligible and accepted as-is).
**Do / Don't.** Generate and minify derived CSS, JS, config, tokens and image
assets at build time, never per-request (example: aurora's generated
`_tokens.css`). This deployment uses stock `uhttpd` without gzip support, so
raw file size is the wire size: do not add `.gz` files that the server will
never select. When a genuinely per-request computation is unavoidable,
record the accepted exception with its rationale in the theme budget sheet
(`aurora-budgets.md`) so a future session doesn't re-litigate it (example:
aurora's per-request `lsdir()` patch discovery in `header.ut` — one cheap
readdir, measured negligible and accepted as-is).
**Verify.** `top` on the device shows no request-time compression process;
`curl -H 'Accept-Encoding: gzip' -w '%{size_download}'` against the asset
shows the byte drop versus an uncompressed request.
**Verify.** A production build is deterministic and already minified; the
identity-transfer rows in `bench.mjs` match the files' raw byte sizes.
**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,"
it's compute on the same constrained cores serving the request.
**Do / Don't.** Set (and respect) a per-page transferred-bytes budget.
Prefer gzip-negotiated transfer over raw bytes wherever the client supports
it (example: aurora's `main.css` is 211,725 B raw but ~28 KB gzip — always
measure and budget the negotiated size, not the raw one).
**Do / Don't.** Set (and respect) a per-page transferred-bytes budget. On this
stock `uhttpd` deployment, measure identity responses and optimize the raw
files themselves: restrict Tailwind source detection, remove unused framework
layers/plugins, minify JS, deduplicate inlined SVGs, and resize raster assets.
**Verify.** The `bench.mjs` bytes column, compared against the theme's own
budget table.
@ -85,5 +83,5 @@ after the change.
|---|---|---|
| TTFB | `bench.mjs` | `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` |

View File

@ -20,8 +20,12 @@ export function parseCurlMetrics(line) {
};
}
export function assertOkStatus(sample) {
if (!Number.isInteger(sample.status) || sample.status < 200 || sample.status >= 400) {
export function assertOkStatus(sample, allowedStatuses = []) {
if (
!Number.isInteger(sample.status) ||
(!(sample.status >= 200 && sample.status < 400) &&
!allowedStatuses.includes(sample.status))
) {
throw new Error(`HTTP ${sample.status}`);
}
return sample;

View File

@ -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", () => {
const s = summarize([
{ ttfbMs: 50, totalMs: 200, bytes: 1000, status: 200 },

View File

@ -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
// with like (spec: error handling / edge cases).
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 (gzip negotiated)", path: "/luci-static/aurora/main.css", gzip: true },
{ label: "login.css (gzip negotiated)", path: "/luci-static/aurora/login.css", gzip: true },
{ label: "menu-aurora.js (gzip negotiated)", path: "/luci-static/resources/menu-aurora.js", gzip: true },
{ label: "login.css (identity)", path: "/luci-static/aurora/login.css" },
{ label: "menu-aurora.js (identity)", path: "/luci-static/resources/menu-aurora.js" },
{
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) {
@ -63,10 +71,12 @@ async function measureOnce(target) {
"-w", CURL_FORMAT,
"--max-time", "30",
];
if (target.gzip) args.push("-H", "Accept-Encoding: gzip");
args.push(`${HOST}${target.path}`);
const { stdout } = await execFileAsync("curl", args);
return assertOkStatus(parseCurlMetrics(stdout));
return assertOkStatus(
parseCurlMetrics(stdout),
target.allowedStatuses ?? [],
);
}
async function measureTarget(target) {

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/`:
- **`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`.
- **`login.css`** — the standalone login page (`sysauth.ut`). Self-contained: imports Tailwind and `_tokens.css` directly.
- **`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 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).

View File

@ -22,10 +22,8 @@
"lightningcss": "^1.32.0",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.7.0",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "^4.1.11",
"terser": "^5.43.1",
"tw-animate-css": "^1.4.0",
"vite": "^7.1.1"
},
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"

View File

@ -32,18 +32,12 @@ importers:
prettier-plugin-tailwindcss:
specifier: ^0.7.0
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:
specifier: ^4.1.11
version: 4.3.2
terser:
specifier: ^5.43.1
version: 5.46.0
tw-animate-css:
specifier: ^1.4.0
version: 1.4.0
vite:
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)
@ -466,9 +460,6 @@ packages:
'@types/node@24.10.9':
resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==}
'@types/prismjs@1.26.5':
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
@ -490,10 +481,6 @@ packages:
caniuse-lite@1.0.30001799:
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:
resolution: {integrity: sha512-8lyR2wHzuIykCpqHKgluGsqQi5iDm3/a2IgP2GBZrasn2sBRkE4NOGsglZxWLs/jZQoNkmA/KM/8NV16rLUdBg==}
@ -698,15 +685,6 @@ packages:
engines: {node: '>=14'}
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:
resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@ -723,12 +701,6 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
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:
resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==}
@ -745,9 +717,6 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
tw-animate-css@1.4.0:
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@ -1054,8 +1023,6 @@ snapshots:
dependencies:
undici-types: 7.16.0
'@types/prismjs@1.26.5': {}
acorn@8.15.0: {}
baseline-browser-mapping@2.10.37: {}
@ -1072,8 +1039,6 @@ snapshots:
caniuse-lite@1.0.30001799: {}
clsx@2.1.1: {}
colorjs.io@0.6.1: {}
commander@2.20.3: {}
@ -1202,14 +1167,6 @@ snapshots:
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:
dependencies:
'@types/estree': 1.0.8
@ -1250,13 +1207,6 @@ snapshots:
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: {}
tapable@2.3.3: {}
@ -1273,8 +1223,6 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
tw-animate-css@1.4.0: {}
undici-types@7.16.0: {}
update-browserslist-db@1.2.3(browserslist@4.28.2):

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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

View File

@ -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

View File

@ -1,9 +1,9 @@
*: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 {
@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 {
@apply text-text relative flex min-h-dvh flex-col text-sm leading-relaxed font-normal;

View File

@ -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')];
}

View File

@ -114,9 +114,12 @@ header {
/* First-level icon: mask + currentColor so it follows the theme.
--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 {
@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 {
@apply [mask:url('@assets/icons/router.svg')_center/contain_no-repeat];
@apply [mask:var(--icon-router)_center/contain_no-repeat];
}
& .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 {
@apply [mask:url('@assets/icons/package.svg')_center/contain_no-repeat];
@apply [mask:var(--icon-package)_center/contain_no-repeat];
}
& .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;
&[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-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"] {
@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"] {
@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"]) {
@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.
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
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. */
@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 {
@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;
}
/* Visibility flips at the end of the slide so collapsed controls leave the
tab order without clipping the animation. */
/* The grid column snaps closed to avoid a full-page layout 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 {
@apply invisible;
}

View File

@ -51,8 +51,14 @@
}
}
@keyframes aurora-fade-in {
from {
opacity: 0;
}
}
.fade-in {
@apply animate-in fade-in-0 fill-mode-backwards duration-[250ms];
@apply animate-[aurora-fade-in_250ms_ease_both];
}
.fade-out {
@ -61,7 +67,7 @@
.spinning {
@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 > & {
@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)];
}

View File

@ -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;
&[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"] {
@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];
}
}
}

View File

@ -28,7 +28,7 @@
@apply text-text-muted relative mt-1 pl-4 leading-none wrap-break-word;
&: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];
}
}
}

View File

@ -27,11 +27,11 @@ input[type="checkbox"] {
}
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"] {
@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 {

View File

@ -11,7 +11,7 @@
/* 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
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 {
@ -33,7 +33,7 @@
}
.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 {
@ -90,38 +90,38 @@
The only maintenance point: add new first-level sections here; anything
unmapped falls back to the default icon at the consumer. */
.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"] {
@apply [--menu-icon:url('@assets/icons/chart-bar.svg')];
@apply [--menu-icon:var(--icon-chart-bar)];
}
.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"] {
@apply [--menu-icon:url('@assets/icons/settings.svg')];
@apply [--menu-icon:var(--icon-settings)];
}
.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"] {
@apply [--menu-icon:url('@assets/icons/server.svg')];
@apply [--menu-icon:var(--icon-server)];
}
.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"] {
@apply [--menu-icon:url('@assets/icons/world.svg')];
@apply [--menu-icon:var(--icon-world)];
}
.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"] {
@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
official OpenWrt package registers a verified menu.d node under this
name, so this is a best-effort key. */
.desktop-nav-title[data-section="asterisk"] {
@apply [--menu-icon:url('@assets/icons/phone.svg')];
@apply [--menu-icon:var(--icon-phone)];
}

View File

@ -1,5 +1,7 @@
.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
instantly. Link taps close into a full-page navigation, and any close

View File

@ -30,7 +30,7 @@
search icon carries the same stroke weight as the poll/changes ones
sitting next to it. */
&::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 {
@ -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;
&::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 {
@ -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;
&::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 {

View File

@ -1,6 +1,6 @@
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 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] {
@apply cursor-not-allowed opacity-(--app-opacity-disabled);
}

View File

@ -4,18 +4,48 @@
* 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 "./_reduced-motion.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] *));
/* 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 {
from {
opacity: 0;
@ -40,7 +70,7 @@
/* Full image fades in on top once loaded (compositor-only opacity change) */
.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 {
@ -102,7 +132,7 @@
/* 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. */
.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 {

View File

@ -3,24 +3,15 @@
* Licensed under the Apache License, Version 2.0.
*/
@import "tailwindcss";
/* Dev-only tooling holds prose and plain CSS whose bare words (static,
transition, relative, ) Tailwind's automatic source detection would
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";
/* Every shipped utility is referenced through @apply in these CSS sources.
Disabling automatic repository scanning prevents class-like words in docs,
scripts and fixtures from leaking into the OpenWrt payload. */
@import "tailwindcss" source(none);
@custom-variant dark (&:where([data-darkmode=true], [data-darkmode=true] *));
@plugin "tailwind-scrollbar" {
nocompatible: true;
}
@import "tw-animate-css";
@import "./_tokens.css";
@import "./_icons.css";
@import "./_base.css";
@import "./_elements.css";

View File

@ -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, []);
});

View File

@ -108,7 +108,7 @@ test("shared navigation styles own accordion animation without a guide rail", ()
"after:transition-[transform,opacity]",
"after:duration-[250ms]",
]);
assert.match(toggle, /arrow-right\.svg/);
assert.match(toggle, /var\(--icon-arrow-right\)/);
assertIncludesUtilities(region, [
"grid",
"grid-rows-[0fr]",
@ -218,7 +218,11 @@ test("mega-menu category masks use Tailwind arbitrary utilities", () => {
assert.match(
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);
});

View File

@ -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*\?\?/);
});

View File

@ -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
// fades it with opacity/visibility. Close must start that fade immediately;
// 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
.split("\n")
.find((l) => l.includes("bg-mega-menu-scrim"));

View File

@ -7,9 +7,9 @@ import tailwindcss from "@tailwindcss/vite";
import browserslist from "browserslist";
import { exec } from "child_process";
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 { browserslistToTargets } from "lightningcss";
import { browserslistToTargets, transform as lightningcssTransform } from "lightningcss";
import { basename, dirname, join, relative, resolve, sep } from "path";
import { minify as terserMinify } from "terser";
import { promisify } from "util";
@ -27,6 +27,10 @@ const CURRENT_DIR = process.cwd();
const PROJECT_ROOT = resolve(CURRENT_DIR, "..");
const BUILD_OUTPUT = resolve(PROJECT_ROOT, "htdocs/luci-static");
const LIGHTNINGCSS_TARGETS = browserslistToTargets(
browserslist("last 4 versions, Firefox ESR, not dead"),
);
function createLuciJsCompressPlugin(): Plugin {
let outDir: string;
@ -48,8 +52,10 @@ function createLuciJsCompressPlugin(): Plugin {
const sourceCode = await readFile(join(srcDir, relPath), "utf-8");
const compressed = await terserMinify(sourceCode, {
parse: { bare_returns: true },
compress: false,
mangle: false,
/* LuCI dependency declarations are string directives. Keep
them while enabling normal compression and local mangling. */
compress: { directives: false, passes: 2 },
mangle: true,
format: { comments: false, beautify: false },
});
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
// /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
@ -789,12 +883,12 @@ export default defineConfig(({ mode }) => {
createMockPlugin(),
createUtSyncPlugin(OPENWRT_SSH_HOST),
createLuciJsCompressPlugin(),
createLoginCssPrunePlugin(),
createPackageHygienePlugin(),
],
css: {
lightningcss: {
targets: browserslistToTargets(
browserslist("last 4 versions, Firefox ESR, not dead"),
),
targets: LIGHTNINGCSS_TARGETS,
},
},
build: {

View File

@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
LUCI_DEPENDS:=+luci-base
PKG_VERSION:=1.1.2
PKG_RELEASE:=55
PKG_VERSION:=1.1.3
PKG_RELEASE:=56
PKG_LICENSE:=Apache-2.0
LUCI_MINIFY_CSS:=

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,10 +14,10 @@
import { cursor } from 'uci';
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 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 = join('-', page_segs);

View File

@ -14,7 +14,11 @@ const themeTokens = cursor().get_all('aurora', 'theme') || {};
const loginLogoSvg = themeTokens.logo_svg || 'logo.svg';
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): %}
<div class="login-bg" aria-hidden="true"></div>