🏅 Sync 2026-04-19 00:24:08

This commit is contained in:
github-actions[bot] committed 2026-04-19 00:24:08 +08:00
1 parent e08c81d7fc
commit 746d64b361
30 files changed
+1982 -1250

No files matched your search

View File
Whitespace-only changes.
-49
View File
@@ -1,49 +0,0 @@
# 更新日志
## [7.0] - 2025-12-05
### 新增
- ✨ 支持 OpenWrt 23.x 版本
- ✨ 支持 OpenWrt 24.x 版本
- ✨ 新增版本兼容层 (`compat.js`)
### 改进
- 🔧 优化 LuCI API 调用,兼容新旧版本
- 🔧 jQuery 依赖改为可选,无 jQuery 环境下自动降级
- 🔧 改进 ubus 调用方式,支持不同版本
- 🔧 优化菜单渲染逻辑,增加空值检查
- 🔧 改进资源加载,添加版本化 URL
### 修复
- 🐛 修复新版本 LuCI 中 `dispatchpath` 未定义问题
- 🐛 修复新版本中 `requestpath` 访问错误
- 🐛 修复无 jQuery 环境下动画失效问题
- 🐛 修复某些元素可能不存在导致的错误
### 技术改进
- 📦 更新 Makefile 版本号到 7.0
- 📦 改进错误处理和边界检查
- 📦 优化代码结构,提高可维护性
## [6.0] - 2023-02-24
### 之前的更新
- 修复安装 package 提示信息背景泛白
- 优化菜单缩放
- 优化显示网口 down 状态显示图标
- 优化 logo 显示
- 新增各设备状态图标显示
- 更换 logo 显示为字体 "OpenWrt",支持以主机名显示 logo
- 修复部分插件显示 bug
- 修复 vssr 状态 bar
- 修复诸多 bug
- 修复兼容部分插件样式
- 修复 aliyundrive-webdav 样式
- 修复 vssr 在 iOS/iPadOS WebApp 模式下显示异常
- 修复 openclash 插件在 iOS/iPadOS WebApp 模式下 env(safe-area-inset-bottom) = 0
- 优化菜单 hover action 状态分辨
- 支持 luci-app-wizard 向导菜单
- Update header box-shadow style
- Update uci-change overflow
- Fix nlbw component
- Added QSDK/QWRT wizard and iStore menu icon fonts
-67
View File
@@ -1,67 +0,0 @@
# 版本兼容性说明
## 支持的 OpenWrt 版本
本主题已针对以下 OpenWrt 版本进行优化和测试:
- ✅ **OpenWrt 21.x** - 完全支持
- ✅ **OpenWrt 23.x** - 完全支持
- ✅ **OpenWrt 24.x** - 完全支持
## 主要兼容性改进
### 1. LuCI API 兼容
- 兼容新旧版本的 `L.env.dispatchpath` 和 `L.env.requestpath`
- 支持不同版本的 ubus 调用方式
- 自动检测并适配 LuCI 版本
### 2. JavaScript 依赖
- jQuery 可选:在没有 jQuery 的环境下使用原生 JavaScript
- 动画效果降级:无 jQuery 时使用 CSS 过渡
- 兼容层自动检测运行环境
### 3. 资源加载
- 版本化资源 URL,避免缓存问题
- 兼容不同版本的资源路径结构
## 已知问题
### OpenWrt 23/24 特定问题
1. 某些第三方插件可能需要单独适配
2. 如果遇到菜单显示问题,请清除浏览器缓存
## 测试建议
编译安装后,请测试以下功能:
- [ ] 登录界面显示正常
- [ ] 主菜单展开/收起
- [ ] 移动端响应式布局
- [ ] 深色模式切换
- [ ] 各插件页面样式
## 反馈问题
如果在特定版本遇到问题,请提供:
1. OpenWrt 版本号
2. LuCI 版本号
3. 浏览器类型和版本
4. 具体问题截图
## 技术细节
### 兼容层实现
主题包含 `compat.js` 兼容层,自动检测 LuCI 版本并提供统一接口。
### 版本检测
```javascript
// 检测是否为新版本 LuCI (23+)
if (L.env.luci_version) {
var majorVersion = parseInt(L.env.luci_version.split('.')[0]);
var isNewVersion = majorVersion >= 23;
}
```
### jQuery 降级
当 jQuery 不可用时,自动使用原生 DOM API:
- `$(el).slideUp()` → `el.style.display = 'none'`
- `$(el).css()` → `el.style.property = value`
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+3 -2
View File
@@ -7,8 +7,9 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=Design Theme LUCI_TITLE:=Design Theme
LUCI_DEPENDS:= LUCI_DEPENDS:=
PKG_VERSION:=7.0 PKG_LICENSE:=Apache-2.0
PKG_RELEASE:=2 PKG_VERSION:=7.1
PKG_RELEASE:=3
LUCI_MINIFY_CSS:= LUCI_MINIFY_CSS:=
CONFIG_LUCI_CSSTIDY:= CONFIG_LUCI_CSSTIDY:=
-140
View File
@@ -1,140 +0,0 @@
<img src="./preview/light.png"/>
# luci-theme-design
**luci-theme-design**基于[luci-theme-neobird](https://github.com/thinktip/luci-theme-neobird)二次开发
## 版本支持
- ✅ OpenWrt 21.x
- ✅ OpenWrt 23.x
- ✅ OpenWrt 24.x
- 适用于官方 [OpenWrt](https://github.com/openwrt/openwrt)
- 修复安装package提示信息背景泛白
- 优化菜单缩放
- 优化显示网口down状态显示图标
- 优化logo显示
- 新增各设备状态图标显示
- 更换logo显示为字体"OpenWrt",支持以主机名显示logo
- 修复部分插件显示bug
- 修复vssr状态bar
- 修复诸多bug
- 修复兼容部分插件样式
- 修复aliyundrive-webdav样式
- 修复vssr在iOS/iPadOS WebApp模式下显示异常
- 修复openclash插件在iOS/iPadOS WebApp 模式下env(safe-area-inset-bottom) = 0
- 优化菜单hover action状态分辨
- 支持luci-app-wizard向导菜单
- Update header box-shadow style
- Update uci-change overflow
- Fix nlbw component
- Added QSDK/QWRT wizard and iStore menu icon fonts
- 新增 OpenWrt 23/24 版本兼容支持
- 优化 LuCI 新旧版本兼容层
- 修复新版本 API 调用问题
## 主要特点
- 针对移动端优化,特别适合手机端做为webapp使用
- 修改和优化了很多插件显示,完善的icon图标,尽量视觉统一
- 简洁的登录界面,底部导航栏,类App的沉浸式体验;
- 适配深色模式,适配系统自动切换;
## 体验Webapp方法
- 在移动端(iOS/iPadOS)浏览器打开管理界面,添加到主屏幕即可。
- 想要实现完全的沉浸式(无浏览器导航、无地址栏等)体验,需要使用SSL证书,请自行申请域名、证书、安装并启用。
- 如果不使用SSL证书,基于安全原因,iOS/iPadOS 在打开新的页面后,将会显示浏览器顶部菜单栏。
## PS
- 资源接口icon未完善,如果有能力画图的欢迎pr,但请确保跟现有icon颜色风格一致
- 有bug欢迎提issue
- 主题个人配色可能会不符合大众胃口,欢迎提配色建议
## 自行编译
### OpenWrt 21/23/24 通用方法
```bash
# 进入 OpenWrt 源码目录
cd openwrt
# 克隆主题到 package 目录
git clone https://github.com/gngpp/luci-theme-design.git package/luci-theme-design
# 配置编译选项
make menuconfig
# 选择: LuCI -> 3. Themes -> luci-theme-design
# 编译
make package/luci-theme-design/compile V=s
```
### 安装后测试
```bash
# 上传测试脚本到路由器
scp test-compatibility.sh root@192.168.1.1:/tmp/
# SSH 登录路由器
ssh root@192.168.1.1
# 运行兼容性测试
sh /tmp/test-compatibility.sh
```
### 手动激活主题
如果主题未自动激活,可以手动设置:
```bash
uci set luci.main.mediaurlbase='/luci-static/design'
uci commit luci
/etc/init.d/uhttpd restart
```
## 版本兼容性
详细的兼容性信息请查看 [COMPATIBILITY.md](COMPATIBILITY.md)
- OpenWrt 21.x - 完全支持 ✅
- OpenWrt 23.x - 完全支持 ✅
- OpenWrt 24.x - 完全支持 ✅
## 故障排除
### 主题显示异常
1. 清除浏览器缓存(Ctrl+F5 强制刷新)
2. 检查主题文件是否完整安装
3. 运行兼容性测试脚本
### 菜单无法展开
1. 检查浏览器控制台是否有 JavaScript 错误
2. 确认 LuCI 版本兼容性
3. 尝试禁用浏览器扩展
### 移动端显示问题
1. 确保使用 HTTPS(WebApp 模式需要)
2. 清除浏览器缓存
3. 重新添加到主屏幕
## 预览(ps: 下面PC端普通字体为苹果浏览器字体,只有移动端是正常显示的)
<details> <summary>iOS</summary>
<img src="./preview/webapp_home.PNG"/>
<img src="./preview/webapp_vssr.PNG"/>
</details>
<details> <summary>iPadOS</summary>
<img src="./preview/IMG_0328.PNG"/>
<img src="./preview/IMG_0329.PNG"/>
</details>
<img src="./preview/login.png"/>
<img src="./preview/home.png"/>
<img src="./preview/home1.png"/>
<img src="./preview/wifi.png"/>
<img src="./preview/iface.png"/>
@@ -1,431 +0,0 @@
/**
* OpenWrt 23/24 兼容性样�补�
*
* 这个文件包�针对 OpenWrt 23.x �24.x 的样�修�
* 新版 LuCI 使用了一些新�CSS 类�和结�
*/
/* ============================================
1. 新版 LuCI 视图容器兼容
============================================ */
/* 新版 LuCI 使用 #view 作为主�内容容器 */
#view {
padding: 0;
}
#view > * {
margin-bottom: 1rem;
}
/* 兼容新版 LuCI �.cbi-map 结构 */
.cbi-map {
margin-top: 10px;
}
/* ============================================
2. 新版表�元素样�
============================================ */
/* 新版 LuCI �input 样�兼容 */
.cbi-input-text,
.cbi-input-password,
.cbi-input-select {
color: var(--inputtext_color);
padding: 5px 10px;
font-size: 0.92rem;
border: var(--input_border);
background-color: var(--inputbg_color);
border-radius: 8px;
height: 2.8rem;
line-height: 2.8rem;
max-width: 550px;
}
/* 新版 checkbox �radio 样� */
.cbi-input-checkbox,
.cbi-input-radio {
width: 1.2rem;
height: 1.2rem;
margin-right: 0.5rem;
vertical-align: middle;
accent-color: var(--active_color);
}
/* ============================================
3. 新版按钮样�兼容
============================================ */
/* 新版 LuCI 按钮类� */
.btn.cbi-button,
button.cbi-button {
-webkit-appearance: none;
appearance: none;
text-transform: uppercase;
color: #fff;
background-color: #337ab7;
transition: all 0.2s ease-in-out;
display: inline-block;
border: none;
cursor: pointer;
text-align: center;
white-space: nowrap;
min-width: 80px;
padding: 0 8px;
height: 35px;
line-height: 35px;
border-radius: 8px;
}
/* 新版 primary/positive 按钮 */
.cbi-button-positive,
.cbi-button-action {
color: var(--bttext_color) !important;
background-color: var(--active_color) !important;
}
.cbi-button-positive:hover,
.cbi-button-action:hover {
background-color: #4a8a80 !important;
}
/* 新版 negative 按钮 */
.cbi-button-negative {
color: #fff !important;
background-color: #d9534f !important;
}
.cbi-button-negative:hover {
background-color: #c9302c !important;
}
/* 新版 neutral 按钮 */
.cbi-button-neutral {
color: #fff !important;
background-color: #617486 !important;
}
/* ============================================
4. 新版表格样�兼容
============================================ */
/* 新版 LuCI 表格结构 */
.table.cbi-section-table {
width: 100%;
border-collapse: collapse;
}
.table.cbi-section-table .tr {
display: table-row;
}
.table.cbi-section-table .th,
.table.cbi-section-table .td {
display: table-cell;
padding: 12px;
vertical-align: middle;
}
/* 新版表格标题�*/
.tr.cbi-section-table-titles {
background-color: var(--inputbg_color);
font-weight: bold;
}
/* ============================================
5. 新版下拉��兼容
============================================ */
/* 新版 cbi-dropdown 样� */
.cbi-dropdown {
position: relative;
display: inline-flex;
min-height: 2.8rem;
border: var(--input_border);
background-color: var(--inputbg_color);
border-radius: 8px;
}
.cbi-dropdown > ul {
display: flex;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
margin: 0 !important;
padding: 0;
list-style: none;
}
.cbi-dropdown > ul > li {
display: flex;
align-items: center;
padding: 0.5em;
}
.cbi-dropdown[open] > ul.dropdown {
position: absolute;
z-index: 1100;
top: 100%;
left: 0;
right: 0;
background: var(--sectionbg_color);
border: var(--input_border);
border-radius: 0 0 8px 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.cbi-dropdown[open] > ul.dropdown > li {
padding: 0.5em 1em;
cursor: pointer;
}
.cbi-dropdown[open] > ul.dropdown > li:hover {
background-color: var(--active_color);
color: #fff;
}
/* ============================================
6. 新版标签页样�兼�
============================================ */
/* 新版 LuCI 标签�*/
.cbi-tabmenu {
display: flex;
flex-wrap: wrap;
border-bottom: var(--tabmenu_bottom);
margin-bottom: 1rem;
}
.cbi-tabmenu > li {
display: inline-block;
margin-right: 0.5rem;
}
.cbi-tabmenu > li > a {
display: block;
padding: 0.5rem 1rem;
text-decoration: none;
color: var(--text_color);
border-bottom: 2px solid transparent;
transition: all 0.2s;
}
.cbi-tabmenu > li.cbi-tab > a,
.cbi-tabmenu > li > a:hover {
color: var(--active_color);
border-bottom-color: var(--active_color);
}
/* ============================================
7. 新版模�框样�兼容
============================================ */
/* 新版 modal 样� */
.modal {
display: flex;
flex-wrap: wrap;
align-items: center;
width: 90%;
max-width: 600px;
min-height: 32px;
margin: 5em auto;
padding: 1em;
border-radius: 8px;
background: var(--model_background);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.modal > * {
flex-basis: 100%;
margin-bottom: 0.5em;
}
.modal > h4 {
color: var(--text_color);
margin-bottom: 1em;
}
.modal > .btn,
.modal > .cbi-button {
flex-basis: auto;
margin: 0.25em;
}
/* ============================================
8. 新版进度�样�兼�
============================================ */
/* 新版进度�*/
.cbi-progressbar {
position: relative;
min-width: 11rem;
height: 1.5rem;
background-color: var(--progressbar_color);
border-radius: 5px;
overflow: hidden;
}
.cbi-progressbar > div {
height: 100%;
background: var(--progressbar);
transition: width 0.25s ease-in;
}
.cbi-progressbar::after {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: attr(title);
text-align: center;
line-height: 1.5rem;
font-size: 0.75rem;
color: var(--progressbartxt_color);
}
/* ============================================
9. 新版状�指示器兼容
============================================ */
/* 新版状�徽�*/
.ifacebadge {
display: inline-flex;
align-items: center;
gap: 0.2rem;
padding: 0.3rem 0.6rem;
background-color: var(--active_color);
color: #fff;
border-radius: 8px;
font-size: 0.85rem;
}
.ifacebadge > img,
.ifacebadge > em {
width: 16px;
height: 16px;
}
/* 区域徽章 */
.zonebadge {
display: inline-block;
padding: 0.2rem 0.5rem;
border-radius: 4px;
}
/* ============================================
10. 新版�应�布局兼容
============================================ */
@media screen and (max-width: 992px) {
/* 新版移动端��*/
.main-left {
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.main-left.active,
.main-left[style*="width: 17rem"],
.main-left:not([style*="width: 0"]) {
transform: translateX(0);
}
}
/* ============================================
11. 新版 LuCI 特定页�修�
============================================ */
/* 系统概览页� */
[data-page="admin-status-overview"] .cbi-section {
margin-bottom: 1rem;
}
/* 网络接�页� */
[data-page="admin-network-network"] .ifacebox {
margin: 0.5rem;
border-radius: 8px;
overflow: hidden;
}
/* 无线页� */
[data-page="admin-network-wireless"] .cbi-section-table {
overflow-x: auto;
}
/* 软件包页�*/
[data-page="admin-system-opkg"] .controls {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
[data-page="admin-system-opkg"] .controls input[type="text"] {
flex: 1;
min-width: 200px;
}
/* ============================================
12. 深色模�增强
============================================ */
@media (prefers-color-scheme: dark) {
/* 新版深色模�下的代��*/
code, pre {
background-color: #2d2d2d;
color: #f8f8f2;
}
/* 新版深色模�下的表格 */
.table.cbi-section-table .tr:nth-child(even) {
background-color: rgba(255, 255, 255, 0.05);
}
/* 新版深色模�下的输入�*/
.cbi-input-text:focus,
.cbi-input-password:focus,
.cbi-input-select:focus {
border-color: var(--active_color);
box-shadow: 0 0 6px var(--active_color);
}
}
/* ============================================
13. 打�样�
============================================ */
@media print {
.main-left,
.navbar,
header,
.showSide,
.darkMask {
display: none !important;
}
.main-right {
width: 100% !important;
margin: 0 !important;
}
}
/* ============================================
14. 无障�访问增�
============================================ */
/* 焦点���*/
:focus-visible {
outline: 2px solid var(--active_color);
outline-offset: 2px;
}
/* �少动画 (用户�好) */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
File diff suppressed because it is too large. Load diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -1,28 +1,404 @@
(function ($) { (function ($) {
function applySafeAreaFix() {
var url = self.location.href;
// 修复某些插件导致在https下env(safe-area-inset-bottom)为0的情况 // 修复某些插件导致在 https 下 env(safe-area-inset-bottom) 为 0 的情况
var url = self.location.href; if ((/(iPhone|iPad|iPod|iOS|Mac|Macintosh)/i.test(navigator.userAgent)) && url.indexOf("openclash") !== -1) {
if ((/(iPhone|iPad|iPod|iOS|Mac|Macintosh)/i.test(navigator.userAgent)) && url.indexOf("openclash") != -1 ) { var oMeta = document.createElement('meta');
var oMeta = document.createElement('meta'); oMeta.content = 'width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0,viewport-fit=cover';
oMeta.content = 'width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0,viewport-fit=cover'; oMeta.name = 'viewport';
oMeta.name = 'viewport'; document.getElementsByTagName('head')[0].appendChild(oMeta);
document.getElementsByTagName('head')[0].appendChild(oMeta); }
} }
// 设置indicators图标,(放在menu script执行之前passwall某些代码错误中断导致失败) function setHeaderShadow() {
document.getElementById("indicators").addEventListener('DOMSubtreeModified', function () { if (window.innerWidth <= 992) {
var child = document.getElementById("indicators"); $("header").css("box-shadow", "0 2px 4px rgb(0 0 0 / 8%)");
if (child.firstElementChild.getAttribute("data-indicator") != "uci-changes") { } else {
child.firstElementChild.textContent = eval("'\ue6b9'") $("header").css("box-shadow", "17rem 2px 4px rgb(0 0 0 / 8%)");
} }
}, false); }
// 监听窗口大小,动态设置header box阴影长度 function syncIndicators() {
$(window).resize( function () { var indicatorRoot = document.getElementById("indicators");
if (window.innerWidth <= 992) {
$("header").css("box-shadow", "0 2px 4px rgb(0 0 0 / 8%)") if (!indicatorRoot) {
} else { return;
$("header").css("box-shadow", "17rem 2px 4px rgb(0 0 0 / 8%)") }
}
Array.prototype.forEach.call(indicatorRoot.querySelectorAll('[data-indicator]'), function (indicator) {
var id = indicator.getAttribute('data-indicator');
var label = (indicator.textContent || '').trim();
if (!label) {
label = indicator.getAttribute('aria-label') || indicator.getAttribute('title') || '';
}
if (id === 'poll-status') {
indicator.setAttribute('title', label);
indicator.setAttribute('aria-label', label);
} else if (id === 'uci-changes') {
var match = label.match(/(\d+)\s*$/);
indicator.setAttribute('title', label);
indicator.setAttribute('aria-label', label);
indicator.setAttribute('data-count', match ? match[1] : '');
}
}); });
})(jQuery); }
function initIndicators() {
var indicatorRoot = document.getElementById("indicators");
if (!indicatorRoot) {
return;
}
syncIndicators();
new MutationObserver(syncIndicators).observe(indicatorRoot, {
childList: true,
subtree: true,
characterData: true,
attributes: true,
attributeFilter: ['data-style']
});
}
function syncOverviewPortStatus() {
var view = document.getElementById('view');
var portGroups = new Map();
var mediaPath = (L.env && L.env.media) ? L.env.media : '/luci-static/design';
var iconMap = {
'alias': 'ethernet',
'alias_disabled': 'ethernet_disabled',
'bridge': 'bridge',
'bridge_disabled': 'bridge_disabled',
'ethernet': 'ethernet',
'ethernet_disabled': 'ethernet_disabled',
'switch': 'switch',
'switch_disabled': 'switch_disabled',
'tunnel': 'tunnel',
'tunnel_disabled': 'tunnel_disabled',
'vlan': 'vlan',
'vlan_disabled': 'vlan_disabled',
'wifi': 'wifi',
'wifi_disabled': 'wifi_disabled',
'signal-none': 'signal-none',
'signal-0': 'signal-0',
'signal-0-25': 'signal-0-25',
'signal-25-50': 'signal-25-50',
'signal-50-75': 'signal-50-75',
'signal-75-100': 'signal-75-100',
'signal-000-000': 'signal-0',
'signal-000-025': 'signal-0-25',
'signal-025-050': 'signal-25-50',
'signal-050-075': 'signal-50-75',
'signal-075-100': 'signal-75-100',
'port_up': 'port_up',
'port_down': 'port_down'
};
if (!view || document.body.getAttribute('data-page') !== 'admin-status-overview') {
return;
}
Array.prototype.forEach.call(view.querySelectorAll('img[src*="/luci-static/resources/icons/"]'), function (img) {
var src = img.getAttribute('src') || '';
var match = src.match(/\/icons\/([^/?#]+)\.(?:png|svg)(?:[?#].*)?$/);
var key = match ? match[1] : '';
var mapped = iconMap[key];
var badge = img.closest('.ifacebadge');
if (!mapped || !badge) {
return;
}
var size = key.indexOf('port_') === 0 ? '18px' :
key.indexOf('signal-') === 0 ? '16px' : '20px';
img.setAttribute('src', mediaPath + '/images/' + mapped + '.png');
img.style.setProperty('background-image', 'none', 'important');
img.style.setProperty('visibility', 'visible', 'important');
img.style.setProperty('opacity', '1', 'important');
img.style.setProperty('display', 'inline-block', 'important');
img.style.setProperty('width', size, 'important');
img.style.setProperty('height', size, 'important');
img.style.setProperty('min-width', size, 'important');
img.style.setProperty('padding', '0', 'important');
img.style.setProperty('margin', '0 .25rem 0 0', 'important');
img.style.setProperty('vertical-align', 'middle', 'important');
img.style.setProperty('object-fit', 'contain', 'important');
});
Array.prototype.forEach.call(view.querySelectorAll('.cbi-section > div'), function (grid) {
var isPortGrid = grid &&
grid.style &&
grid.style.display === 'grid' &&
grid.style.gridTemplateColumns.indexOf('minmax(70px, 1fr)') !== -1 &&
grid.querySelector('img[src*="/luci-static/resources/icons/port_"]');
if (!isPortGrid) {
return;
}
grid.classList.add('port-status-grid');
Array.prototype.forEach.call(grid.querySelectorAll('.ifacebox'), function (card) {
card.classList.add('port-status-card');
});
});
Array.prototype.forEach.call(view.querySelectorAll('img[src*="/luci-static/resources/icons/port_"]'), function (img) {
var card = img.closest('.ifacebox');
var slot = card ? card.closest('.port-status-slot') : null;
var parent = card ? (slot ? slot.parentElement : card.parentElement) : null;
var iconType = (img.getAttribute('src') || '').indexOf('port_down.png') !== -1 ? 'down' : 'up';
var badge = img.closest('.ifacebadge');
if (badge) {
img.setAttribute('src', mediaPath + '/images/port_' + iconType + '.png');
}
if (!card || !parent) {
return;
}
img.setAttribute('src', mediaPath + '/images/port_' + iconType + '.png');
img.style.setProperty('display', 'block', 'important');
img.style.setProperty('width', '24px', 'important');
img.style.setProperty('height', '24px', 'important');
img.style.setProperty('padding', '0', 'important');
img.style.setProperty('margin', '0.05rem auto', 'important');
if (img.nextElementSibling && img.nextElementSibling.tagName === 'BR') {
img.nextElementSibling.style.setProperty('display', 'none', 'important');
}
if (!portGroups.has(parent)) {
portGroups.set(parent, []);
}
if (portGroups.get(parent).indexOf(card) === -1) {
portGroups.get(parent).push(card);
}
});
portGroups.forEach(function (cards, parent) {
var width = window.innerWidth || document.documentElement.clientWidth || 1280;
var columns;
cards.forEach(function (card) {
var body = card.querySelector('.ifacebox-body');
var slot = card.parentElement;
if (!slot || !slot.classList.contains('port-status-slot')) {
slot = document.createElement('div');
slot.className = 'port-status-slot';
parent.insertBefore(slot, card);
slot.appendChild(card);
}
card.classList.add('port-status-card');
slot.style.setProperty('display', 'flex', 'important');
slot.style.setProperty('justify-content', 'center', 'important');
slot.style.setProperty('align-items', 'stretch', 'important');
slot.style.setProperty('min-width', '0', 'important');
slot.style.setProperty('padding', '0.15rem 0.65rem 0.75rem', 'important');
card.style.setProperty('margin', '0', 'important');
card.style.setProperty('min-width', '76px', 'important');
card.style.setProperty('max-width', '108px', 'important');
card.style.setProperty('width', 'fit-content', 'important');
card.style.setProperty('text-align', 'center', 'important');
if (body) {
body.style.setProperty('text-align', 'center', 'important');
body.style.setProperty('align-items', 'center', 'important');
body.style.setProperty('justify-content', 'center', 'important');
}
});
if (width <= 480) {
columns = 1;
} else if (width <= 768) {
columns = Math.min(cards.length, 2);
} else if (cards.length <= 4) {
columns = cards.length;
} else if (cards.length <= 6) {
columns = 3;
} else {
columns = 4;
}
parent.style.setProperty('display', 'grid', 'important');
parent.style.setProperty('grid-template-columns', 'repeat(' + columns + ', minmax(0, 1fr))', 'important');
parent.style.setProperty('align-items', 'stretch', 'important');
parent.style.setProperty('justify-items', 'stretch', 'important');
parent.style.setProperty('width', '100%', 'important');
parent.style.setProperty('max-width', '100%', 'important');
parent.style.setProperty('padding', '0.2rem 0.6rem 0.35rem', 'important');
parent.style.setProperty('margin', '0 auto 1rem', 'important');
parent.style.setProperty('row-gap', '0.2rem', 'important');
parent.style.setProperty('column-gap', '0', 'important');
});
}
function initOverviewPortStatus() {
var view = document.getElementById('view');
if (!view || document.body.getAttribute('data-page') !== 'admin-status-overview') {
return;
}
syncOverviewPortStatus();
new MutationObserver(syncOverviewPortStatus).observe(view, {
childList: true,
subtree: true
});
}
function syncNetworkInterfaceIcons() {
var view = document.getElementById('view');
var mediaPath = (L.env && L.env.media) ? L.env.media : '/luci-static/design';
var iconMap = {
'alias': 'ethernet',
'alias_disabled': 'ethernet_disabled',
'bridge': 'bridge',
'bridge_disabled': 'bridge_disabled',
'ethernet': 'ethernet',
'ethernet_disabled': 'ethernet_disabled',
'switch': 'switch',
'switch_disabled': 'switch_disabled',
'tunnel': 'tunnel',
'tunnel_disabled': 'tunnel_disabled',
'vlan': 'vlan',
'vlan_disabled': 'vlan_disabled',
'wifi': 'wifi',
'wifi_disabled': 'wifi_disabled'
};
if (!view || document.body.getAttribute('data-page') !== 'admin-network-network') {
return;
}
Array.prototype.forEach.call(view.querySelectorAll('img[src*="/luci-static/resources/icons/"]'), function (img) {
var src = img.getAttribute('src') || '';
var match = src.match(/\/icons\/([^/?#]+)\.(?:png|svg)(?:[?#].*)?$/);
var key = match ? match[1] : '';
var mapped = iconMap[key];
if (!mapped) {
return;
}
img.setAttribute('src', mediaPath + '/images/' + mapped + '.png');
img.classList.add('theme-network-icon');
img.style.setProperty('background-image', 'none', 'important');
img.style.setProperty('object-fit', 'contain', 'important');
});
}
function initNetworkInterfaceIcons() {
var view = document.getElementById('view');
if (!view || document.body.getAttribute('data-page') !== 'admin-network-network') {
return;
}
syncNetworkInterfaceIcons();
new MutationObserver(syncNetworkInterfaceIcons).observe(view, {
childList: true,
subtree: true
});
}
function syncWirelessIcons() {
var mediaPath = (L.env && L.env.media) ? L.env.media : '/luci-static/design';
var iconMap = {
'wifi': 'wifi_big',
'wifi_disabled': 'wifi_big_disabled',
'signal-none': 'signal-none',
'signal-0': 'signal-0',
'signal-0-25': 'signal-0-25',
'signal-25-50': 'signal-25-50',
'signal-50-75': 'signal-50-75',
'signal-75-100': 'signal-75-100',
'signal-000-000': 'signal-0',
'signal-000-025': 'signal-0-25',
'signal-025-050': 'signal-25-50',
'signal-050-075': 'signal-50-75',
'signal-075-100': 'signal-75-100'
};
if (document.body.getAttribute('data-page') !== 'admin-network-wireless') {
return;
}
Array.prototype.forEach.call(document.querySelectorAll('img[src*="/luci-static/resources/icons/"]'), function (img) {
var src = img.getAttribute('src') || '';
var match = src.match(/\/icons\/([^/?#]+)\.(?:png|svg)(?:[?#].*)?$/);
var key = match ? match[1] : '';
var inAssocList = !!img.closest('#wifi_assoclist_table');
var mapped = iconMap[key];
var badge = img.closest('.ifacebadge') || img.closest('.center');
var isDeviceIcon = (key === 'wifi' || key === 'wifi_disabled');
var size = isDeviceIcon ? '64px' : '18px';
if (!mapped || !badge) {
return;
}
if (inAssocList && isDeviceIcon) {
mapped = key;
size = '16px';
}
img.setAttribute('src', mediaPath + '/images/' + mapped + '.png');
img.classList.add('theme-wireless-icon');
img.style.setProperty('background-image', 'none', 'important');
img.style.setProperty('visibility', 'visible', 'important');
img.style.setProperty('opacity', '1', 'important');
img.style.setProperty('display', 'inline-block', 'important');
img.style.setProperty('width', size, 'important');
img.style.setProperty('height', size, 'important');
img.style.setProperty('min-width', size, 'important');
img.style.setProperty('padding', '0', 'important');
img.style.setProperty('margin', (inAssocList && isDeviceIcon) ? '0 .2rem 0 0' : (isDeviceIcon ? '0 .3rem 0 0' : '0 .25rem 0 0'), 'important');
img.style.setProperty('vertical-align', 'middle', 'important');
img.style.setProperty('object-fit', 'contain', 'important');
});
}
function initWirelessIcons() {
if (document.body.getAttribute('data-page') !== 'admin-network-wireless') {
return;
}
syncWirelessIcons();
new MutationObserver(syncWirelessIcons).observe(document.body, {
childList: true,
subtree: true
});
}
function init() {
applySafeAreaFix();
initIndicators();
initOverviewPortStatus();
initNetworkInterfaceIcons();
initWirelessIcons();
setHeaderShadow();
$(window).on('resize', setHeaderShadow);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})(jQuery);
@@ -1,17 +1,73 @@
'use strict'; 'use strict';
'require baseclass'; 'require baseclass';
'require request';
'require ui'; 'require ui';
// 兼容新旧版本 LuCI
var LuciCompat = {
isNewVersion: function() {
return L.env.luci_version && parseFloat(L.env.luci_version.split('.')[0]) >= 23;
}
};
return baseclass.extend({ return baseclass.extend({
__init__: function() { __init__: function() {
ui.menu.load().then(L.bind(this.render, this)); var params = new URLSearchParams(window.location.search);
if (params.get('menu') === 'flush') {
ui.menu.flushCache();
params.delete('menu');
window.history.replaceState(null, '', window.location.pathname +
(params.toString() ? '?' + params.toString() : '') +
window.location.hash);
}
this.syncConditionalMenuCache()
.then(L.bind(function(reloading) {
if (reloading)
return;
return ui.menu.load().then(L.bind(this.render, this));
}, this));
},
reloadPageWithMenuFlush: function() {
var url = new URL(window.location.href);
url.searchParams.set('menu', 'flush');
window.location.replace(url.toString());
},
getConditionalMenuStamp: function() {
var googleFuMode = !!document.querySelector('.navbar a[href*="/admin/services/openclash"]');
return Promise.resolve(JSON.stringify({
google_fu_mode: googleFuMode
}));
},
flushBackendMenuCache: function() {
ui.menu.flushCache();
return L.resolveDefault(request.get(L.url('admin/services/theme_menu_flush')), null).then(function() {
ui.menu.flushCache();
}).then(function() {
ui.menu.flushCache();
});
},
syncConditionalMenuCache: function() {
var storageKey = 'design.menu.condition-stamp';
if (!document.body.classList.contains('logged-in'))
return Promise.resolve();
return this.getConditionalMenuStamp().then(L.bind(function(stamp) {
var previousStamp = window.localStorage.getItem(storageKey);
if (previousStamp === stamp)
return false;
window.localStorage.setItem(storageKey, stamp);
return this.flushBackendMenuCache().then(L.bind(function() {
this.reloadPageWithMenuFlush();
return true;
}, this));
}, this));
}, },
render: function(tree) { render: function(tree) {
@@ -243,4 +299,3 @@ return baseclass.extend({
} }
}, },
}); });
@@ -1,32 +0,0 @@
<%#
Material is a clean HTML5 theme for LuCI. It is based on luci-theme-bootstrap and MUI
luci-theme-material
Copyright 2015 Lutty Yang <lutty@wcan.in>
Have a bug? Please create an issue here on GitHub!
https://github.com/LuttyYang/luci-theme-material/issues
luci-theme-bootstrap:
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
Copyright 2012 David Menting <david@nut-bolt.nl>
MUI:
https://github.com/muicss/mui
Licensed to the public under the Apache License 2.0
-%>
<% local ver = require "luci.version" %>
</div>
<footer class="mobile-hide">
<a href="https://github.com/openwrt/luci">Powered by <%= ver.luciname %> (<%= ver.luciversion %>)</a> / <%= ver.distversion %>
</footer>
</div>
</div>
<script type="text/javascript">L.require('menu-design')</script>
</body>
</html>
@@ -1,130 +0,0 @@
<%#
Material is a clean HTML5 theme for LuCI. It is based on luci-theme-bootstrap and MUI
luci-theme-material
Copyright 2015-2017 Lutty Yang <lutty@wcan.in>
Have a bug? Please create an issue here on GitHub!
https://github.com/LuttyYang/luci-theme-material/issues
luci-theme-bootstrap:
Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org>
Copyright 2012 David Menting <david@nut-bolt.nl>
MUI:
https://github.com/muicss/mui
Licensed to the public under the Apache License 2.0
-%>
<%
local sys = require "luci.sys"
local util = require "luci.util"
local http = require "luci.http"
local disp = require "luci.dispatcher"
local ver = require "luci.version"
-- 兼容新旧版本的 ubus 调用
local boardinfo = {}
if util.ubus then
boardinfo = util.ubus("system", "board") or {}
else
-- OpenWrt 23+ 可能使用不同的方式
local ubus = require "ubus"
local conn = ubus.connect()
if conn then
boardinfo = conn:call("system", "board", {}) or {}
conn:close()
end
end
local node = disp.context.dispatched
local path = table.concat(disp.context.path, "-")
http.prepare_content("text/html; charset=UTF-8")
-%>
<!DOCTYPE html>
<html lang="<%=luci.i18n.context.lang%>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,viewport-fit=cover" />
<link rel="manifest" href="<%=media%>/manifest.json">
<title><%=striptags( (boardinfo.hostname or "OpenWrt") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
<meta name="format-detection" content="telephone=no, email=no"/>
<meta name="x5-fullscreen" content="true">
<meta name="full-screen" content="yes">
<meta name="x5-page-mode" content="app">
<meta name="browsermode" content="application">
<meta name="msapplication-tap-highlight" content="no">
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black">
<meta name="application-name" content="<%=striptags( (boardinfo.hostname or "OpenWrt") ) %> - LuCI">
<meta name="apple-mobile-web-app-title" content="<%=striptags( (boardinfo.hostname or "OpenWrt") ) %> - LuCI">
<meta name="msapplication-TileImage" content="<%=media%>/images/icon.png"/>
<link rel="icon" href="<%=media%>/images/icon.png" sizes="192x192">
<link rel="apple-touch-icon" sizes="192x192" href="<%=media%>/images/icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes" />
<meta name="msapplication-TileColor" content="#09c">
<link rel="stylesheet" href="<%=media%>/css/style.css?v=<%= ver.luciversion %>">
<link rel="stylesheet" href="<%=media%>/css/compat-openwrt23.css?v=<%= ver.luciversion %>">
<script src="<%=media%>/js/jquery.min.js?v=3.5.1"></script>
<script src="<%=media%>/js/compat.js?v=<%= ver.luciversion %>"></script>
<link rel="shortcut icon" href="<%=media%>/favicon.ico">
<% if node and node.css then %>
<link rel="stylesheet" href="<%=resource%>/<%=node.css%>">
<% end -%>
<script src="<%=url('admin/translations', luci.i18n.context.lang)%><%# ?v=PKG_VERSION %>"></script>
<script src="<%=resource%>/cbi.js<%= ver.luciversion and ('?v=' .. ver.luciversion) or '' %>"></script>
<script src="<%=resource%>/luci.js<%= ver.luciversion and ('?v=' .. ver.luciversion) or '' %>"></script>
<% if css then %>
<style title="text/css">
<%= css %>
</style>
<% end -%>
</head>
<body class="lang_<%=luci.i18n.context.lang%> <% if luci.dispatcher.context.authsession then %>logged-in<% end %> <% if not (path == "") then %>node-<%= path %><% else %>node-main-login<% end %>" data-page="<%= pcdata(path) %>">
<header>
<div class="fill">
<div class="container">
<span class="showSide"></span>
<a class="brand" href="#"><%=striptags(boardinfo.hostname or "OpenWrt")%></a>
<div class="status" id="indicators"></div>
</div>
</div>
</header>
<div class="navbar">
<a href="/cgi-bin/luci/admin/status/overview"><img src="<%=media%>/images/home.png" /></a>
<a href="/cgi-bin/luci/admin/services/openclash"><img src="<%=media%>/images/openclash.png" /></a>
<a href="/cgi-bin/luci/admin/network/network"><img src="<%=media%>/images/link.png" /></a>
<a href="/cgi-bin/luci/admin/status/realtime"><img src="<%=media%>/images/rank.png" /></a>
<a href="/cgi-bin/luci/admin/system/admin"><img src="<%=media%>/images/user.png" /></a>
</div>
<div class="main">
<div style="" class="loading"><span><div class="loading-img"></div><%:Collecting data...%></span></div>
<div class="main-left" id="mainmenu" style="display:none"></div>
<div class="main-right">
<div class="modemenu-buttons" style="display:none">
<ul id="modemenu"></ul>
</div>
<div class="darkMask"></div>
<div id="maincontent">
<div class="container">
<%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") and path ~= "admin-system-admin-password" then -%>
<%- end -%>
<noscript>
<div class="alert-message warning">
<h4><%:JavaScript required!%></h4>
<p><%:You must enable JavaScript in your browser or LuCI will not work properly.%></p>
</div>
</noscript>
<div id="tabmenu" style="display:none"></div>
<script src="<%=media%>/js/style.js?v=<%=ver.luciversion%>"></script>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

@@ -0,0 +1,10 @@
{
"admin/services/theme_menu_flush": {
"leaf": true,
"action": {
"type": "function",
"module": "luci.controller.design_theme",
"function": "menu_flush"
}
}
}
@@ -0,0 +1,15 @@
'use strict';
import { glob, unlink } from 'fs';
return {
menu_flush: function() {
for (let path in glob('/tmp/luci-indexcache', '/tmp/luci-indexcache.*'))
unlink(path);
system('rm -rf /tmp/luci-modulecache 2>/dev/null');
http.prepare_content('application/json');
http.write_json({ flushed: true });
}
};
@@ -0,0 +1,18 @@
{#
Design theme footer template for LuCI ucode runtime.
-#}
</div>
<footer class="mobile-hide">
<a href="https://github.com/openwrt/luci" target="_blank" rel="noreferrer">Powered by {{ version.luciname }} ({{ version.luciversion }})</a>
/
{{ version.distname }} {{ version.distversion }}{% if (version.distrevision): %} ({{ version.distrevision }}){% endif %}
</footer>
</div>
</div>
</div>
<script type="text/javascript">L.require('menu-design')</script>
</body>
</html>
@@ -0,0 +1,124 @@
{#
Design is a clean HTML5 theme for LuCI. It is based on luci-theme-material
and luci-theme-neobird.
Copyright 2015 Lutty Yang <lutty@wcan.in>
Copyright 2021 2smile <thinktip@gmail.com>
Licensed to the public under the Apache License 2.0.
-#}
{%
import { access } from 'fs';
import { getuid, getspnam } from 'luci.core';
const boardinfo = ubus.call('system', 'board');
const hostname = striptags(boardinfo?.hostname ?? 'OpenWrt');
const dispatch_path = ctx.path ?? [];
const request_path = ctx.request_path ?? dispatch_path;
const current_path = length(dispatch_path) ? join('-', dispatch_path) : 'main-login';
const current_page = length(request_path) ? join('-', request_path) : current_path;
const show_proxy_icon = access('/etc/config/google_fu_mode');
const theme_rev = `${version?.luciversion ?? 'dev'}-designfix-20260418i`;
http.prepare_content('text/html; charset=UTF-8');
-%}
<!DOCTYPE html>
<html lang="{{ dispatcher.lang }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover">
<link rel="manifest" href="{{ media }}/manifest.json">
<title>{{ striptags(`${hostname}${dispatched?.title ? ` - ${_(dispatched.title)}` : ''}`) }} - LuCI</title>
<meta name="format-detection" content="telephone=no, email=no">
<meta name="x5-fullscreen" content="true">
<meta name="full-screen" content="yes">
<meta name="x5-page-mode" content="app">
<meta name="browsermode" content="application">
<meta name="msapplication-tap-highlight" content="no">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black">
<meta name="application-name" content="{{ hostname }} - LuCI">
<meta name="apple-mobile-web-app-title" content="{{ hostname }} - LuCI">
<meta name="msapplication-TileImage" content="{{ media }}/images/icon.png">
<link rel="icon" href="{{ media }}/images/icon.png" sizes="192x192">
<link rel="apple-touch-icon" sizes="192x192" href="{{ media }}/images/icon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="msapplication-TileColor" content="#09c">
<link rel="stylesheet" href="{{ media }}/css/style.css?v={{ theme_rev }}">
<script src="{{ media }}/js/jquery.min.js?v=3.5.1"></script>
<script src="{{ media }}/js/compat.js?v={{ theme_rev }}"></script>
<link rel="shortcut icon" href="{{ media }}/favicon.ico">
{% if (node?.css): %}
<link rel="stylesheet" href="{{ resource }}/{{ node.css }}">
{% endif %}
<script src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}"></script>
<script src="{{ resource }}/cbi.js?v={{ theme_rev }}"></script>
{% if (css): %}
<style title="text/css">{{ css }}</style>
{% endif %}
</head>
<body class="lang_{{ dispatcher.lang }} {{ ctx.authsession ? 'logged-in' : '' }} {{ length(dispatch_path) ? `node-${join('-', dispatch_path)}` : 'node-main-login' }}" data-page="{{ entityencode(current_page, true) }}">
<header>
<div class="fill">
<div class="container">
<span class="showSide"></span>
<a class="brand" href="{{ ctx.authsession ? dispatcher.build_url('admin/status/overview') : '#' }}">{{ hostname }}</a>
<div class="status" id="indicators"></div>
</div>
</div>
</header>
<div class="navbar">
<a href="{{ dispatcher.build_url('admin/status/overview') }}"><img src="{{ media }}/images/home.png"></a>
{% if (show_proxy_icon): %}
<a href="{{ dispatcher.build_url('admin/services/openclash') }}"><img src="{{ media }}/images/openclash.png"></a>
{% endif %}
<a href="{{ dispatcher.build_url('admin/network/network') }}"><img src="{{ media }}/images/link.png"></a>
<a href="{{ dispatcher.build_url('admin/status/realtime') }}"><img src="{{ media }}/images/rank.png"></a>
<a href="{{ dispatcher.build_url('admin/system/admin') }}"><img src="{{ media }}/images/user.png"></a>
</div>
<div class="main">
<div class="loading"><span><div class="loading-img"></div>{{ _('Collecting data...') }}</span></div>
<div class="main-left" id="mainmenu" style="display:none"></div>
<div class="main-right">
<div class="modemenu-buttons" style="display:none">
<ul id="modemenu"></ul>
</div>
<div class="darkMask"></div>
<div id="maincontent">
<div class="container">
{% if (getuid() == 0 && getspnam('root')?.pwdp === '' && ctx.authsession && current_page != 'admin-system-admin' && current_page != 'admin-system-admin-password'): %}
<div class="alert-message warning">
<h4>{{ _('No password set!') }}</h4>
<p>{{ _('There is no password set on this router. Please configure a root password to protect the web interface.') }}</p>
{% if (dispatcher.lookup('admin/system/admin')): %}
<div class="right"><a class="btn" href="{{ dispatcher.build_url('admin/system/admin') }}">{{ _('Go to password configuration...') }}</a></div>
{% endif %}
</div>
{% endif %}
{% if (boardinfo?.rootfs_type == 'initramfs'): %}
<div class="alert-message warning">
<h4>{{ _('System running in recovery (initramfs) mode.') }}</h4>
<p>{{ _('No changes to settings will be stored and are lost after rebooting. This mode should only be used to install a firmware upgrade') }}</p>
{% if (dispatcher.lookup('admin/system/flash')): %}
<div class="right"><a class="btn" href="{{ dispatcher.build_url('admin/system/flash') }}">{{ _('Go to firmware upgrade...') }}</a></div>
{% endif %}
</div>
{% endif %}
<noscript>
<div class="alert-message warning">
<h4>{{ _('JavaScript required!') }}</h4>
<p>{{ _('You must enable JavaScript in your browser or LuCI will not work properly.') }}</p>
</div>
</noscript>
<div id="tabmenu" style="display:none"></div>
<script src="{{ media }}/js/style.js?v={{ theme_rev }}"></script>