⛅ Sync 2026-03-22 20:18:41

This commit is contained in:
github-actions[bot] committed 2026-03-22 20:18:41 +08:00
1 parent 6124032c49
commit 93d2ced2d9
38 files changed
+705 -4060

No files matched your search

+8 -2
View File
@@ -1,13 +1,19 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-guest-wifi PKG_NAME:=luci-app-guest-wifi
PKG_MAINTAINER:=kenzok8 <https://github.com/kenzok78>
PKG_VERSION:=2.0.1 PKG_VERSION:=2.0.1
PKG_RELEASE:=1 PKG_RELEASE:=2
LUCI_TITLE:=LuCI support for guest-wifi LUCI_TITLE:=LuCI support for guest-wifi
LUCI_DEPENDS:=+luci-base +luci-compat LUCI_DEPENDS:=+luci-base +luci-compat
LUCI_PKGARCH:=all LUCI_PKGARCH:=all
LUCI_DESCRIPTION:=Simple guest WiFi configuration interface for LuCI
define Package/$(PKG_NAME)/conffiles
/etc/config/wireless
endef
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature # call BuildPackage - OpenWrt buildroot signature
+77 -1
View File
@@ -1 +1,77 @@
# luci-app-guest-wifi # luci-app-guest-wifi
适用于 OpenWrt/LuCI 的访客 WiFi 配置插件。
## 功能特性
- **访客网络配置**: 通过 Web 界面配置独立的访客无线网络
- **加密选项**: 支持无加密、WPA2-PSK、WPA3-SAE、WPA2/WPA3 混合模式
- **AP 隔离**: 可选的客户端隔离功能,防止访客设备之间互相访问
- **自动生效**: 保存配置后自动重新加载无线设置
## 依赖
- `luci`
- `luci-base`
- `luci-compat` (用于兼容旧版 LuCI CBI 模型)
## 安装
```bash
# 添加 feed
echo 'src-git guestwifi https://github.com/kenzok78/luci-app-guest-wifi' >> feeds.conf.default
./scripts/feeds update -a
./scripts/feeds install -a -p guestwifi
# 编译
make package/luci-app-guest-wifi/compile V=s
```
## 使用说明
1. 访问 OpenWrt Web 管理界面 → 网络 → 访客WiFi
2. 添加访客 WiFi 配置段(如果没有自动创建)
3. 启用访客网络,设置 SSID 和加密方式
4. 可选启用 AP 隔离功能
5. 保存并应用
## 配置说明
| 选项 | 说明 |
|------|------|
| 启用 | 是否启用访客 WiFi |
| 网络名称(SSID) | 无线网络名称 |
| 加密方式 | 无/WPA2-PSK/WPA3-SAE/WPA2/WPA3混合 |
| 密码 | 无线连接密码(加密模式下必填)|
| AP 隔离 | 防止客户端之间互相通信 |
## 目录结构
```
luci-app-guest-wifi/
├── Makefile
├── luasrc/
│ ├── controller/ # LuCI 控制器
│ └── model/cbi/ # CBI 模型 (兼容旧版)
├── htdocs/luci-static/resources/view/
│ └── guest-wifi/wifi.js # JavaScript 视图 (现代 LuCI)
├── po/ # 翻译文件
└── root/
├── etc/
│ ├── config/ # UCI 配置 (通过 LuCI 管理)
│ ├── init.d/ # 启动脚本
│ └── uci-defaults/ # 初始化脚本
└── usr/share/
├── luci/menu.d/ # 菜单配置
└── rpcd/acl.d/ # ACL 权限
```
## 工作原理
- 插件通过 UCI 配置管理 `/etc/config/wireless` 中的 `wifi-iface` 段落
- 通过在 `wifi-iface` 配置中添加 `option guest_wifi '1'` 来标识访客网络
- 控制器和视图会自动过滤显示所有标记为访客的 WiFi 配置段
## 许可证
Apache-2.0
@@ -1,46 +0,0 @@
'use strict';
'require form';
'require view';
'require uci';
'require ui';
return view.extend({
load: function() {
return Promise.all([
uci.load('wireless')
]);
},
render: function() {
const m = new form.Map('wireless', _('Guest WiFi'),
_('Guest WiFi provides a separate wireless network for guest access with isolated permissions.'));
const s = m.section(form.TypedSection, 'wifi-iface', _('Guest WiFi Settings'));
s.anonymous = true;
s.addremove = true;
s.option(form.Flag, 'guest_wifi', _('Enable'),
_('Enable guest WiFi network'));
s.option(form.Value, 'ssid', _('Network Name (SSID)'));
const o = s.option(form.ListValue, 'encryption', _('Encryption'));
o.value('none', _('No Encryption'));
o.value('psk2', _('WPA2-PSK'));
o.value('sae', _('WPA3-SAE'));
o.value('sae-mixed', _('WPA2/WPA3-Mixed'));
o.default = 'psk2';
const key = s.option(form.Value, 'key', _('Password'));
key.depends('encryption', 'psk2');
key.depends('encryption', 'sae');
key.depends('encryption', 'sae-mixed');
key.datatype = 'wpakey';
key.password = true;
s.option(form.Flag, 'isolate', _('AP Isolation'),
_('Prevents wireless clients from communicating with each other'));
return m.render();
}
});
@@ -2,57 +2,65 @@
'require form'; 'require form';
'require view'; 'require view';
'require uci'; 'require uci';
'require ui';
return view.extend({ return view.extend({
load: function() { load: function() {
return Promise.all([ return uci.load('wireless');
uci.load('wireless') },
]);
},
render: function() { render: function() {
const m = new form.Map('wireless', _('Guest WiFi'), var m = new form.Map('wireless', _('Guest WiFi'),
_('Guest WiFi provides a separate wireless network for guest access with isolated permissions.')); _('Guest WiFi provides a separate wireless network for guest access with isolated permissions.'));
const s = m.section(form.TypedSection, 'wifi-iface', _('Guest WiFi Settings')); var s = m.section(form.TypedSection, 'wifi-iface', _('Guest WiFi Settings'));
s.anonymous = true; s.anonymous = true;
s.addremove = true; s.addremove = true;
s.filter = function(section_id) { s.filter = function(section_id) {
return uci.get('wireless', section_id, 'guest_wifi') === '1'; return uci.get('wireless', section_id, 'guest_wifi') === '1';
}; };
s.cfgvalue = function(section_id) {
return uci.get('wireless', section_id, 'guest_wifi');
};
s.handleApply = function(section, ev) {
uci.save(function() {
uci.apply(function() {
uci.load('wireless');
XHR.poll(2);
});
});
};
let o; var o = null;
o = s.option(form.Flag, 'guest_wifi', _('Enable'), o = s.option(form.Flag, 'guest_wifi', _('Enable'),
_('Enable guest WiFi network')); _('Enable guest WiFi network'));
o.rmempty = false; o.rmempty = false;
o.default = '0'; o.default = '0';
o = s.option(form.Value, 'ssid', _('Network Name (SSID)')); o = s.option(form.Value, 'ssid', _('Network Name (SSID)'));
o.rmempty = false; o.rmempty = false;
o = s.option(form.ListValue, 'encryption', _('Encryption')); o = s.option(form.ListValue, 'encryption', _('Encryption'));
o.value('none', _('No Encryption')); o.value('none', _('No Encryption'));
o.value('psk2', _('WPA2-PSK')); o.value('psk2', _('WPA2-PSK'));
o.value('sae', _('WPA3-SAE')); o.value('sae', _('WPA3-SAE'));
o.value('sae-mixed', _('WPA2/WPA3-Mixed')); o.value('sae-mixed', _('WPA2/WPA3-Mixed'));
o.rmempty = false; o.rmempty = false;
o.default = 'psk2'; o.default = 'psk2';
o = s.option(form.Value, 'key', _('Password')); o = s.option(form.Value, 'key', _('Password'));
o.depends('encryption', 'psk2'); o.depends('encryption', 'psk2');
o.depends('encryption', 'sae'); o.depends('encryption', 'sae');
o.depends('encryption', 'sae-mixed'); o.depends('encryption', 'sae-mixed');
o.datatype = 'wpakey'; o.datatype = 'wpakey';
o.rmempty = false; o.rmempty = false;
o.password = true; o.password = true;
o = s.option(form.Flag, 'isolate', _('AP Isolation'), o = s.option(form.Flag, 'isolate', _('AP Isolation'),
_('Prevents wireless clients from communicating with each other')); _('Prevents wireless clients from communicating with each other'));
o.rmempty = false; o.rmempty = false;
o.default = '1'; o.default = '1';
return m.render(); return m.render();
} }
}); });
@@ -1,9 +1,11 @@
module("luci.controller.guest-wifi", package.seeall) module("luci.controller.guest-wifi", package.seeall)
function index() local fs = require "nixio.fs"
if not nixio.fs.access("/etc/config/wireless") then
return
end
entry({"admin", "network", "guest-wifi"}, view("guest-wifi/wifi"), _("Guest WiFi"), 60) function index()
end if not fs.access("/etc/config/wireless") then
return
end
entry({"admin", "network", "guest-wifi"}, view("guest-wifi/wifi"), _("Guest WiFi"), 60)
end
@@ -1,21 +1,26 @@
local form = require("form")
local m, s, o local m, s, o
m = Map("wireless", translate("Guest WiFi"), m = Map("wireless", translate("Guest WiFi"),
translate("Guest WiFi provides a separate wireless network for guest access with isolated permissions.")) translate("Guest WiFi provides a separate wireless network for guest access with isolated permissions."))
s = m:section(TypedSection, "wifi-iface", translate("Guest WiFi Settings")) s = m:section(form.TypedSection, "wifi-iface", translate("Guest WiFi Settings"))
s.anonymous = true s.anonymous = true
s.addremove = true s.addremove = true
s.filter = function(section_id)
local val = uci:get("wireless", section_id, "guest_wifi")
return val == "1"
end
o = s:option(Flag, "guest_wifi", translate("Enable"), o = s:option(form.Flag, "guest_wifi", translate("Enable"),
translate("Enable guest WiFi network")) translate("Enable guest WiFi network"))
o.rmempty = false o.rmempty = false
o.default = "0" o.default = "0"
o = s:option(Value, "ssid", translate("Network Name (SSID)")) o = s:option(form.Value, "ssid", translate("Network Name (SSID)"))
o.rmempty = false o.rmempty = false
o = s:option(ListValue, "encryption", translate("Encryption")) o = s:option(form.ListValue, "encryption", translate("Encryption"))
o:value("none", translate("No Encryption")) o:value("none", translate("No Encryption"))
o:value("psk2", translate("WPA2-PSK")) o:value("psk2", translate("WPA2-PSK"))
o:value("sae", translate("WPA3-SAE")) o:value("sae", translate("WPA3-SAE"))
@@ -23,7 +28,7 @@ o:value("sae-mixed", translate("WPA2/WPA3-Mixed"))
o.rmempty = false o.rmempty = false
o.default = "psk2" o.default = "psk2"
o = s:option(Value, "key", translate("Password")) o = s:option(form.Value, "key", translate("Password"))
o:depends("encryption", "psk2") o:depends("encryption", "psk2")
o:depends("encryption", "sae") o:depends("encryption", "sae")
o:depends("encryption", "sae-mixed") o:depends("encryption", "sae-mixed")
@@ -31,9 +36,9 @@ o.datatype = "wpakey"
o.rmempty = false o.rmempty = false
o.password = true o.password = true
o = s:option(Flag, "isolate", translate("AP Isolation"), o = s:option(form.Flag, "isolate", translate("AP Isolation"),
translate("Prevents wireless clients from communicating with each other")) translate("Prevents wireless clients from communicating with each other"))
o.rmempty = false o.rmempty = false
o.default = "1" o.default = "1"
return m return m
@@ -1,5 +1,14 @@
msgid "" msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8" msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: luci-app-guest-wifi\n"
"PO-Revision-Date: 2024\n"
"Last-Translator: kenzok8\n"
"Language-Team: Chinese\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Guest WiFi" msgid "Guest WiFi"
msgstr "" msgstr ""
@@ -41,4 +50,4 @@ msgid "AP Isolation"
msgstr "" msgstr ""
msgid "Prevents wireless clients from communicating with each other" msgid "Prevents wireless clients from communicating with each other"
msgstr "" msgstr ""
+1
View File
@@ -0,0 +1 @@
zh_Hans
-1
View File
@@ -1 +0,0 @@
zh-cn
@@ -1,3 +1,15 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: luci-app-guest-wifi\n"
"PO-Revision-Date: 2024\n"
"Last-Translator: kenzok8\n"
"Language-Team: Chinese\n"
"Language: zh_Hans\n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
msgid "Guest WiFi" msgid "Guest WiFi"
msgstr "访客WiFi" msgstr "访客WiFi"
@@ -38,4 +50,4 @@ msgid "AP Isolation"
msgstr "AP隔离" msgstr "AP隔离"
msgid "Prevents wireless clients from communicating with each other" msgid "Prevents wireless clients from communicating with each other"
msgstr "防止无线客户端之间相互通信" msgstr "防止无线客户端之间相互通信"
@@ -1,3 +0,0 @@
config guest-wifi 'config'
option enabled '0'
option isolate '1'
@@ -4,13 +4,13 @@ START=99
USE_PROCD=1 USE_PROCD=1
reload_service() { reload_service() {
wifi reload wifi reload >/dev/null 2>&1
} }
service_triggers() { service_triggers() {
procd_add_reload_trigger "wireless" procd_add_reload_trigger "wireless"
} }
start_service() { start_service() {
reload_service reload_service
} }
+3 -16
View File
@@ -1,19 +1,6 @@
#!/bin/sh #!/bin/sh
[ ! -f "/usr/share/ucitrack/luci-app-guest-wifi.json" ] && {
cat > /usr/share/ucitrack/luci-app-guest-wifi.json << EEOF
{
"config": "guest-wifi",
"init": "guest-wifi"
}
EEOF
}
uci -q batch <<-EOF >/dev/null [ -z "${IPKG_INSTROOT}" ] || exit 0
delete ucitrack.@guest-wifi[-1]
add ucitrack guest-wifi
set ucitrack.@guest-wifi[-1].init=guest-wifi
commit ucitrack
EOF
rm -f /tmp/luci-indexcache rm -f /tmp/luci-indexcache 2>/dev/null
exit 0 exit 0
@@ -1,8 +0,0 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
set luci.languages.zh_cn='chinese'
commit luci
EOF
exit 0
@@ -1,9 +0,0 @@
module("luci.controller.guest-wifi", package.seeall)
function index()
if not nixio.fs.access("/etc/config/wireless") then
return
end
entry({"admin", "network", "guest-wifi"}, view("guest-wifi/wifi"), _("Guest WiFi"), 60)
end
@@ -1,19 +0,0 @@
#!/bin/sh
# 检查无线配置
check_wireless() {
local enabled
config_load wireless
config_get enabled guest_wifi enabled 0
if [ "$enabled" = "1" ]; then
# 应用guest wifi配置
wifi reload
fi
}
# 主循环
while true; do
check_wireless
sleep 300
done
@@ -1,14 +1,14 @@
{ {
"luci-app-guest-wifi": { "luci-app-guest-wifi": {
"description": "Grant access to guest-wifi configuration", "description": "Grant access to guest-wifi configuration",
"read": { "read": {
"uci": [ "wireless", "guest-wifi" ], "uci": [ "wireless" ],
"ubus": { "ubus": {
"iwinfo": [ "devices", "info", "assoclist" ] "iwinfo": [ "devices", "info", "assoclist" ]
} }
}, },
"write": { "write": {
"uci": [ "wireless", "guest-wifi" ] "uci": [ "wireless" ]
} }
} }
} }
@@ -65,6 +65,9 @@ if singbox_tags:find("with_quic") then
end end
o:value("anytls", "AnyTLS") o:value("anytls", "AnyTLS")
o:value("ssh", "SSH") o:value("ssh", "SSH")
if singbox_tags:find("with_naive_outbound") then
o:value("naive", "NaïveProxy")
end
o:value("_urltest", translate("URLTest")) o:value("_urltest", translate("URLTest"))
o:value("_shunt", translate("Shunt")) o:value("_shunt", translate("Shunt"))
o:value("_iface", translate("Custom Interface")) o:value("_iface", translate("Custom Interface"))
@@ -224,6 +227,7 @@ o = s:option(Value, _n("username"), translate("Username"))
o:depends({ [_n("protocol")] = "http" }) o:depends({ [_n("protocol")] = "http" })
o:depends({ [_n("protocol")] = "socks" }) o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "ssh" }) o:depends({ [_n("protocol")] = "ssh" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(Value, _n("password"), translate("Password")) o = s:option(Value, _n("password"), translate("Password"))
o.password = true o.password = true
@@ -234,6 +238,7 @@ o:depends({ [_n("protocol")] = "trojan" })
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "anytls" }) o:depends({ [_n("protocol")] = "anytls" })
o:depends({ [_n("protocol")] = "ssh" }) o:depends({ [_n("protocol")] = "ssh" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(ListValue, _n("security"), translate("Encrypt Method")) o = s:option(ListValue, _n("security"), translate("Encrypt Method"))
for a, t in ipairs(security_list) do o:value(t) end for a, t in ipairs(security_list) do o:value(t) end
@@ -248,6 +253,7 @@ o:depends({ [_n("protocol")] = "shadowsocks" })
o = s:option(Flag, _n("uot"), translate("UDP over TCP")) o = s:option(Flag, _n("uot"), translate("UDP over TCP"))
o:depends({ [_n("protocol")] = "socks" }) o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(Value, _n("alter_id"), "Alter ID") o = s:option(Value, _n("alter_id"), "Alter ID")
o.datatype = "uinteger" o.datatype = "uinteger"
@@ -399,6 +405,26 @@ o = s:option(Value, _n("ssh_client_version"), translate("Client Version"), trans
o:depends({ [_n("protocol")] = "ssh" }) o:depends({ [_n("protocol")] = "ssh" })
-- [[ SSH config end ]] -- -- [[ SSH config end ]] --
-- [[ naive start ]] --
o = s:option(Value, _n("naive_insecure_concurrency"), translate("Concurrent Tunnels"))
o.datatype = "uinteger"
o.placeholder = "0"
o.default = "0"
o:depends({ [_n("protocol")] = "naive" })
o = s:option(Flag, _n("naive_quic"), translate("QUIC"))
o.default = 0
o:depends({ [_n("protocol")] = "naive" })
o = s:option(ListValue, _n("naive_congestion_control"), translate("Congestion control algorithm"))
o.default = "bbr"
o:value("bbr", translate("BBR"))
o:value("bbr2", translate("BBRv2"))
o:value("cubic", translate("CUBIC"))
o:value("reno", translate("New Reno"))
o:depends({ [_n("naive_quic")] = "1" })
-- [[ naive end ]] --
o = s:option(Flag, _n("tls"), translate("TLS")) o = s:option(Flag, _n("tls"), translate("TLS"))
o.default = 0 o.default = 0
o:depends({ [_n("protocol")] = "vmess" }) o:depends({ [_n("protocol")] = "vmess" })
@@ -432,6 +458,7 @@ o:depends({ [_n("tls")] = true })
o:depends({ [_n("protocol")] = "hysteria"}) o:depends({ [_n("protocol")] = "hysteria"})
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
o.default = "0" o.default = "0"
@@ -446,6 +473,7 @@ o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
o:depends({ [_n("protocol")] = "tuic" }) o:depends({ [_n("protocol")] = "tuic" })
o:depends({ [_n("protocol")] = "hysteria" }) o:depends({ [_n("protocol")] = "hysteria" })
o:depends({ [_n("protocol")] = "hysteria2" }) o:depends({ [_n("protocol")] = "hysteria2" })
o:depends({ [_n("protocol")] = "naive" })
o = s:option(TextValue, _n("ech_config"), translate("ECH Config")) o = s:option(TextValue, _n("ech_config"), translate("ECH Config"))
o.default = "" o.default = ""
@@ -622,6 +650,7 @@ o:depends({ [_n("tcp_guise")] = "http" })
o:depends({ [_n("transport")] = "http" }) o:depends({ [_n("transport")] = "http" })
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
o:depends({ [_n("protocol")] = "naive" })
-- [[ Mux ]]-- -- [[ Mux ]]--
o = s:option(Flag, _n("mux"), translate("Mux")) o = s:option(Flag, _n("mux"), translate("Mux"))
@@ -154,7 +154,7 @@ function gen_outbound(flag, node, tag, proxy_table)
} }
local tls = nil local tls = nil
if node.protocol == "hysteria" or node.protocol == "hysteria2" or node.protocol == "tuic" then if node.protocol == "hysteria" or node.protocol == "hysteria2" or node.protocol == "tuic" or node.protocol == "naive" then
node.tls = "1" node.tls = "1"
end end
if node.tls == "1" then if node.tls == "1" then
@@ -489,6 +489,23 @@ function gen_outbound(flag, node, tag, proxy_table)
} }
end end
if node.protocol == "naive" then
protocol_table = {
username = (node.username and node.username ~= "") and node.username or "",
password = (node.password and node.password ~= "") and node.password or "",
insecure_concurrency = tonumber(node.naive_insecure_concurrency or 0) > 0 and tonumber(node.naive_insecure_concurrency) or 0,
udp_over_tcp = node.uot == "1" and {
enabled = true,
version = 2
} or false,
extra_headers = node.user_agent and {
["User-Agent"] = node.user_agent
} or nil,
quic = node.naive_quic == "1" and true or false,
quic_congestion_control = (node.naive_quic == "1" and node.naive_congestion_control) and node.naive_congestion_control or nil
}
end
if protocol_table then if protocol_table then
for key, value in pairs(protocol_table) do for key, value in pairs(protocol_table) do
result[key] = value result[key] = value
@@ -679,6 +679,31 @@ local current_node = map:get(section)
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure"); params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
} }
params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") {
params = params.substring(1);
}
url += params;
} else if (v_type === "sing-box" && opt.get(dom_prefix + "protocol").value === "naive") {
protocol = "naive+https";
var v_username = opt.get(dom_prefix + "username");
var v_password = opt.get(dom_prefix + "password");
var v_port = opt.get(dom_prefix + "port");
url = encodeURIComponent(v_username.value) +
":" + encodeURIComponent(v_password.value) +
"@" + _address +
":" + v_port.value + "?";
var params = "security=tls";
params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("insecure-concurrency", dom_prefix + "naive_insecure_concurrency");
params += opt.query("ech", dom_prefix + "ech_config");
params += opt.query("uot", dom_prefix + "uot");
if (opt.get(dom_prefix + "naive_quic")?.checked) {
protocol = "naive+quic";
params += opt.query("congestion_control", dom_prefix + "naive_congestion_control");
}
params += "#" + encodeURI(v_alias.value); params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") { if (params[0] == "&") {
params = params.substring(1); params = params.substring(1);
@@ -1729,6 +1754,55 @@ local current_node = map:get(section)
opt.set('remarks', decodeURIComponent(m.hash.substr(1))); opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
} }
} }
if (ssu[0] === "naive+https" || ssu[0] === "naive+quic") {
if (has_singbox) {
dom_prefix = "singbox_"
opt.set('type', "sing-box");
}
opt.set(dom_prefix + 'protocol', "naive");
var _parsedUrl = new URL("http://" + ssu[1]);
var username = _parsedUrl.username;
var password = _parsedUrl.password;
var hostname = _parsedUrl.hostname;
var port = _parsedUrl.port;
var search = _parsedUrl.search;
var hash = _parsedUrl.hash;
if (!username || !password) { //某些链接会把username和password之间的:进行编码
const decoded = decodeURIComponent(username || password || "");
const i = decoded.indexOf(":");
if (i > -1) {
username = decoded.slice(0, i);
password = decoded.slice(i + 1);
}
}
opt.set(dom_prefix + 'username', decodeURIComponent(username));
opt.set(dom_prefix + 'password', decodeURIComponent(password));
opt.set(dom_prefix + 'address', unbracketIP(hostname));
opt.set(dom_prefix + 'port', port || "443");
var queryParam = {};
if (search.length > 1) {
var query = search.split('?')
var queryParams = query[1];
var queryArray = queryParams.split('&');
var params;
for (i = 0; i < queryArray.length; i++) {
params = queryArray[i].split('=');
queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || '');
}
}
opt.set(dom_prefix + 'naive_insecure_concurrency', queryParam['insecure-concurrency'] || '0');
opt.set(dom_prefix + 'uot', (queryParam.uot ?? '0') === '1');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'ech', !!queryParam.ech);
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
if (ssu[0] === "naive+quic") {
opt.set(dom_prefix + 'naive_quic', true);
opt.set(dom_prefix + 'naive_congestion_control', queryParam.congestion_control || 'bbr');
}
if (hash) {
opt.set('remarks', decodeURIComponent(hash.substr(1)));
}
}
if (dom_prefix && dom_prefix != null) { if (dom_prefix && dom_prefix != null) {
if (opt.get(dom_prefix + 'port').value) { if (opt.get(dom_prefix + 'port').value) {
opt.get(dom_prefix + 'port').focus(); opt.get(dom_prefix + 'port').focus();
+3
View File
@@ -2107,3 +2107,6 @@ msgstr "当前使用的 %s 节点"
msgid "Search nodes..." msgid "Search nodes..."
msgstr "搜索节点…" msgstr "搜索节点…"
msgid "Concurrent Tunnels"
msgstr "并发隧道连接数"
@@ -1561,6 +1561,68 @@ local function processData(szType, content, add_mode, group)
return nil return nil
end end
end end
elseif szType == 'naive+https' or szType == 'naive+quic' then
if has_singbox then
result.type = 'sing-box'
result.protocol = "naive"
else
log("跳过 NaïveProxy 节点,因未安装 NaïveProxy 核心程序 Sing-box。")
return nil
end
local alias = ""
if content:find("#") then
local idx_sp = content:find("#")
alias = content:sub(idx_sp + 1, -1)
content = content:sub(0, idx_sp - 1)
end
result.remarks = UrlDecode(alias)
local Info = content
if content:find("@", 1, true) then
local contents = split(content, "@")
local auth = contents[1] or ""
local idx = auth:find(":", 1, true)
if not idx then --修正某些链接会把username和password之间的:进行编码
auth = UrlDecode(auth)
idx = auth:find(":", 1, true)
end
if idx then
result.username = UrlDecode(auth:sub(1, idx - 1))
result.password = UrlDecode(auth:sub(idx + 1))
end
Info = (contents[2] or ""):gsub("/%?", "?")
end
local query = split(Info, "%?")
local host_port = query[1]
local params = {}
for _, v in pairs(split(query[2], '&')) do
local t = split(v, '=')
if #t > 1 then
params[string.lower(t[1])] = UrlDecode(t[2])
end
end
if host_port:find(":") then
local sp = split(host_port, ":")
result.port = sp[#sp]
if api.is_ipv6addrport(host_port) then
result.address = api.get_ipv6_only(host_port)
else
result.address = sp[1]
end
else
result.address = host_port
end
result.tls_serverName = params.sni
result.uot = params.uot
result.naive_insecure_concurrency = params["insecure-concurrency"] or "0"
if params.ech and params.ech ~= "" then
result.ech = "1"
result.ech_config = params.ech
end
if szType == "naive+quic" then
result.naive_quic = "1"
result.naive_congestion_control = params.congestion_control or "bbr"
end
else else
log('暂时不支持' .. szType .. "类型的节点订阅,跳过此节点。") log('暂时不支持' .. szType .. "类型的节点订阅,跳过此节点。")
return nil return nil
+2 -2
View File
@@ -10,8 +10,8 @@ THEME_NAME:=alpha
THEME_TITLE:=Alpha THEME_TITLE:=Alpha
PKG_NAME:=luci-theme-$(THEME_NAME) PKG_NAME:=luci-theme-$(THEME_NAME)
PKG_VERSION:=3.9.6_beta PKG_VERSION:=3.9.7_beta
PKG_RELEASE:=2 PKG_RELEASE:=3
include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/package.mk
+56 -18
View File
@@ -30,29 +30,42 @@ return baseclass.extend({
); );
document.querySelector(".main > .loading").style.opacity = "0"; document.querySelector(".main > .loading").style.opacity = "0";
document.querySelector(".main > .loading").style.visibility = "hidden"; document.querySelector(".main > .loading").style.visibility = "hidden";
if (window.innerWidth <= 1152) if (window.innerWidth <= 1152) {
document.querySelector(".main-left").style.width = "0"; document.querySelector(".main-left").style.transform = "translateX(-20rem)";
}
window.addEventListener("resize", this.handleSidebarToggle, true); window.addEventListener("resize", this.handleSidebarToggle, true);
}, },
handleMenuExpand: function (ev) { handleMenuExpand: function (ev) {
var a = ev.target, var a = ev.target,
ul1 = a.parentNode, ul1 = a.parentNode,
ul2 = a.nextElementSibling; ul2 = a.nextElementSibling,
isActive = ul1.classList.contains("active");
document.querySelectorAll("li.slide.active").forEach(function (li) { document.querySelectorAll("li.slide.active").forEach(function (li) {
if (li !== a.parentNode || li == ul1) { if (li !== a.parentNode || li == ul1) {
var menu = li.querySelector("ul");
if (menu) {
if (!menu.style.maxHeight || menu.style.maxHeight === "1200px") {
menu.style.maxHeight = menu.scrollHeight + "px";
}
void menu.offsetHeight; // Force layout
menu.style.maxHeight = "0px";
}
li.classList.remove("active"); li.classList.remove("active");
li.childNodes[0].classList.remove("active"); li.childNodes[0].classList.remove("active");
} }
if (li == ul1) return; if (li == ul1) return;
}); });
if (!ul2) return; if (!ul2) return;
if ( if (!isActive) {
ul2.parentNode.offsetLeft + ul2.offsetWidth <= if (
ul1.offsetLeft + ul1.offsetWidth ul2.parentNode.offsetLeft + ul2.offsetWidth <=
) ul1.offsetLeft + ul1.offsetWidth
ul2.classList.add("align-left"); )
ul1.classList.add("active"); ul2.classList.add("align-left");
a.classList.add("active"); ul1.classList.add("active");
a.classList.add("active");
ul2.style.maxHeight = ul2.scrollHeight + "px";
}
a.blur(); a.blur();
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@@ -167,13 +180,38 @@ return baseclass.extend({
darkMask = document.querySelector(".darkMask"), darkMask = document.querySelector(".darkMask"),
mainRight = document.querySelector(".main-right"), mainRight = document.querySelector(".main-right"),
mainLeft = document.querySelector(".main-left"), mainLeft = document.querySelector(".main-left"),
open = mainLeft.style.width == ""; open = mainLeft.style.transform === ""; // true if currently open
if (width > 1152 || ev.type == "resize") open = true;
darkMask.style.visibility = open ? "" : "visible"; // If it's a resize event, we simulate that the sidebar was "open" so the logic below closes it for mobile, and opens it for desktop
darkMask.style.opacity = open ? "" : 1; if (ev.type == "resize") {
if (width <= 1152) mainLeft.style.width = open ? "0" : ""; open = true;
else mainLeft.style.width = ""; }
mainLeft.style.visibility = open ? "" : "visible";
mainRight.style["overflow-y"] = open ? "visible" : "hidden"; // Logics: 'open' true means we want to CLOSE it on mobile, but OPEN it on desktop.
// Actually, let's make it clearer: we toggle the state.
var willOpen = !open;
if (ev.type == "resize") {
willOpen = width > 1152;
}
if (width <= 1152) {
// Mobile behavior
mainLeft.style.width = ""; // Reset width
mainLeft.style.transform = willOpen ? "" : "translateX(-20rem)";
mainLeft.style.visibility = willOpen ? "visible" : "";
darkMask.style.visibility = willOpen ? "visible" : "";
darkMask.style.opacity = willOpen ? 1 : "";
mainRight.style.width = "";
} else {
// Desktop behavior
mainLeft.style.width = ""; // Reset width
mainLeft.style.transform = willOpen ? "" : "translateX(-20rem)";
mainLeft.style.visibility = willOpen ? "visible" : "hidden";
mainRight.style.width = willOpen ? "" : "100%";
darkMask.style.visibility = "";
darkMask.style.opacity = "";
}
if (ev && ev.preventDefault) ev.preventDefault();
if (ev && ev.stopPropagation) ev.stopPropagation();
}, },
}); });
+53 -20
View File
@@ -783,6 +783,7 @@
float: right; float: right;
width: calc(100% - 16rem); width: calc(100% - 16rem);
height: 100%; height: 100%;
transition: width 450ms cubic-bezier(0.16, 1, 0.3, 1);
} }
.nowrap:not(.td) { .nowrap:not(.td) {
@@ -795,6 +796,17 @@
} }
/* Sidebar */ /* Sidebar */
.showSide {
position: -webkit-sticky;
position: sticky;
top: 0rem;
display: inline-flex;
align-items: center;
height: 100%;
margin-right: 0.7rem;
cursor: pointer;
}
.main>.main-left { .main>.main-left {
position: fixed; position: fixed;
top: 4.8rem; top: 4.8rem;
@@ -802,7 +814,7 @@
float: left; float: left;
overflow-x: auto; overflow-x: auto;
background-color: #2222359a; background-color: #2222359a;
transition: visibility 400ms, width 400ms; transition: visibility 450ms, transform 450ms cubic-bezier(0.16, 1, 0.3, 1);
border-top-left-radius: 0px; border-top-left-radius: 0px;
border-top-right-radius: 20px; border-top-right-radius: 20px;
border-bottom-left-radius: 0px; border-bottom-left-radius: 0px;
@@ -2499,7 +2511,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
content: ""; content: "";
transition: opacity 400ms, visibility 400ms; transition: opacity 450ms cubic-bezier(0.16, 1, 0.3, 1), visibility 450ms;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
background-color: #2222359a; background-color: #2222359a;
@@ -2840,7 +2852,10 @@
.alert-message .btn, .alert-message .btn,
.modal .btn { .modal .btn {
padding: 0.3rem 0.6rem; padding: 0 1.2rem;
display: inline-flex;
align-items: center;
justify-content: center;
} }
.container .alert, .container .alert,
@@ -2866,14 +2881,8 @@
border-radius: 10px; border-radius: 10px;
} }
.main>.main-left>.nav>.slide>ul,
.main>.main-left[style*="overflow: hidden"]>.nav>.slide>.menu::before,
.tr.placeholder .td[data-title]::before,
.cbi-dropdown>ul.preview,
.cbi-dropdown>ul>li .hide-close,
.cbi-dropdown[open]>ul.dropdown>li .hide-open, .cbi-dropdown[open]>ul.dropdown>li .hide-open,
.hidden, .hidden,
.showSide,
.node-main-login>.main>.main-left, .node-main-login>.main>.main-left,
[data-page^="admin-system-commands"] .panel-title, [data-page^="admin-system-commands"] .panel-title,
[data-page^="command-cfg"] .mobile-hide, [data-page^="command-cfg"] .mobile-hide,
@@ -2881,7 +2890,6 @@
display: none; display: none;
} }
.main>.main-left>.nav>.slide.active>ul,
.cbi-dropdown[empty]>ul>li, .cbi-dropdown[empty]>ul>li,
.cbi-dropdown[optional][open]>ul.dropdown>li[placeholder], .cbi-dropdown[optional][open]>ul.dropdown>li[placeholder],
.cbi-dropdown[multiple][open]>ul.dropdown>li>form, .cbi-dropdown[multiple][open]>ul.dropdown>li>form,
@@ -2892,6 +2900,21 @@
display: block; display: block;
} }
.main>.main-left>.nav>.slide>ul {
display: block;
overflow: hidden;
max-height: 0;
opacity: 0;
visibility: hidden;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease-out, visibility 0.4s;
}
.main>.main-left>.nav>.slide.active>ul {
max-height: 1200px; /* Fallback for initial load */
opacity: 1;
visibility: visible;
transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease-in, visibility 0s;
}
.main>.main-left>.nav>li:hover, .main>.main-left>.nav>li:hover,
.main>.main-left>.nav>.slide>.menu:hover, .main>.main-left>.nav>.slide>.menu:hover,
.main>.main-left>.nav>.slide>.slide-menu>li:hover { .main>.main-left>.nav>.slide>.slide-menu>li:hover {
@@ -3194,16 +3217,26 @@
width: 100%; width: 100%;
} }
.showSide { .showSide {
position: -webkit-sticky; position: -webkit-sticky !important;
position: sticky; position: sticky !important;
top: 0rem; top: 0rem;
display: inline-flex; display: inline-flex !important;
align-items: center; align-items: center;
height: 100%; min-width: 40px;
margin-right: 0.7rem; height: 100%;
cursor: pointer; margin-right: 0.7rem;
} cursor: pointer;
flex-shrink: 0 !important;
z-index: 3000 !important;
}
.showSide img {
width: 30px !important;
height: auto !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
body:not(.logged-in) .showSide { body:not(.logged-in) .showSide {
visibility: hidden; visibility: hidden;
File diff suppressed because it is too large. Load diff
@@ -1 +0,0 @@
buat tambah login page disini
Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 124 KiB

+2 -1
View File
@@ -410,7 +410,8 @@ img.bawah {
display: block; display: block;
padding: 20px; padding: 20px;
text-decoration: none; text-decoration: none;
width: 25rem; width: 14rem;
max-width: 80vw;
} }
.icon-credits a { .icon-credits a {
+95 -114
View File
@@ -1,127 +1,108 @@
<%# <%# Alpha os is made from me for all .. especially for indo wrt members and fan of OpenWrt or DBAI Community
Alpha os is made from me for all .. especially for indo wrt members and fan of OpenWrt or DBAI Community luci-theme-alpha Copyright 2024 derisamedia <facebook.com/derisamedia>
luci-theme-alpha
Copyright 2024 derisamedia <facebook.com/derisamedia>
Have a bug? Please create an issue here on GitHub! Have a bug? Please create an issue here on GitHub!
luci-theme-material luci-theme-material
Copyright 2015 Lutty Yang <lutty@wcan.in> Copyright 2015 Lutty Yang <lutty@wcan.in>
luci-theme-bootstrap: luci-theme-bootstrap:
Copyright 2008 Steven Barth <steven@midlink.org> Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org> Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
Copyright 2012 David Menting <david@nut-bolt.nl> Copyright 2012 David Menting <david@nut-bolt.nl>
MUI: MUI:
https://github.com/muicss/mui https://github.com/muicss/mui
Licensed to the public under the Apache License 2.0 Licensed to the public under the Apache License 2.0
-%> -%>
<% <% local ver=require "luci.version" local uci=require "luci.model.uci" .cursor() local
local ver = require "luci.version" config=uci:get_all("alpha", "theme" ) -- Table to store navbar icons local icon={} -- Iterate
local uci = require "luci.model.uci".cursor() through all navbar entries uci:foreach("alpha", "navbar" , function(section) local
local config = uci:get_all("alpha", "theme") address=section.address local icon_url=section.icon if address and icon_url then --
Remove '/www/luci-static/alpha/' from icon_url
icon_url=string.gsub(icon_url, "^/www/luci%-static/alpha/" , "" ) icon[address]=icon_url end
end) -- Check navbar enable status local navbar_enabled=config.navbar=="1" -- Calculate number
of enabled navbar links local num_links=0 uci:foreach("alpha", "navbar" , function(section) if
section.enable=="Enable" then num_links=num_links + 1 end end) -- Calculate link width for
responsive design local link_width=string.format("calc(100%% / %d)", num_links) -- Retrieve
theme properties local background_color=config.color or '#2222359a' local
blur_value=tonumber(config.blur) or 20 local link_blur=string.format("blur(%dpx)", blur_value)
%>
-- Table to store navbar icons <style>
local icon = {} .main>.main-left,
.cbi-section,
.cbi-section-error,
#iptables,
.Firewall form,
#cbi-network>.cbi-section-node,
#cbi-wireless>.cbi-section-node,
#cbi-wireless>#wifi_assoclist_table,
[data-tab-title],
[data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear),
[data-page="admin-system-opkg"] #maincontent>.container,
.tabs,
.cbi-tabmenu,
.cbi-tooltip {
background-color: <%=background_color%>;
backdrop-filter: <%=link_blur%>;
-webkit-backdrop-filter: <%=link_blur%>;
}
-- Iterate through all navbar entries @media screen and (max-width: 720px) {
uci:foreach("alpha", "navbar", function(section) .navbar a {
local address = section.address width: <%=link_width%>;
local icon_url = section.icon }
}
if address and icon_url then <% if not navbar_enabled then %>.navbar {
-- Remove '/www/luci-static/alpha/' from icon_url display: none;
icon_url = string.gsub(icon_url, "^/www/luci%-static/alpha/", "") }
icon[address] = icon_url
end
end)
-- Check navbar enable status <% end %>
local navbar_enabled = config.navbar == "1" </style>
-- Calculate number of enabled navbar links </div>
local num_links = 0 <footer class="mobile">
uci:foreach("alpha", "navbar", function(section) <a href="https://github.com/derisamedia/luci-theme-alpha">
if section.enable == "Enable" then <%=ver.luciname%> | Alpha OS Theme v3.9.7
num_links = num_links + 1 </a>
end </footer>
end) </div>
<div class="navbar active">
<% if navbar_enabled then %>
<div class="dropdown">
<% uci:foreach("alpha", "navbar" , function(section) local address=section.address
local icon_url=icon[address] or 'default/icon.png' local
target_blank=section.newtab=="Yes" and 'target="_blank"' or '' if
section.enable=="Enable" then %>
<a href="<%=address%>" <%=target_blank%>>
<img src="<%=media%>/<%=icon_url%>" />
</a>
<% end end) %>
</div>
<% end %>
<label class="toggler">
<img src="<%=media%>/gaya/icon/arrow.svg">
</label>
</div>
<script>
{
const nav = document.querySelector(".navbar");
let lastScrollY = window.scrollY;
window.addEventListener("scroll", () => {
if (lastScrollY < window.scrollY) {
nav.classList.add("navbar--hidden");
} else {
nav.classList.remove("navbar--hidden");
}
lastScrollY = window.scrollY;
});
}
</script>
</div>
<script type="text/javascript">L.require('menu-alpha')</script>
<script src="<%=media%>/app.js"></script>
</body>
-- Calculate link width for responsive design </html>
local link_width = string.format("calc(100%% / %d)", num_links)
-- Retrieve theme properties
local background_color = config.color or '#2222359a'
local blur_value = tonumber(config.blur) or 20
local link_blur = string.format("blur(%dpx)", blur_value)
%>
<style>
.main > .main-left, .cbi-section, .cbi-section-error, #iptables, .Firewall form, #cbi-network > .cbi-section-node, #cbi-wireless > .cbi-section-node, #cbi-wireless > #wifi_assoclist_table, [data-tab-title], [data-page^="admin-system-admin"]:not(.node-main-login) .cbi-map:not(#cbi-dropbear), [data-page="admin-system-opkg"] #maincontent > .container, .tabs, .cbi-tabmenu, .cbi-tooltip {
background-color: <%=background_color%>;
backdrop-filter: <%=link_blur%>;
-webkit-backdrop-filter: <%=link_blur%>;
}
@media screen and (max-width: 720px) {
.navbar a {
width: <%=link_width%>;
}
}
<% if not navbar_enabled then %>
.navbar {
display: none;
}
<% end %>
</style>
</div>
<footer class="mobile">
<a href="https://github.com/derisamedia/luci-theme-alpha"><%=ver.luciname%> | Alpha OS Theme v3.9.5</a>
</footer>
</div>
<div class="navbar active">
<% if navbar_enabled then %>
<div class="dropdown">
<%
uci:foreach("alpha", "navbar", function(section)
local address = section.address
local icon_url = icon[address] or 'default/icon.png'
local target_blank = section.newtab == "Yes" and 'target="_blank"' or ''
if section.enable == "Enable" then
%>
<a href="<%=address%>" <%=target_blank%>>
<img src="<%=media%>/<%=icon_url%>" />
</a>
<%
end
end)
%>
</div>
<% end %>
<label class="toggler">
<img src="<%=media%>/gaya/icon/arrow.svg">
</label>
</div>
<script>
{
const nav = document.querySelector(".navbar");
let lastScrollY = window.scrollY;
window.addEventListener("scroll", () => {
if (lastScrollY < window.scrollY) {
nav.classList.add("navbar--hidden");
} else {
nav.classList.remove("navbar--hidden");
}
lastScrollY = window.scrollY;
});
}
</script>
</div>
<script type="text/javascript">L.require('menu-alpha')</script>
<script src="<%=media%>/app.js"></script>
</body>
</html>
+1 -1
View File
@@ -54,7 +54,7 @@
</div> </div>
<footer class="mobile"> <footer class="mobile">
<a href="https://github.com/derisamedia/luci-theme-alpha">{{ version.luciname }} | Alpha OS Theme v3.9.5</a> <a href="https://github.com/derisamedia/luci-theme-alpha">{{ version.luciname }} | Alpha OS Theme v3.9.7</a>
</footer> </footer>
</div> </div>
<div class="navbar active"> <div class="navbar active">
+98 -86
View File
@@ -1,96 +1,108 @@
<%# <%# Alpha os is made from me for all .. especially for indo wrt members and fan of OpenWrt or DBAI Community
Alpha os is made from me for all .. especially for indo wrt members and fan of OpenWrt or DBAI Community luci-theme-alpha Copyright 2022 derisamedia <facebook.com/derisamedia>
luci-theme-alpha
Copyright 2022 derisamedia <facebook.com/derisamedia>
Have a bug? Please create an issue here on GitHub! Have a bug? Please create an issue here on GitHub!
luci-theme-material luci-theme-material
Copyright 2015-2017 Lutty Yang <lutty@wcan.in> Copyright 2015-2017 Lutty Yang <lutty@wcan.in>
luci-theme-bootstrap: luci-theme-bootstrap:
Copyright 2008 Steven Barth <steven@midlink.org> Copyright 2008 Steven Barth <steven@midlink.org>
Copyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org> Copyright 2008-2016 Jo-Philipp Wich <jow@openwrt.org>
Copyright 2012 David Menting <david@nut-bolt.nl> Copyright 2012 David Menting <david@nut-bolt.nl>
MUI: MUI:
https://github.com/muicss/mui https://github.com/muicss/mui
Licensed to the public under the Apache License 2.0 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
local sys = require "luci.sys" disp=require "luci.dispatcher" local ver=require "luci.version" local
local util = require "luci.util" boardinfo=util.ubus("system", "board" ) or { } local node=disp.context.dispatched local
local http = require "luci.http" path=table.concat(disp.context.path, "-" ) http.prepare_content("text/html; charset=UTF-8") -%>
local disp = require "luci.dispatcher" <!DOCTYPE html>
local ver = require "luci.version" <html lang="<%=luci.i18n.context.lang%>">
local boardinfo = util.ubus("system", "board") or { } <head>
<meta charset="utf-8">
<meta name="color-scheme" content="dark">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#09c">
<meta name="msapplication-tap-highlight" content="no">
<meta name="msapplication-TileColor" content="#09c">
<meta name="application-name" content="<%=striptags( (boardinfo.hostname or " ?") ) %> -
LuCI">
<meta name="apple-mobile-web-app-title" content="<%=striptags( (boardinfo.hostname or " ?")
) %> - LuCI">
<link rel="stylesheet" href="<%=media%>/gaya/gaya.css">
<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=git-21.295.66888-fc702bc"></script>
<script src="<%=resource%>/cbi.js?v=git-21.295.66888-fc702bc"></script>
<title>
<%=striptags( (boardinfo.hostname or "?" ) .. ( (node and node.title) and ' - '
.. translate(node.title) or '' )) %> - LuCI
</title>
<% if css then %>
<style title="text/css">
<%=css %>
</style>
<% end -%>
</head>
local node = disp.context.dispatched <body
local path = table.concat(disp.context.path, "-") 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) %>"
style="background-image:url('/luci-static/alpha/background/dashboard.png')">
<header>
<div class="fill">
<div class="container">
<a id="logo"
href="<% if luci.dispatcher.context.authsession then %><%=url('admin/status/overview')%><% else %>#<% end %>">
<img src="<%=media%>/brand.png" alt="OpenWrt">
</a>
<div class="status" id="indicators"></div>
<div class="logout">
<a href="/cgi-bin/luci/admin/logout" data-title="Logout">Keluar</a>
</div>
<div class="showSide">
<img src="<%=media%>/gaya/icon/menu_icon.svg" alt="">
</div>
</div>
</div>
</header>
<div class="main">
<div style="" class="loading"><span>
<div class="loading-img"></div>
</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 -%>
http.prepare_content("text/html; charset=UTF-8") <noscript>
-%> <div class="alert-message warning">
<!DOCTYPE html> <h4>
<html lang="<%=luci.i18n.context.lang%>"> <%:JavaScript required!%>
<head> </h4>
<meta charset="utf-8"> <p>
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/> <%:You must enable JavaScript in your browser or LuCI
<meta name="apple-mobile-web-app-capable" content="yes"> will not work properly.%>
<meta name="mobile-web-app-capable" content="yes"> </p>
<meta name="theme-color" content="#09c"> </div>
<meta name="msapplication-tap-highlight" content="no"> </noscript>
<meta name="msapplication-TileColor" content="#09c"> <div id="tabmenu" style="display:none"></div>
<meta name="application-name" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
<meta name="apple-mobile-web-app-title" content="<%=striptags( (boardinfo.hostname or "?") ) %> - LuCI">
<link rel="stylesheet" href="<%=media%>/gaya/gaya.css">
<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=git-21.295.66888-fc702bc"></script>
<script src="<%=resource%>/cbi.js?v=git-21.295.66888-fc702bc"></script>
<title><%=striptags( (boardinfo.hostname or "?") .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI</title>
<% 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) %>" style="background-image:url('/luci-static/alpha/background/dashboard.png')">
<header>
<div class="fill">
<div class="container">
<a id="logo" href="<% if luci.dispatcher.context.authsession then %><%=url('admin/status/overview')%><% else %>#<% end %>">
<img src="<%=media%>/brand.png" alt="OpenWrt">
</a>
<div class="status" id="indicators"></div>
<div class="logout" >
<a href="/cgi-bin/luci/admin/logout" data-title="Logout">Keluar</a>
</div>
<div class="showSide">
<img src="<%=media%>/gaya/icon/menu_icon.svg" alt="">
</div>
</div>
</div>
</header>
<div class="main">
<div style="" class="loading"><span><div class="loading-img"></div></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>
+1
View File
@@ -14,6 +14,7 @@
<html lang="{{ dispatcher.lang }}"> <html lang="{{ dispatcher.lang }}">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="color-scheme" content="dark">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/>
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
+1 -1
View File
@@ -2,7 +2,7 @@
<!-- saved from url=(0014)about:internet --> <!-- saved from url=(0014)about:internet -->
<html lang="en"> <html lang="en">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="color-scheme" content="dark">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
+1 -1
View File
@@ -3,7 +3,7 @@
<html lang="{{ dispatcher.lang }}"> <html lang="{{ dispatcher.lang }}">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="color-scheme" content="dark">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
+2 -2
View File
@@ -1452,8 +1452,8 @@
} }
@layer table { @layer table {
.table, table.table,
table { .table {
@apply border-border max-md:bg-panel-bg/98 mb-3 w-full border-separate border-spacing-0 overflow-visible rounded-2xl border max-md:mx-0 max-md:block max-md:w-full max-md:overflow-visible max-md:border-0 max-md:shadow-none; @apply border-border max-md:bg-panel-bg/98 mb-3 w-full border-separate border-spacing-0 overflow-visible rounded-2xl border max-md:mx-0 max-md:block max-md:w-full max-md:overflow-visible max-md:border-0 max-md:shadow-none;
&[width="100%"] { &[width="100%"] {
+2 -2
View File
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS) LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
LUCI_DEPENDS:=+luci-base LUCI_DEPENDS:=+luci-base
PKG_VERSION:=0.11.1 PKG_VERSION:=0.11.2
PKG_RELEASE:=2 PKG_RELEASE:=3
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
LUCI_MINIFY_CSS:= LUCI_MINIFY_CSS:=
@@ -1,2 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading:initial;--tw-font-weight:initial;--tw-duration:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-space-y-reverse:0;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-md:28rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-Line truncated @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading:initial;--tw-font-weight:initial;--tw-duration:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-content:"";--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-space-y-reverse:0;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-md:28rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--radius-4xl:2rem;--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-Line truncated