🏅 Sync 2026-04-19 00:24:08
No files matched your search
@@ -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
|
|
||||||
@@ -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`
|
|
||||||
@@ -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.
|
|
||||||
@@ -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:=
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
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>
|
|
||||||
|
Before Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 373 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 561 KiB |
|
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>
|
||||||