+ `;
+}
+
+async function loadModel(state) {
+ const [summary, overview] = await Promise.all([
+ fetchJSON(`${state.apiBase}summary`),
+ fetchJSON(`${state.apiBase}mirrors/overview`),
+ ]);
+ state.model.summary = summary;
+ state.model.overview = overview;
+ state.model.error = "";
+}
+
+async function reload(state) {
+ try {
+ await loadModel(state);
+ } catch (error) {
+ state.model.error = error instanceof Error ? error.message : "加载失败";
+ } finally {
+ render(state.root, state.model);
+ }
+}
+
+async function refreshMirrors(state) {
+ state.model.refreshing = true;
+ state.model.error = "";
+ render(state.root, state.model);
+ try {
+ await fetchJSON(`${state.apiBase}mirrors/refresh`, { method: "POST" });
+ await loadModel(state);
+ if (state.hostAdapter && state.hostAdapter.toast) {
+ state.hostAdapter.toast.success("KSpeeder 测速已刷新");
+ }
+ } catch (error) {
+ state.model.error = error instanceof Error ? error.message : "刷新测速失败";
+ if (state.hostAdapter && state.hostAdapter.toast) {
+ state.hostAdapter.toast.error(state.model.error);
+ }
+ } finally {
+ state.model.refreshing = false;
+ render(state.root, state.model);
+ }
+}
+
+export async function bootstrap() {}
+
+export async function mount(props) {
+ const root = props && props.domElement;
+ if (!root) {
+ return;
+ }
+ const context = props.context || {};
+ const state = {
+ root,
+ apiBase: normalizeAPIBase(context),
+ hostAdapter: props.hostAdapter,
+ model: {
+ summary: {},
+ overview: {},
+ error: "",
+ refreshing: false,
+ },
+ };
+ stateByElement.set(root, state);
+ root.addEventListener("click", handleClick);
+ render(root, state.model);
+ await reload(state);
+}
+
+export async function unmount(props) {
+ const root = props && props.domElement;
+ if (!root) {
+ return;
+ }
+ root.removeEventListener("click", handleClick);
+ root.innerHTML = "";
+ stateByElement.delete(root);
+}
+
+function handleClick(event) {
+ const target = event.target && event.target.closest ? event.target.closest("[data-action]") : null;
+ if (!target) {
+ return;
+ }
+ const root = event.currentTarget;
+ const state = stateByElement.get(root);
+ if (!state) {
+ return;
+ }
+ const action = target.getAttribute("data-action");
+ if (action === "reload") {
+ void reload(state);
+ } else if (action === "refresh-mirrors") {
+ void refreshMirrors(state);
+ }
+}
+
+export default { bootstrap, mount, unmount };
diff --git a/istoreenhance/files/www/index.html b/istoreenhance/files/www/index.html
new file mode 100644
index 00000000..bae0337c
--- /dev/null
+++ b/istoreenhance/files/www/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+ KSpeeder
+
+
+
+
+
+
+
diff --git a/kaiplus/Makefile b/kaiplus/Makefile
index 92a28094..1840c9e5 100644
--- a/kaiplus/Makefile
+++ b/kaiplus/Makefile
@@ -9,8 +9,8 @@ include $(TOPDIR)/rules.mk
PKG_ARCH_kaiplus:=$(ARCH)
PKG_NAME:=kaiplus
-PKG_VERSION:=kaiplus-runtime-v1.0.5
-PKG_RELEASE:=5
+PKG_VERSION:=dockermanager-runtime-v0.1.1
+PKG_RELEASE:=6
PKG_SOURCE:=$(PKG_NAME)-binary-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/kaiplus-runtime-v$(PKG_VERSION)/
PKG_HASH:=skip
diff --git a/linkease-common-bin/Makefile b/linkease-common-bin/Makefile
index 0bbef523..d64e6125 100644
--- a/linkease-common-bin/Makefile
+++ b/linkease-common-bin/Makefile
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
PKG_NAME:=linkease-common-bin
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
PKG_SOURCE_DATE:=1.7.5
-PKG_RELEASE:=14
+PKG_RELEASE:=15
ARCH_HEXCODE:=
ifeq ($(ARCH),x86_64)
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
ARCH_HEXCODE=1b0c
endif
-PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc
+PKG_SOURCE_VERSION:=b0d9136d243ac1a5c5290190ffa6f66265aa7fba
PKG_SOURCE:=linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-common-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
diff --git a/linkease/Makefile b/linkease/Makefile
index c5109c10..79ba3639 100644
--- a/linkease/Makefile
+++ b/linkease/Makefile
@@ -12,7 +12,7 @@ PKG_ARCH_LINKEASE:=$(ARCH)
PKG_NAME:=linkease
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
PKG_SOURCE_DATE:=1.7.5
-PKG_RELEASE:=17
+PKG_RELEASE:=18
ARCH_HEXCODE:=
ifeq ($(ARCH),x86_64)
@@ -28,7 +28,7 @@ else ifeq ($(ARCH),mipsel)
ARCH_HEXCODE=1b0c
endif
-PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc
+PKG_SOURCE_VERSION:=b0d9136d243ac1a5c5290190ffa6f66265aa7fba
PKG_SOURCE:=linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkease-runtime-v$(PKG_SOURCE_DATE)/
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-bin-$(PKG_SOURCE_DATE)-linux-$(PKG_ARCH_LINKEASE)
diff --git a/linkeasefull/Makefile b/linkeasefull/Makefile
index 53a042c0..79729b7c 100644
--- a/linkeasefull/Makefile
+++ b/linkeasefull/Makefile
@@ -10,8 +10,8 @@ PKG_ARCH_LINKEASE:=$(ARCH)
PKG_NAME:=linkeasefull
# use PKG_SOURCE_DATE instead of PKG_VERSION for compitable
-PKG_SOURCE_DATE:=3.0.12
-PKG_RELEASE:=16
+PKG_SOURCE_DATE:=3.0.13
+PKG_RELEASE:=17
ARCH_HEXCODE:=
ifeq ($(ARCH),x86_64)
@@ -24,7 +24,7 @@ LINKEASE_RUNTIME_ARCH:=arm64
PKG_HASH:=skip
endif
-PKG_SOURCE_VERSION:=2036fce819b5a44574d07cca9927eae73da43efc
+PKG_SOURCE_VERSION:=b0d9136d243ac1a5c5290190ffa6f66265aa7fba
PKG_SOURCE:=linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH).tar.gz
PKG_SOURCE_URL:=https://github.com/istoreos/istoreos-app-hub/releases/download/linkeasefull-runtime-v$(PKG_SOURCE_DATE)/
PKG_BUILD_DIR:=$(BUILD_DIR)/linkease-runtime-$(PKG_SOURCE_DATE)-linux-$(LINKEASE_RUNTIME_ARCH)
diff --git a/linkeasefull/files/linkeasefull.init b/linkeasefull/files/linkeasefull.init
index b3ef2d48..13639675 100644
--- a/linkeasefull/files/linkeasefull.init
+++ b/linkeasefull/files/linkeasefull.init
@@ -52,10 +52,6 @@ is_persistent_data_root_parent() {
return 0
}
-linkease_enabled() {
- [ "$(uci -q get linkease.@linkease[0].enabled 2>/dev/null)" = "1" ]
-}
-
read_config() {
section="$1"
config_get_bool enabled "$section" enabled 1
@@ -80,10 +76,6 @@ start_service() {
config_foreach read_config linkeasefull
[ "$enabled" = "1" ] || return 1
[ -x "$PROG" ] || return 1
- if linkease_enabled; then
- echo "linkeasefull: linkease is enabled; please disable linkease before starting linkeasefull" >&2
- return 1
- fi
rm -f "$LOCAL_API"
if [ -n "$data_root_parent" ]; then
diff --git a/luci-app-accesscontrol-plus/Makefile b/luci-app-accesscontrol-plus/Makefile
index c67f3b67..e291e134 100644
--- a/luci-app-accesscontrol-plus/Makefile
+++ b/luci-app-accesscontrol-plus/Makefile
@@ -12,7 +12,7 @@ LUCI_PKGARCH:=all
PKG_NAME:=luci-app-accesscontrol-plus
PKG_VERSION:=1
-PKG_RELEASE:=2
+PKG_RELEASE:=3
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-accesscontrol-plus/luasrc/controller/miaplus.lua b/luci-app-accesscontrol-plus/luasrc/controller/miaplus.lua
index ae5454a0..f33fb9ec 100644
--- a/luci-app-accesscontrol-plus/luasrc/controller/miaplus.lua
+++ b/luci-app-accesscontrol-plus/luasrc/controller/miaplus.lua
@@ -15,7 +15,8 @@ end
function act_status()
local e = {}
- e.running = luci.sys.call("iptables -t filter -S MIAPLUS >/dev/null 2>&1") == 0
+ -- 交给 init 脚本的 status 方法判断:它会按 fw3(iptables)/fw4(nft) 自动分发
+ e.running = luci.sys.call("/etc/init.d/miaplus status >/dev/null 2>&1") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
diff --git a/luci-app-accesscontrol-plus/luasrc/model/cbi/template.lua b/luci-app-accesscontrol-plus/luasrc/model/cbi/template.lua
index d931e842..b111d57d 100644
--- a/luci-app-accesscontrol-plus/luasrc/model/cbi/template.lua
+++ b/luci-app-accesscontrol-plus/luasrc/model/cbi/template.lua
@@ -16,13 +16,42 @@ e = t:option(Flag, "enable", translate("Enabled"))
e.rmempty = false
e.default = "1"
+-- HH:MM / H:MM -> 分钟;非法或空返回 nil
+local function time_to_min(s)
+ local h, m = s:match("^(%d?%d):(%d%d)$")
+ if not h then return nil end
+ h, m = tonumber(h), tonumber(m)
+ if h > 23 or m > 59 then return nil end
+ return h * 60 + m
+end
+
e = t:option(Value, "timeon", translate("Start time"))
e.optional = false
e.default = "00:00"
+function e.validate(self, value, section)
+ local mins = time_to_min(value)
+ if not mins then
+ return nil, translate("Invalid time, expected HH:MM")
+ end
+ return string.format("%02d:%02d", math.floor(mins / 60), mins % 60)
+end
e = t:option(Value, "timeoff", translate("End time"))
e.optional=false
e.default = "23:59"
+function e.validate(self, value, section)
+ local mins = time_to_min(value)
+ if not mins then
+ return nil, translate("Invalid time, expected HH:MM")
+ end
+ -- 跨字段:起止时间不能相同(起止相同即无管控,易误配)
+ local on = self.map:formvalue("cbid.miaplus." .. section .. ".timeon")
+ local on_min = on and time_to_min(on)
+ if on_min ~= nil and on_min == mins then
+ return nil, translate("Start and end time must differ")
+ end
+ return string.format("%02d:%02d", math.floor(mins / 60), mins % 60)
+end
e = t:option(Flag, "z1", translate("Mon"))
e.rmempty = true
diff --git a/luci-app-accesscontrol-plus/root/etc/init.d/miaplus b/luci-app-accesscontrol-plus/root/etc/init.d/miaplus
index 03d370b4..4090d682 100755
--- a/luci-app-accesscontrol-plus/root/etc/init.d/miaplus
+++ b/luci-app-accesscontrol-plus/root/etc/init.d/miaplus
@@ -10,8 +10,19 @@ START=30
CONFIG=miaplus
+# 非 procd 脚本:status 命令不在 rc.common 默认命令表里,需显式注册
+EXTRA_COMMANDS="status"
+EXTRA_HELP=" status Show service status (auto fw3/fw4)"
+
. /lib/functions.sh
+# 检测当前使用的防火墙框架:fw4 (nftables) 还是 fw3 (iptables)
+# 注意:fw4 与 fw3 共用 /etc/config/firewall,不存在 /etc/config/fw4,
+# 只能靠 fw4 / nft 命令是否存在来判定。
+have_fw4() {
+ command -v fw4 >/dev/null 2>&1 && command -v nft >/dev/null 2>&1
+}
+
uci_export_section_name() {
local ret=$(uci -n export $CONFIG | grep "config $1" | awk '{print $3}' | awk -F\' '{print $2}')
echo ${ret:=$2}
@@ -62,11 +73,23 @@ add_template_rule(){
config_get enable "$section" enable 0
[ "$enable" = "1" ] && add_rule "$section"
}
+# 分发到具体实现
add_rule(){
+ if have_fw4; then
+ add_rule_nft "$1"
+ else
+ add_rule_ipt "$1"
+ fi
+}
+
+# =====================================================================
+# fw3 (iptables / ip6tables)
+# =====================================================================
+add_rule_ipt(){
ipv6enable=$(uci -q get miaplus.@basic[0].ipv6enable)
local macaddrs=$(uci_get_mac_by_template $1 '')
if [ -z "$macaddrs" ]; then
- exit 0
+ return 0
fi
for macaddr in $macaddrs
do
@@ -110,21 +133,9 @@ add_rule(){
done
}
-del_rule(){
- type=$1
- blackMacAdd=$(iptables -t nat -L $type | grep -w RETURN | grep -w "MAC" | awk '{print $7}')
- [ -n "$blackMacAdd" ] && {
- for macaddrb in $blackMacAdd
- do
- iptables -t nat -D $type -m mac --mac-source $macaddrb -j RETURN
- done
- }
-}
-
-start(){
- stop
+start_ipt(){
enable=$(uci -q get miaplus.@basic[0].enable)
- [ "${enable:-0}" -eq 0 ] && exit 0
+ [ "${enable:-0}" -eq 0 ] && return 0
iptables -t filter -N MIAPLUS
iptables -I INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
iptables -I INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS
@@ -144,7 +155,7 @@ start(){
fi
add_rules
}
-status(){
+status_ipt(){
if iptables -t filter -S MIAPLUS >/dev/null 2>&1; then
echo "running"
return 0
@@ -153,7 +164,7 @@ status(){
echo "not running"
return 1
}
-stop(){
+stop_ipt(){
iptables -t filter -D FORWARD -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
iptables -D INPUT -p udp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
iptables -D INPUT -p tcp --dport 53 -m comment --comment "Rule For Control" -j MIAPLUS 2>/dev/null
@@ -173,3 +184,260 @@ stop(){
ip6tables -t filter -X MIAPLUS 2>/dev/null
fi
}
+
+# =====================================================================
+# fw4 (nftables)
+# 使用独立表 inet miaplus,避免与 fw4 自身的 nftables 规则冲突。
+#
+# table inet miaplus {
+# chain input { type filter hook input priority 0; } # 仅匹配 53 端口
+# chain prerouting{ type nat hook prerouting priority dstnat; } # DNS 重定向到本机
+# }
+#
+# DNS 语义与 fw3 一致:只对发往 53 端口的流量做上网控制。
+#
+# NAT 竞争说明:本表 prerouting 链与 fw4 主表(inet fw4)的 PREROUTING/dstnat 同为
+# priority -100(dstnat)。nf_tables 允许多表同优先级共存,但同一数据包的同一 hook
+# 优先级内按表加载顺序处理,conntrack binding(redirect/redirect)先到先得。若 fw4
+# 自身对 DNS(53) 也做了重定向/端口转发,两者可能互抢,现象以先加载者为准。
+# 一般情况下 DNS 默认直通本机,二者不冲突;如遇规则不生效,优先排查此项。
+# =====================================================================
+
+# 构造 nft 的星期集合片段,如 "{ 1, 2 }" / "5" / 空(不限制)
+# z1..z7 => 周一..周日;nft meta day: 0=Sun,1=Mon,...6=Sat => z1->1 ...
+nft_dayterm() {
+ local days="" n=1 day
+ for z in "$1" "$2" "$3" "$4" "$5" "$6" "$7"; do
+ [ "$z" = "1" ] || { n=$((n+1)); continue; }
+ day=$(( n % 7 )) # z1..z7 -> 1,2,3,4,5,6,0
+ days="${days:+$days, }$day"
+ n=$((n+1))
+ done
+ if [ -z "$days" ]; then
+ echo ""
+ elif echo "$days" | grep -q ,; then
+ echo "{ $days }"
+ else
+ echo "$days"
+ fi
+}
+
+# 去掉前导零(防空串转成 "0"),规避 08/09 被当八进制的算术陷阱
+_digits() {
+ echo "$1" | sed -e 's/^0*//' -e 's/^$/0/'
+}
+# HH:MM -> 分钟数(输入可为 H:M 或 HH:MM,非零填充)
+_mins() {
+ local h m
+ h=$(_digits "${1%%:*}")
+ m=$(_digits "${1#*:}")
+ echo $(( ${h:-0} * 60 + ${m:-0} ))
+}
+# 任意 "H:M"/"HH:MM" -> 规范 "HH:MM"
+_pad() {
+ local h m
+ h=$(_digits "${1%%:*}")
+ m=$(_digits "${1#*:}")
+ printf '%02d:%02d' "${h:-0}" "${m:-0}"
+}
+
+# 把一个 [t_on, t_off] 窗口(H:M/HH:MM)转成 nft 时间匹配表达式(行),
+# 输出到全局变量 NFT_FRAGS。nft 的 meta hour 原生支持 "HH:MM"-"HH:MM"。
+# 统一语义:结束分钟视为完整包含(闭区间含其末秒),故上界一律补 :59 —
+# 非跨午夜 [t_on, t_off] -> "HH:MM"-"t_off:59";跨午夜拆 [t_on,23:59:59]+[00:00,t_off:59]。
+nft_split_window() {
+ local t_on t_off
+ NFT_FRAGS=""
+ [ -n "$1" ] && [ -n "$2" ] || return 0
+ t_on=$(_pad "$1"); t_off=$(_pad "$2")
+
+ if [ "$(_mins "$t_on")" -le "$(_mins "$t_off")" ]; then
+ NFT_FRAGS="meta hour \"$t_on\"-\"$t_off:59\""
+ else
+ # 跨午夜:拆成 [on,23:59:59] + [00:00,off:59]
+ NFT_FRAGS="meta hour \"$t_on\"-\"23:59:59\"
+meta hour \"00:00\"-\"$t_off:59\""
+ fi
+}
+
+# 统一执行 nft 规则命令并检查返回码。
+# 失败时记日志、立即回滚删表并置全局 NFT_FAIL=1(供调用方中止后续写入),
+# 使最终状态收敛为"完整规则表"或"空表",避免留下只 DROP 无 ACCEPT 的断网中间态。
+nft_rule() {
+ # 已失败则短路,避免对已回滚删除的表继续逐条报错刷屏
+ [ -n "$NFT_FAIL" ] && return 1
+ "$@" 2>/dev/null || {
+ logger -t miaplus "nft rule failed: $*"
+ stop_nft
+ NFT_FAIL=1
+ }
+}
+
+# 组合规则前缀:链决定是否只限 53 端口;ipv6enable=0 时限定 ipv4
+nft_prefix() {
+ local chain=$1
+ local pref=""
+ [ "$chain" = "forward" ] || pref="meta l4proto { tcp, udp } th dport 53 "
+ if [ "$ipv6enable" != "1" ]; then
+ pref="meta nfproto ipv4 $pref"
+ fi
+ NFT_PREFIX="$pref"
+}
+
+# 插入一条白名单 ACCEPT 规则(insert 到链首,兜底 DROP 在尾)
+# 参数:MAC day_term [链名=当前]
+nft_insert_accept() {
+ local mac=$1 day=$2 chain=${3:-input}
+ nft_prefix "$chain"
+ local cond
+ while IFS= read -r frag; do
+ [ -n "$frag" ] || continue
+ cond="$NFT_PREFIX ether saddr $mac $frag"
+ [ -n "$day" ] && cond="$cond meta day $day"
+ nft_rule nft insert rule inet miaplus $chain $cond accept
+ [ -n "$NFT_FAIL" ] && return 1
+ done </dev/null 2>&1; then
+ echo "running"
+ return 0
+ fi
+ echo "not running"
+ return 1
+}
+stop_nft(){
+ nft delete table inet miaplus 2>/dev/null
+ return 0
+}
+
+# =====================================================================
+# 统一入口
+# =====================================================================
+start(){
+ stop
+ if have_fw4; then
+ start_nft
+ else
+ start_ipt
+ fi
+}
+status(){
+ if have_fw4; then
+ status_nft
+ else
+ status_ipt
+ fi
+}
+stop(){
+ if have_fw4; then
+ stop_nft
+ else
+ stop_ipt
+ fi
+}
diff --git a/luci-app-accesscontrol-plus/root/etc/uci-defaults/luci-miaplus b/luci-app-accesscontrol-plus/root/etc/uci-defaults/luci-miaplus
index 63fa5d15..ce12a6f2 100644
--- a/luci-app-accesscontrol-plus/root/etc/uci-defaults/luci-miaplus
+++ b/luci-app-accesscontrol-plus/root/etc/uci-defaults/luci-miaplus
@@ -8,6 +8,8 @@
EEOF
}
+# fw4 与 fw3 共用 /etc/config/firewall 的 config include 机制;fw4_compatible
+# 让 fw4 在 reload 时执行用户脚本,fw3 忽略此未知 option(安全)
uci -q batch <<-EOF >/dev/null
delete ucitrack.@miaplus[-1]
add ucitrack miaplus
@@ -18,6 +20,7 @@ uci -q batch <<-EOF >/dev/null
set firewall.miaplus.type=script
set firewall.miaplus.path=/etc/miaplus.include
set firewall.miaplus.reload=1
+ set firewall.miaplus.fw4_compatible=1
commit firewall
EOF
diff --git a/luci-app-baidudrive/Makefile b/luci-app-baidudrive/Makefile
index 30537189..0aa2243a 100644
--- a/luci-app-baidudrive/Makefile
+++ b/luci-app-baidudrive/Makefile
@@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=baidudrive
-PKG_VERSION:=1.0.1
-PKG_RELEASE:=2
+PKG_VERSION:=1.0.2
+PKG_RELEASE:=3
LUCI_DEPENDS:=+baidudrive +luci-compat
LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0
diff --git a/luci-app-istoreenhance/Makefile b/luci-app-istoreenhance/Makefile
index 019d1941..145a19e2 100644
--- a/luci-app-istoreenhance/Makefile
+++ b/luci-app-istoreenhance/Makefile
@@ -8,11 +8,10 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for KSpeeder
LUCI_DEPENDS:=+istoreenhance
LUCI_PKGARCH:=all
-PKG_VERSION:=0.6.6-r1
+PKG_VERSION:=0.6.6-r2
# PKG_RELEASE MUST be empty for luci.mk
-PKG_RELEASE:=1
+PKG_RELEASE:=2
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
-
diff --git a/luci-app-kaiplus/Makefile b/luci-app-kaiplus/Makefile
index c9ac94ed..a08afbaa 100644
--- a/luci-app-kaiplus/Makefile
+++ b/luci-app-kaiplus/Makefile
@@ -7,8 +7,8 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=KaiPlus
-PKG_VERSION:=1.0.0
-PKG_RELEASE:=4
+PKG_VERSION:=1.0.1
+PKG_RELEASE:=5
LUCI_DEPENDS:=+kaiplus +luci-compat
LUCI_MINIFY_CSS:=0
LUCI_MINIFY_JS:=0
diff --git a/luci-app-mptcp/docs/images/01-mptcp-settings.png b/luci-app-mptcp/docs/images/01-mptcp-settings.png
new file mode 100644
index 00000000..27aad392
Binary files /dev/null and b/luci-app-mptcp/docs/images/01-mptcp-settings.png differ
diff --git a/luci-app-mptcp/docs/images/01a-diagnostics.png b/luci-app-mptcp/docs/images/01a-diagnostics.png
new file mode 100644
index 00000000..f3b2f88b
Binary files /dev/null and b/luci-app-mptcp/docs/images/01a-diagnostics.png differ
diff --git a/luci-app-mptcp/docs/images/01b-dscp-weight-routing.png b/luci-app-mptcp/docs/images/01b-dscp-weight-routing.png
new file mode 100644
index 00000000..85ea24e1
Binary files /dev/null and b/luci-app-mptcp/docs/images/01b-dscp-weight-routing.png differ
diff --git a/luci-app-mptcp/docs/images/02-bandwidth-all.png b/luci-app-mptcp/docs/images/02-bandwidth-all.png
new file mode 100644
index 00000000..2cadad1d
Binary files /dev/null and b/luci-app-mptcp/docs/images/02-bandwidth-all.png differ
diff --git a/luci-app-mptcp/docs/images/03-bandwidth-iface.png b/luci-app-mptcp/docs/images/03-bandwidth-iface.png
new file mode 100644
index 00000000..a6de47e0
Binary files /dev/null and b/luci-app-mptcp/docs/images/03-bandwidth-iface.png differ
diff --git a/luci-app-mptcp/docs/images/04-established-connections.png b/luci-app-mptcp/docs/images/04-established-connections.png
new file mode 100644
index 00000000..2c8126fa
Binary files /dev/null and b/luci-app-mptcp/docs/images/04-established-connections.png differ
diff --git a/luci-app-mptcp/docs/images/05-fullmesh.png b/luci-app-mptcp/docs/images/05-fullmesh.png
new file mode 100644
index 00000000..f2aca213
Binary files /dev/null and b/luci-app-mptcp/docs/images/05-fullmesh.png differ
diff --git a/luci-app-mptcp/docs/images/06-monitoring.png b/luci-app-mptcp/docs/images/06-monitoring.png
new file mode 100644
index 00000000..2d7f0008
Binary files /dev/null and b/luci-app-mptcp/docs/images/06-monitoring.png differ
diff --git a/luci-app-mptcp/docs/mptcp-guide.md b/luci-app-mptcp/docs/mptcp-guide.md
new file mode 100644
index 00000000..929409d3
--- /dev/null
+++ b/luci-app-mptcp/docs/mptcp-guide.md
@@ -0,0 +1,247 @@
+# MPTCP — User Guide
+
+`luci-app-mptcp` is the LuCI front end for the kernel's Multipath TCP stack
+— the layer that actually bonds your WAN links together. It has seven pages
+under **Network → MPTCP**: **MPTCP** (settings), **Diagnostics**,
+**DSCP / Weight Routing**, **Bandwidth**, **Established connections**,
+**MPTCP Fullmesh**, and **MPTCP monitoring**.
+
+Screenshots below were taken on a test router (`v0.64-snapshot`, kernel
+6.18, 3 WAN links). Peer/remote IPs that would reveal the test VPS were
+replaced with a placeholder (`203.0.113.10`) before capture — everything
+else is unmodified live output.
+
+## MPTCP (settings)
+
+```
+https:///cgi-bin/luci/admin/network/mptcp/mptcp
+```
+
+
+
+The form's contents adapt to your kernel version — the screenshot is from
+a 6.18 kernel, which exposes the modern in-kernel/userspace path-manager
+options; older kernels (<6) show a different, longer set of legacy fields
+(scheduler-specific tuning like ndiffports/round-robin subflow counts,
+`mptcp_version`, etc.) instead.
+
+### GLOBALS
+
+| Field | Meaning |
+|---|---|
+| **Multipath TCP** | Read-only indicator of whether MPTCP is enabled network-wide. |
+| **Multipath TCP checksum** | Enables MPTCP-level checksums (extra integrity check, minor overhead). |
+| **Multipath TCP path-manager** | `default` or `fullmesh` (OMR's normal choice — creates subflows between every local/remote address pair). |
+| **Multipath TCP scheduler** | Which subflow the kernel picks to send data on. `default`, or a BPF scheduler (`bpf_burst`, `bpf_red`, `bpf_first`, `bpf_rr`, plus any custom `.o` dropped into `/usr/share/bpf/scheduler` — auto-discovered and listed here). On kernels <6, classic in-tree schedulers (round-robin, redundant, BLEST, ECF) are offered instead. |
+| **Congestion Control** | Populated live from `sysctl net.ipv4.tcp_available_congestion_control` — pick any congestion control algorithm your kernel has compiled in. Default is `cubic`; this bench uses `bbr`. |
+| **Path Manager type** *(kernel ≥6 only)* | In-kernel (simpler, default) vs. userspace (delegates subflow decisions to `mptcpd`). |
+| **Max subflows** | Cap on additional subflows per MPTCP connection — usually matches (or exceeds) your WAN count. |
+| **Retranmission intervals** | How many MPTCP-level retransmit intervals with no traffic/no ack before a subflow is declared *stale*. Lower = faster active-backup switchover; higher = better utilization on lossy/high-BER links. |
+| **Max add address** | Cap on `ADD_ADDR` suboptions accepted per connection. |
+| **Control message timeout** | Resend delay for an unacknowledged `ADD_ADDR` message. |
+| **Blackhole timeout** *(kernel ≥6.18)* | Initial cooldown before re-enabling MPTCP on a socket after a middlebox appears to be blackholing it; grows exponentially on repeat failures. |
+| **Close timeout** *(kernel ≥6.18)* | Make-after-break grace period: how long a socket holds state after its last subflow drops, before moving to `TCP_CLOSE`. |
+| **Control message timeout** *(second one, kernel ≥6.18)* | SYN+MP_CAPABLE retransmit count before falling back to plain TCP. |
+
+When the userspace path manager is selected, extra fields appear
+(`mptcpd` enable/disable, its plugin and path-manager `.so` lists —
+auto-discovered from `/usr/lib/mptcpd` — and address announcement/
+notification flags); not shown here since this bench uses the in-kernel
+manager.
+
+### Interfaces Settings
+
+One block per real network interface (LAN + every WAN — VPN-internal
+interfaces like `omrvpn`/`omr6in4` are filtered out here since they aren't
+independent WAN paths):
+
+- **Multipath TCP** — `disabled`, `enabled`, `master`, or `backup`.
+ Exactly one interface must be `master` (the interface MPTCP's initial
+ subflow uses); the rest are `enabled` (or `backup`, only cut in when
+ masters/enabled links fail).
+- **Weight** — only used when a `*weight` scheduler/path-manager is
+ active; >100 makes a link more attractive, <100 less, up to 256.
+
+## Diagnostics
+
+```
+https:///cgi-bin/luci/admin/network/mptcp/mptcp_diagnostics
+```
+
+Answers *why isn't MPTCP aggregating my WANs correctly?* without requiring
+you to read raw `nstat`/`ip mptcp` output by hand. Combines the kernel's
+MPTCP MIB counters, the live endpoint/subflow state, and a per-WAN sanity
+check into a plain-English, color-coded issue list (fallback to plain TCP,
+blackholed/stale subflows, resets, checksum errors, JOIN handshake
+failures, dropped ADD_ADDR/RM_ADDR signalling), refreshed every 15 seconds.
+
+
+
+On this bench, the issue list actually fired: an orange banner flagging
+*"2644 MPTCP-level reset/fastclose event(s) sent by this router (~119/hour
+since boot)"* — with its own plain-English caveat underneath explaining
+that a high count sent *by the router itself* (as opposed to received) is
+usually just abortive closes from its own health-check/keepalive probes
+opening a connection, sending one request, then closing it, and is only
+worth escalating if paired with rising Blackhole/SubflowStale counters —
+which is exactly the kind of self-diagnosis this page exists to save you
+from having to work out by hand.
+
+A **Per-WAN MPTCP endpoint status** table cross-checks every
+multipath-enabled interface against `ip mptcp endpoint show`: a WAN can
+look perfectly healthy in OpenMPTCProuter's Status page (IP, gateway, ping
+all fine) and still never carry a subflow if its endpoint is missing —
+this is usually the fastest way to catch that.
+
+A **Live MPTCP subflows** table goes one level deeper than that: a live
+read of every established subflow (preferring `omr-sockdiag`, a small tool
+that queries the kernel directly over `NETLINK_SOCK_DIAG` — the same
+INET_DIAG protocol `ss` uses internally — and falling back to parsing
+`ss -tin` text if that tool isn't installed), one row per actual established
+subflow, attributed to its WAN by matching the connection's local address
+against the endpoint list above. Each row shows the local/remote
+address:port, whether the kernel is
+currently using it as an active or backup path, congestion window, RTT
+(smoothed/variance), retransmissions (current/total), and pacing/delivery
+rate — the same live TCP-level numbers `ss -i` would show for that
+connection, without needing to run it by hand over SSH. A WAN with a
+correctly-registered endpoint but no row here means the path manager
+just hasn't opened a subflow on it yet (or it's currently down/unreachable),
+which the endpoint table alone can't tell you.
+
+A **Kernel MPTCP
+configuration** block shows the live scheduler/path-manager/checksum/
+timeout values straight from `/proc/sys/net/mptcp/*`, and a sortable
+counters table plus collapsible raw `multipath -k/-f/-c` sections are
+available for anyone who wants the unprocessed data (the raw `-k`/`-f`/`-c`
+sections mirror **MPTCP monitoring**'s kernel-info line, **MPTCP
+Fullmesh**, and **Established connections** below).
+
+The counters table is genuinely cumulative since boot, unlike **MPTCP
+monitoring**'s raw `multipath -m` output below: that wraps `nstat -z`,
+which (without `-a`) reports the delta since the *previous* `nstat` call by
+anyone on the router — confirmed live, two calls a couple seconds apart
+came back ~0. A page auto-polling every 15s through that would show
+"since last poll" numbers while looking like a lifetime total, and would
+race with anything else on the box that also calls `nstat`. Diagnostics
+reads the kernel's own procfs SNMP-style counters directly instead
+(`/proc/net/netstat`'s `MPTcpExt:` block, or `/proc/net/mptcp_net/snmp` on
+older kernels), which has no such history-file/shared-state gotcha.
+
+Backed by a new method on the same `luci.mptcp` rpcd object as the other
+pages: `ubus call luci.mptcp diagnose '{}'` (no arguments).
+
+## DSCP / Weight Routing
+
+```
+https:///cgi-bin/luci/admin/network/mptcp/mptcp_dscp_routing
+```
+
+A per-WAN pin/weight table shared between two independent consumers —
+MPTCP's `bpf_dscp`/`bpf_weight*` schedulers (see the MPTCP settings page
+above) and `mqvpn`'s equivalent scheduler modes — so both can be
+configured from one place instead of duplicating the same WAN list twice.
+This page only edits the pins/weights themselves; which scheduler is
+actually *active* is still chosen on the MPTCP or MQVPN settings pages.
+
+Its content is entirely conditional: it shows the **DSCP class pins**
+table only if MPTCP's scheduler is `bpf_dscp` *or* mqvpn is enabled at
+all (mqvpn pushes its DSCP mask unconditionally whenever it's on, unlike
+weight below), and the **WAN weight** section only if MPTCP's scheduler
+is one of the `bpf_weight`/`bpf_weight_rr`/`bpf_burstweight` family *or*
+mqvpn's scheduler is `wrtt`/`wrr`. With neither condition met, the page
+just says so and renders nothing else.
+
+
+
+On this bench, MPTCP's scheduler is `blest` (neither DSCP nor weight) but
+mqvpn is enabled, so only the DSCP half shows:
+
+| Section | Fields |
+|---|---|
+| **Gateway sync** | **Mirror DSCP/weight pins to gateway** — also push each pin/weight to the VPS so it holds for download traffic too (the gateway's own send direction), not just what the router sends. Disabling it only stops *future* syncs; pins already pushed stay pushed. Its *saved* value (not the live checkbox) decides whether the Download column below even renders — toggling it needs Save & Apply and a reload before that column appears/disappears. |
+| **DSCP class pins** | One row per DSCP class → **Upload interface** (router → internet: pins the class to a WAN for `bpf_dscp`'s local map, and for mqvpn announces it on that path's DSCP mask via the control API) and, only while Gateway sync is on, **Download interface** (gateway → router; `bpf_dscp`-only, pins the VPS's own send-side choice — leave empty to just mirror the Upload interface instead). mqvpn has no separate download pin at all — its downlink always mirrors upload. |
+| **WAN weight** *(not shown here — no weight scheduler active)* | One row per multipath-enabled WAN, a single **Weight** value (default 100, max 256) used by both upload and download, for both MPTCP's `*weight` schedulers and mqvpn's Weighted RTT/Weighted Round Robin. |
+
+## Bandwidth
+
+```
+https:///cgi-bin/luci/admin/network/mptcp/bandwidth
+```
+
+Live, polled-every-second SVG traffic graphs. A tab per multipath-enabled
+interface, plus an **All interfaces** overview.
+
+
+
+The combined view overlays every WAN's inbound (and, separately, outbound)
+throughput plus a bold total line, with a live current/average/peak table
+underneath per interface and for the total.
+
+
+
+Selecting a WAN tab (e.g. `wan1`) narrows this to that link alone —
+inbound and outbound charts with their own current/average/peak stats,
+useful for judging one link's real-world throughput independent of the
+others.
+
+## Established connections
+
+```
+https:///cgi-bin/luci/admin/network/mptcp/mptcp_connections
+```
+
+
+
+Raw, auto-refreshing (every 10s) output of `multipath -c` — every ESTAB
+MPTCP connection with local and peer address:port. Useful for confirming
+traffic is actually using multiple subflows/paths, or for spotting
+connections stuck on a single WAN.
+
+## MPTCP Fullmesh
+
+```
+https:///cgi-bin/luci/admin/network/mptcp/mptcp_fullmesh
+```
+
+
+
+Raw output of `multipath -f`: the fullmesh path-manager's current subflow
+map — one line per local address, its subflow ID, and which device it
+rides on. This is the direct evidence that the fullmesh path-manager
+picked up every WAN address correctly; a WAN missing from this list isn't
+being used as an MPTCP subflow.
+
+## MPTCP monitoring
+
+```
+https:///cgi-bin/luci/admin/network/mptcp/mptcp_monitor
+```
+
+
+
+Raw output of `multipath -m`: the kernel's full `MPTcpExt*` counter dump
+(from `nstat`/`netstat -s`-style MIB counters) — join attempts/failures,
+retransmissions, checksum errors, address add/remove events, fallbacks to
+plain TCP, etc. All zero on a healthy, idle test link; non-zero values in
+categories like `MPTcpExtMPCapableFallbackSYNACK` or `MPTcpExtMPFailTx`
+are what you'd check first when MPTCP negotiation is suspected to be
+failing (e.g. a middlebox stripping MPTCP TCP options).
+
+## Not reachable from the menu: MPTCP Support Check
+
+The package also ships `mptcp/mptcp_check.js` — a page that runs a
+`tracebox`-based trace toward the configured VPS over a chosen interface,
+to show whether MPTCP options survive the path (look for
+`TCPOptionMPTCPCapable [...] Sender's Key` in the output: present means
+supported, a leading `-` means something on the path is stripping it).
+It calls the same `luci.mptcp` rpcd backend as the other pages
+(`mptcp_check_trace`, wrapping `tracebox -s omr-mptcp-trace.lua`).
+
+However, `luci-app-mptcp.json` has no menu entry for it, and LuCI's
+dispatcher falls back to the MPTCP settings page for its URL
+(`admin/network/mptcp/mptcp_check`) rather than routing to it — so as
+shipped, this page isn't actually reachable through the web UI. Worth
+knowing if you're debugging why MPTCP isn't negotiating: the check logic
+exists and works via the same rpcd method, it just currently has no way in
+from the menu.
diff --git a/luci-app-oaf/Makefile b/luci-app-oaf/Makefile
index f6b09f8a..0ee31021 100644
--- a/luci-app-oaf/Makefile
+++ b/luci-app-oaf/Makefile
@@ -1,15 +1,13 @@
-
-#
-# This is free software, licensed under the Apache License, Version 2.0 .
-#
-
include $(TOPDIR)/rules.mk
-LUCI_TITLE:=LuCI support for OAF
+LUCI_TITLE:=LuCI app for OAF
LUCI_PKGARCH:=all
-LUCI_DEPENDS:=+appfilter +kmod-oaf +luci-compat
+LUCI_DEPENDS:=+appfilter +kmod-oaf +luci-compat +luci-lib-jsonc
+
PKG_NAME:=luci-app-oaf
-PKG_VERSION:=6.1.4
-PKG_RELEASE:=3
+PKG_VERSION:=7.0
+PKG_RELEASE:=4
+
include $(TOPDIR)/feeds/luci/luci.mk
+
# call BuildPackage - OpenWrt buildroot signature
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10003.png
deleted file mode 100755
index 81f76f4f..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10004.png
deleted file mode 100755
index e3447dd1..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10005.png
deleted file mode 100755
index b208cea6..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10006.png
deleted file mode 100755
index e9e9c4e5..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10007.png
deleted file mode 100755
index 9d8923b2..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10008.png
deleted file mode 100755
index a37cc9be..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10010.png
deleted file mode 100755
index dd251642..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10011.png
deleted file mode 100755
index 6bbdc0e8..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10011.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10012.png
deleted file mode 100755
index 23046e6d..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10013.png
deleted file mode 100755
index cbdc7d08..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10013.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10014.png
deleted file mode 100755
index 27541b57..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10014.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10015.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10015.png
deleted file mode 100755
index ebefaf5a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10015.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10016.png
deleted file mode 100755
index 69a2c969..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10016.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10017.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10017.png
deleted file mode 100755
index 8cb17a18..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10017.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10018.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10018.png
deleted file mode 100755
index 41a1d52e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10018.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10019.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10019.png
deleted file mode 100755
index 37a93217..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10019.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10020.png
deleted file mode 100755
index 5dce396e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10020.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10021.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10021.png
deleted file mode 100755
index d4c79dcf..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10021.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10022.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10022.png
deleted file mode 100755
index 6bc4e323..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10022.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1003.png
deleted file mode 100755
index ced03216..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10034.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10034.png
deleted file mode 100755
index 4825ca47..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10034.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10035.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/10035.png
deleted file mode 100755
index 07e56e14..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/10035.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1004.png
deleted file mode 100755
index 6d73e955..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1005.png
deleted file mode 100755
index 75e702e9..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1006.png
deleted file mode 100755
index 83430bdb..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1007.png
deleted file mode 100755
index 6ff6bbcd..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1008.png
deleted file mode 100755
index 77283307..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1009.png
deleted file mode 100755
index 9b6c8e53..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1010.png
deleted file mode 100755
index 0e4e232d..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1012.png
deleted file mode 100755
index 526b2064..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1013.png
deleted file mode 100755
index 8b30ba1b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1013.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/1014.png
deleted file mode 100755
index 9b068599..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/1014.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/11001.png
deleted file mode 100755
index 74060db5..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/11002.png
deleted file mode 100755
index a8d35fd0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11002.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/11003.png
deleted file mode 100755
index 090bb5f1..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/11003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14001.png
deleted file mode 100755
index 7f8e532e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14002.png
deleted file mode 100755
index 67a04248..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14002.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14003.png
deleted file mode 100755
index 6a790fe8..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14004.png
deleted file mode 100755
index 4c374faf..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14005.png
deleted file mode 100755
index f92f89f7..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14006.png
deleted file mode 100755
index 7cb1b8d2..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14007.png
deleted file mode 100755
index b9056dc9..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14008.png
deleted file mode 100755
index 7a3b67ea..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14009.png
deleted file mode 100755
index 0e6cd8e4..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14010.png
deleted file mode 100755
index 8b54042c..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14011.png
deleted file mode 100755
index b61b31ef..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14011.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14012.png
deleted file mode 100755
index 64f342b0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14013.png
deleted file mode 100755
index 29d301b0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14013.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/14014.png
deleted file mode 100755
index c283c48b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/14014.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2001.png
deleted file mode 100755
index 5aedcefa..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2005.png
deleted file mode 100755
index 1aed8064..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2006.png
deleted file mode 100755
index fa6ebaa7..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2007.png
deleted file mode 100755
index 924a5a22..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2008.png
deleted file mode 100755
index d5e1fd4c..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2009.png
deleted file mode 100755
index 11da6624..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2010.png
deleted file mode 100755
index f86222ab..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2011.png
deleted file mode 100755
index 2ea2bd4e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2011.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2012.png
deleted file mode 100755
index dc012b16..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2013.png
deleted file mode 100755
index 8526f285..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2013.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2014.png
deleted file mode 100755
index 0f2a8ed4..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2014.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2015.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2015.png
deleted file mode 100755
index 97a9c5c0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2015.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2016.png
deleted file mode 100755
index 5b3e18d3..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2016.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2017.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2017.png
deleted file mode 100755
index d4db13fb..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2017.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2023.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2023.png
deleted file mode 100755
index 14626ce0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2023.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2025.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2025.png
deleted file mode 100755
index 5d8b5f81..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2025.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2026.png
deleted file mode 100755
index e7027432..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2026.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2027.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2027.png
deleted file mode 100755
index 7e907bb4..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2027.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2033.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2033.png
deleted file mode 100755
index a419e2b1..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2033.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2034.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2034.png
deleted file mode 100755
index 7863637a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2034.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2040.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2040.png
deleted file mode 100755
index 59dc8fbf..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2040.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2041.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2041.png
deleted file mode 100755
index 9eeb0bca..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2041.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2042.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2042.png
deleted file mode 100755
index a102313b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2042.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2050.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2050.png
deleted file mode 100755
index e75d5adb..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2050.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2051.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2051.png
deleted file mode 100755
index 1ce8fbd1..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2051.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2067.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2067.png
deleted file mode 100755
index 5a9647e4..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2067.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2068.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2068.png
deleted file mode 100755
index 3e3ed9ea..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2068.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2069.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2069.png
deleted file mode 100755
index 1ab35931..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2069.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2070.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2070.png
deleted file mode 100755
index 96cb6fe3..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2070.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2071.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2071.png
deleted file mode 100755
index 419765fb..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2071.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2074.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2074.png
deleted file mode 100755
index 479549ab..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2074.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2075.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2075.png
deleted file mode 100755
index c73c4d84..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2075.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2080.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/2080.png
deleted file mode 100755
index d5b4536a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/2080.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3001.png
deleted file mode 100755
index bc5c054f..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3004.png
deleted file mode 100755
index 2a87819f..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3006.png
deleted file mode 100755
index 5b0eb2bf..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3008.png
deleted file mode 100755
index d546b752..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3010.png
deleted file mode 100755
index a1d17050..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3011.png
deleted file mode 100755
index 8c0f1b74..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3011.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3012.png
deleted file mode 100755
index bf399d46..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3016.png
deleted file mode 100755
index 7ffb2630..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3016.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3017.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3017.png
deleted file mode 100755
index d7888ba5..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3017.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3018.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3018.png
deleted file mode 100755
index 9f216f0a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3018.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3019.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3019.png
deleted file mode 100755
index 523ec84b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3019.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3020.png
deleted file mode 100755
index 3e67dff7..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3020.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3021.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3021.png
deleted file mode 100755
index e1bb4642..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3021.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3022.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3022.png
deleted file mode 100755
index 47d311e8..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3022.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3023.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3023.png
deleted file mode 100755
index e1262611..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3023.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3024.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3024.png
deleted file mode 100755
index 15791e61..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3024.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3025.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3025.png
deleted file mode 100755
index 6c35b893..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3025.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3026.png
deleted file mode 100755
index a0f25595..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3026.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3027.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3027.png
deleted file mode 100755
index 92f98764..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3027.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3028.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3028.png
deleted file mode 100755
index 1ef08a86..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3028.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3029.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3029.png
deleted file mode 100755
index 630e7765..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3029.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3030.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3030.png
deleted file mode 100755
index 630e7765..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3030.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3043.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3043.png
deleted file mode 100755
index a8aed22b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3043.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3049.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3049.png
deleted file mode 100755
index 8647fba9..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3049.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3084.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3084.png
deleted file mode 100755
index 694a38f3..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3084.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3085.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3085.png
deleted file mode 100755
index b2df1e17..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3085.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3086.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3086.png
deleted file mode 100755
index 2acf4a4a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3086.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3089.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3089.png
deleted file mode 100755
index 5db0102a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3089.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3094.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3094.png
deleted file mode 100755
index f7799c9e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3094.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3121.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/3121.png
deleted file mode 100755
index a243e5c9..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/3121.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4001.png
deleted file mode 100755
index dfabf0f0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4002.png
deleted file mode 100755
index 51f88a1c..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4002.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4003.png
deleted file mode 100755
index c19cbd87..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4004.png
deleted file mode 100755
index c8c87c63..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4005.png
deleted file mode 100755
index 2a983244..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4006.png
deleted file mode 100755
index ad6f7e6d..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4007.png
deleted file mode 100755
index 350b7515..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4008.png
deleted file mode 100755
index 951310ff..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4009.png
deleted file mode 100755
index 3bff5319..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4010.png
deleted file mode 100755
index 89be88cb..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4012.png
deleted file mode 100755
index 19aaee6f..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4013.png
deleted file mode 100755
index b3c53808..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4013.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4014.png
deleted file mode 100755
index e3cd7859..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4014.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4015.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4015.png
deleted file mode 100755
index 00ec2e05..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4015.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4016.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4016.png
deleted file mode 100755
index edee7bfe..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4016.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4018.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4018.png
deleted file mode 100755
index 9a06f47a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4018.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4019.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4019.png
deleted file mode 100755
index f573b6e9..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4019.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4021.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4021.png
deleted file mode 100755
index c4e5b369..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4021.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4022.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4022.png
deleted file mode 100755
index 19640f9a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4022.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4023.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4023.png
deleted file mode 100755
index 45478b31..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4023.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4024.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4024.png
deleted file mode 100755
index 912813d5..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4024.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4025.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4025.png
deleted file mode 100755
index b7257ebd..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4025.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4026.png
deleted file mode 100755
index 49e49998..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4026.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4040.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4040.png
deleted file mode 100755
index 4ac3afed..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4040.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4041.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4041.png
deleted file mode 100755
index 18355f22..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4041.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4052.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4052.png
deleted file mode 100755
index b7729e77..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4052.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4053.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4053.png
deleted file mode 100755
index d65070f0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4053.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4054.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/4054.png
deleted file mode 100755
index 33d5fe53..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/4054.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5001.png
deleted file mode 100755
index 8d9bcdc4..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5002.png
deleted file mode 100755
index b3d60616..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5002.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5003.png
deleted file mode 100755
index 630e7765..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5004.png
deleted file mode 100755
index f2bdb15b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5005.png
deleted file mode 100755
index 7b6f20ce..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5006.png
deleted file mode 100755
index 12149c1e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5007.png
deleted file mode 100755
index 61837fa3..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5008.png
deleted file mode 100755
index 20fe9c14..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5009.png
deleted file mode 100755
index 26e95d30..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5010.png
deleted file mode 100755
index b1f7d4ae..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5011.png
deleted file mode 100755
index fc3cee01..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5011.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/5012.png
deleted file mode 100755
index 662ed392..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/5012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6001.png
deleted file mode 100755
index bf81ed94..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6002.png
deleted file mode 100755
index 48b4bd3b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6002.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6003.png
deleted file mode 100755
index f3e14a00..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6004.png
deleted file mode 100755
index a4a1916b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6005.png
deleted file mode 100755
index a1b4de7d..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6006.png
deleted file mode 100755
index a286157b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6007.png
deleted file mode 100755
index 08e6f737..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6008.png
deleted file mode 100755
index 2a3eab41..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6009.png
deleted file mode 100755
index 81a10ed5..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6010.png
deleted file mode 100755
index d2d7c413..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6011.png
deleted file mode 100755
index e5699059..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6011.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6012.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6012.png
deleted file mode 100755
index 185bb04b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6012.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6013.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6013.png
deleted file mode 100755
index 07280c32..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6013.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6014.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/6014.png
deleted file mode 100755
index 417f3031..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/6014.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7002.png
deleted file mode 100755
index 3ecd6146..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7002.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7004.png
deleted file mode 100755
index a8d35fd0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7005.png
deleted file mode 100755
index 6607839d..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7006.png
deleted file mode 100755
index 5aedcefa..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7007.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7007.png
deleted file mode 100755
index 8253fd84..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7007.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7008.png
deleted file mode 100755
index 421a9f0a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7009.png
deleted file mode 100755
index 87bcebc8..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7010.png
deleted file mode 100755
index d98d56aa..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7011.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7011.png
deleted file mode 100755
index d62cb630..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7011.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7020.png
deleted file mode 100755
index 8e899f28..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7020.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7030.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7030.png
deleted file mode 100755
index 9152277b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7030.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7031.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7031.png
deleted file mode 100755
index 3d611605..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7031.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7032.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/7032.png
deleted file mode 100755
index d2c8bc55..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/7032.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8001.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8001.png
deleted file mode 100755
index b2df1e17..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8001.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8002.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8002.png
deleted file mode 100755
index 51193da3..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8002.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8003.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8003.png
deleted file mode 100755
index bc90b67c..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8003.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8004.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8004.png
deleted file mode 100755
index b0c4142e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8004.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8005.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8005.png
deleted file mode 100755
index 6fd95f73..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8005.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8006.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8006.png
deleted file mode 100755
index 4c217b2b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8006.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8008.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8008.png
deleted file mode 100755
index f3e3a72e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8008.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8009.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8009.png
deleted file mode 100755
index 479549ab..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8009.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8010.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8010.png
deleted file mode 100755
index ee01aad9..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8010.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8020.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8020.png
deleted file mode 100755
index 5d7bc0a1..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8020.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8026.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8026.png
deleted file mode 100755
index a16637e5..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8026.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8027.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8027.png
deleted file mode 100755
index 9333d453..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8027.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8029.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8029.png
deleted file mode 100755
index 5bc1c777..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8029.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8030.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8030.png
deleted file mode 100755
index fd08701c..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8030.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8031.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8031.png
deleted file mode 100755
index ed3af349..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8031.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8035.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8035.png
deleted file mode 100755
index cdf5d235..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8035.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8036.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8036.png
deleted file mode 100755
index 2cb0c32b..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8036.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8037.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8037.png
deleted file mode 100755
index c1b8dc8e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8037.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8038.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8038.png
deleted file mode 100755
index f1dff01f..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8038.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8039.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8039.png
deleted file mode 100755
index cda7743a..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8039.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8041.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8041.png
deleted file mode 100755
index e67c0fce..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8041.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8044.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8044.png
deleted file mode 100755
index ff615ca0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8044.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8046.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8046.png
deleted file mode 100755
index 75537572..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8046.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8064.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8064.png
deleted file mode 100755
index 33483b65..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8064.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8065.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8065.png
deleted file mode 100755
index 4f336852..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8065.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8066.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8066.png
deleted file mode 100755
index 14542a81..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8066.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8077.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8077.png
deleted file mode 100755
index 1bb8c086..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8077.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8079.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8079.png
deleted file mode 100755
index 8d1f6d40..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8079.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8087.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8087.png
deleted file mode 100755
index f16c0de3..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8087.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8089.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8089.png
deleted file mode 100755
index 03454029..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8089.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8090.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8090.png
deleted file mode 100755
index c46f0806..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8090.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8092.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8092.png
deleted file mode 100755
index 7ea6544d..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8092.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8093.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8093.png
deleted file mode 100755
index ddced493..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8093.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8096.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8096.png
deleted file mode 100755
index 420dcd84..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8096.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8098.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8098.png
deleted file mode 100755
index 52ee3bc0..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8098.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8099.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8099.png
deleted file mode 100755
index 08156734..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8099.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8100.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8100.png
deleted file mode 100755
index dd251642..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8100.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8103.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8103.png
deleted file mode 100755
index 83402263..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8103.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8104.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8104.png
deleted file mode 100755
index 8004eb2c..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8104.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8105.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8105.png
deleted file mode 100755
index ba7f02dd..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8105.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8106.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8106.png
deleted file mode 100755
index 11a0790f..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8106.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8107.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8107.png
deleted file mode 100755
index 854b57c3..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8107.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8108.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8108.png
deleted file mode 100755
index a1265241..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8108.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8110.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8110.png
deleted file mode 100755
index 1b6a493e..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8110.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8111.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8111.png
deleted file mode 100755
index 7be639c8..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8111.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8112.png b/luci-app-oaf/htdocs/luci-static/resources/app_icons/8112.png
deleted file mode 100755
index 90c29ab5..00000000
Binary files a/luci-app-oaf/htdocs/luci-static/resources/app_icons/8112.png and /dev/null differ
diff --git a/luci-app-oaf/htdocs/luci-static/resources/css/common.css b/luci-app-oaf/htdocs/luci-static/resources/css/common.css
deleted file mode 100644
index 5929e8a7..00000000
--- a/luci-app-oaf/htdocs/luci-static/resources/css/common.css
+++ /dev/null
@@ -1,83 +0,0 @@
-
-body {
- font-family: Arial, sans-serif;
-}
-h1, h2 {
- color: #333;
-}
-
-
-
-.button-check {
- padding: 3px 8px;
- background-color: transparent;
- color: green;
- border: 1px solid green;
- border-radius: 3px;
- cursor: pointer;
- font-size: 12px;
-}
-.button-container {
- display: flex;
- justify-content: right;
- margin-top: 5px;
-}
-
-.submit-button {
- margin-top: 30px;
- width: 150px;
- height: 30px;
- background-color: #2885e8;
- color: white;
- border: none;
- border-radius: 5px;
- cursor: pointer;
-}
-.submit-button:hover {
- background-color: #0f77e6;
-}
-
-.switch {
- position: relative;
- display: inline-block;
- width: 40px;
- height: 20px;
-}
-
-.switch input {
- opacity: 0;
- width: 0;
- height: 0;
-}
-
-.slider {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: #ccc;
- transition: .4s;
- border-radius: 20px;
-}
-
-.slider:before {
- position: absolute;
- content: "";
- height: 16px;
- width: 16px;
- left: 2px;
- bottom: 2px;
- background-color: white;
- transition: .4s;
- border-radius: 50%;
-}
-
-input:checked + .slider {
- background-color: #2196F3;
-}
-
-input:checked + .slider:before {
- transform: translateX(20px);
-}
\ No newline at end of file
diff --git a/luci-app-oaf/htdocs/luci-static/resources/app_icons/default.png b/luci-app-oaf/htdocs/luci-static/resources/oaf/app_icons/default.png
old mode 100755
new mode 100644
similarity index 100%
rename from luci-app-oaf/htdocs/luci-static/resources/app_icons/default.png
rename to luci-app-oaf/htdocs/luci-static/resources/oaf/app_icons/default.png
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/common.js b/luci-app-oaf/htdocs/luci-static/resources/oaf/common.js
new file mode 100644
index 00000000..5b07e803
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/common.js
@@ -0,0 +1,207 @@
+function showCommonModal(message, duration = 2000, type = 'info') {
+ const existingModal = document.querySelector('.common-modal-mask');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ const modalMask = document.createElement('div');
+ modalMask.className = 'common-modal-mask';
+
+ const modalContent = document.createElement('div');
+ modalContent.className = 'common-modal-content';
+
+ modalContent.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
+ modalContent.style.color = 'white';
+
+ modalContent.textContent = message;
+
+ modalMask.appendChild(modalContent);
+ document.body.appendChild(modalMask);
+
+ setTimeout(() => {
+ modalMask.classList.add('show');
+ }, 10);
+
+ setTimeout(() => {
+ modalMask.classList.remove('show');
+ setTimeout(() => {
+ if (modalMask.parentNode) {
+ modalMask.remove();
+ }
+ }, 300);
+ }, duration);
+}
+
+function showSuccess(message, duration = 2000) {
+ showCommonModal(message, duration);
+}
+
+function showWarning(message, duration = 2000) {
+ showCommonModal(message, duration);
+}
+
+function showError(message, duration = 3000) {
+ showCommonModal(message, duration);
+}
+
+function showInfo(message, duration = 2000) {
+ showCommonModal(message, duration);
+}
+
+function showConfirmModal(message, onConfirm, onCancel, confirmText = '确定', cancelText = '取消') {
+ const existingModal = document.querySelector('.common-modal-mask');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ const modalMask = document.createElement('div');
+ modalMask.className = 'common-modal-mask';
+
+ const modalContent = document.createElement('div');
+ modalContent.className = 'common-modal-content';
+ modalContent.style.width = '300px';
+ modalContent.style.height = 'auto';
+ modalContent.style.minHeight = '120px';
+ modalContent.style.padding = '20px';
+ modalContent.style.flexDirection = 'column';
+ modalContent.style.justifyContent = 'space-between';
+ modalContent.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
+ modalContent.style.color = 'white';
+
+ const messageDiv = document.createElement('div');
+ messageDiv.textContent = message;
+ messageDiv.style.marginBottom = '20px';
+ messageDiv.style.textAlign = 'center';
+
+ const buttonContainer = document.createElement('div');
+ buttonContainer.style.display = 'flex';
+ buttonContainer.style.justifyContent = 'center';
+ buttonContainer.style.gap = '10px';
+
+ const confirmBtn = document.createElement('button');
+ confirmBtn.textContent = confirmText;
+ confirmBtn.style.padding = '8px 16px';
+ confirmBtn.style.backgroundColor = '#2885e8';
+ confirmBtn.style.color = 'white';
+ confirmBtn.style.border = 'none';
+ confirmBtn.style.borderRadius = '4px';
+ confirmBtn.style.cursor = 'pointer';
+ confirmBtn.onclick = () => {
+ modalMask.remove();
+ if (onConfirm) onConfirm();
+ };
+
+ const cancelBtn = document.createElement('button');
+ cancelBtn.textContent = cancelText;
+ cancelBtn.style.padding = '8px 16px';
+ cancelBtn.style.backgroundColor = '#666';
+ cancelBtn.style.color = 'white';
+ cancelBtn.style.border = 'none';
+ cancelBtn.style.borderRadius = '4px';
+ cancelBtn.style.cursor = 'pointer';
+ cancelBtn.onclick = () => {
+ modalMask.remove();
+ if (onCancel) onCancel();
+ };
+
+ buttonContainer.appendChild(confirmBtn);
+ buttonContainer.appendChild(cancelBtn);
+
+ modalContent.appendChild(messageDiv);
+ modalContent.appendChild(buttonContainer);
+ modalMask.appendChild(modalContent);
+ document.body.appendChild(modalMask);
+
+ setTimeout(() => {
+ modalMask.classList.add('show');
+ }, 10);
+}
+
+function formatFileSize(bytes) {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+}
+
+function debounce(func, wait) {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+}
+
+function throttle(func, limit) {
+ let inThrottle;
+ return function() {
+ const args = arguments;
+ const context = this;
+ if (!inThrottle) {
+ func.apply(context, args);
+ inThrottle = true;
+ setTimeout(() => inThrottle = false, limit);
+ }
+ };
+}
+
+function getUrlParameter(name) {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get(name);
+}
+
+function setUrlParameter(name, value) {
+ const url = new URL(window.location);
+ url.searchParams.set(name, value);
+ window.history.replaceState({}, '', url);
+}
+
+function deepClone(obj) {
+ if (obj === null || typeof obj !== 'object') return obj;
+ if (obj instanceof Date) return new Date(obj.getTime());
+ if (obj instanceof Array) return obj.map(item => deepClone(item));
+ if (typeof obj === 'object') {
+ const clonedObj = {};
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ clonedObj[key] = deepClone(obj[key]);
+ }
+ }
+ return clonedObj;
+ }
+}
+
+function validateIP(ip) {
+ if (!ip || ip.trim() === '') return true;
+ const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+ return ipRegex.test(ip.trim());
+}
+
+function validateNetmask(mask) {
+ if (!mask || mask.trim() === '') return true;
+ const maskRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
+ if (!maskRegex.test(mask.trim())) return false;
+ const parts = mask.split('.');
+ const binary = parts.map(p => parseInt(p).toString(2).padStart(8, '0')).join('');
+ return /^1+0*$/.test(binary);
+}
+
+window.showCommonModal = showCommonModal;
+window.showSuccess = showSuccess;
+window.showWarning = showWarning;
+window.showError = showError;
+window.showInfo = showInfo;
+window.showConfirmModal = showConfirmModal;
+window.formatFileSize = formatFileSize;
+window.debounce = debounce;
+window.throttle = throttle;
+window.getUrlParameter = getUrlParameter;
+window.setUrlParameter = setUrlParameter;
+window.deepClone = deepClone;
+window.validateIP = validateIP;
+window.validateNetmask = validateNetmask;
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/css/common.css b/luci-app-oaf/htdocs/luci-static/resources/oaf/css/common.css
new file mode 100644
index 00000000..c5b7421f
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/css/common.css
@@ -0,0 +1,269 @@
+
+body {
+ font-family: Arial, sans-serif;
+}
+h1, h2 {
+ color: #333;
+}
+
+
+
+.button-check {
+ padding: 3px 8px;
+ background-color: transparent;
+ color: green;
+ border: 1px solid green;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 12px;
+}
+.button-container {
+ display: flex;
+ justify-content: right;
+ margin-top: 5px;
+}
+
+.submit-button {
+ margin-top: 30px;
+ width: 150px;
+ height: 30px;
+ background-color: #2885e8;
+ color: #fff;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+}
+.submit-button:hover {
+ background-color: #0f77e6;
+}
+
+.switch {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ height: 20px;
+}
+
+.switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: #ccc;
+ transition: .4s;
+ border-radius: 20px;
+}
+
+.slider:before {
+ position: absolute;
+ content: "";
+ height: 16px;
+ width: 16px;
+ left: 2px;
+ bottom: 2px;
+ background-color: #fff;
+ transition: .4s;
+ border-radius: 50%;
+}
+
+input:checked + .slider {
+ background-color: #2196F3;
+}
+
+input:checked + .slider:before {
+ left: 22px;
+}
+
+.fwx-form-card {
+ max-width: 780px;
+ margin: 0 auto 16px auto;
+ padding: 16px;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ background: transparent;
+ box-shadow: none;
+}
+
+.form-title {
+ font-size: 18px;
+ font-weight: 600;
+ margin-bottom: 8px;
+ display: flex;
+ align-items: center;
+}
+
+.form-desc {
+ color: #6b7280;
+ font-size: 13px;
+ margin-bottom: 12px;
+}
+
+[data-darkmode="true"] .form-desc {
+ color:#c0c0c0;
+}
+
+.oaf-check-item {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ min-width: 0;
+ line-height: 1.4;
+ white-space: nowrap;
+}
+
+.oaf-check-item.oaf-check-item > input[type="checkbox"] {
+ margin: 0;
+ flex: 0 0 auto;
+}
+
+.oaf-check-item.oaf-check-item > label {
+ display: inline-flex;
+ align-items: center;
+ margin: 0;
+ min-width: 0;
+ line-height: 1.4;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.oaf-check-item.oaf-check-item > label span {
+ min-width: 0;
+ white-space: nowrap;
+}
+
+.common-modal-mask {
+ display: none;
+ position: fixed;
+ top: 0; left: 0;
+ width: 100%; height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ justify-content: center;
+ align-items: center;
+}
+.common-modal-mask.show {
+ display: flex;
+}
+.common-modal-content {
+ background-color: rgba(0, 0, 0, 0.5);
+ padding: 10px;
+ border-radius: 5px;
+ text-align: center;
+ width: 100px;
+ height: 70px;
+ color: #fff;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+/* Common table styles */
+.common-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.common-table th,
+.common-table td {
+ padding: 8px;
+ border: 1px solid;
+ text-align: left;
+}
+
+.common-table th {
+ font-weight: bold;
+}
+
+.table-header {
+ display: grid;
+ padding: 10px;
+ font-weight: bold;
+ border: 1px solid;
+ border-bottom: none;
+}
+
+.table-row {
+ display: grid;
+ padding: 10px;
+ border: 1px solid;
+ border-top: none;
+ align-items: center;
+}
+
+
+/* Table title style */
+.table-title {
+ font-size: 18px;
+ font-weight: bold;
+ margin-bottom: 20px;
+}
+
+/* Common button styles */
+.btn-delete {
+ padding: 3px 8px;
+ background-color: #ff4444;
+ color: #fff;
+ border: none;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 13px;
+}
+
+.btn-delete:hover {
+ background-color: #cc0000;
+}
+
+.btn-edit {
+ padding: 3px 8px;
+ background-color: #007bff;
+ color: #fff;
+ border: none;
+ border-radius: 3px;
+ cursor: pointer;
+ margin-right: 5px;
+ font-size: 13px;
+}
+
+.btn-edit:hover {
+ background-color: #0056b3;
+}
+
+.btn-add {
+ padding: 5px 15px;
+ background-color: #2885e8;
+ color: #fff;
+ border: none;
+ border-radius: 3px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.btn-add:hover {
+ background-color: #1e6bb8;
+}
+
+
+
+.form-row { display: flex; align-items: center; margin-bottom: 10px; gap: 10px; }
+.form-label { width: 140px; font-weight: 500; color: inherit; }
+.form-row input { width: 360px; max-width: 100%; padding: 6px 10px; border: 1px solid #d1d5db; border-radius: 4px; background: inherit; color: inherit; }
+.form-row select { width: 360px; max-width: 100%; }
+.form-text-row { margin-bottom: 10px; font-size: 13px; color: #6b7280; }
+.input-short { width: 200px !important; max-width: 100%; }
+
+
+.btn { padding: 2px 25px; border: 1px solid #10b981; background: #10b981; color: #fff; border-radius: 4px; cursor: pointer; font-weight: 600; }
+.btn:disabled { opacity: .5; cursor: not-allowed; }
+.btn-secondary { background: transparent; color: #10b981; padding: 2px 25px; }
+.section-header { font-weight: 600; margin: 12px 0 6px; color: inherit; }
+.row-inline { display: flex; gap: 10px; }
+.row-inline .form-row { flex: 1; }
+.status-tip { margin-top: 8px; font-size: 13px; color: #6b7280; }
diff --git a/luci-app-oaf/htdocs/luci-static/resources/echarts.min.js b/luci-app-oaf/htdocs/luci-static/resources/oaf/echarts.min.js
similarity index 100%
rename from luci-app-oaf/htdocs/luci-static/resources/echarts.min.js
rename to luci-app-oaf/htdocs/luci-static/resources/oaf/echarts.min.js
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/application.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/application.svg
new file mode 100644
index 00000000..c4978870
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/application.svg
@@ -0,0 +1 @@
+
diff --git a/luci-app-oaf/htdocs/luci-static/resources/icons/arrow.png b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/arrow.png
similarity index 100%
rename from luci-app-oaf/htdocs/luci-static/resources/icons/arrow.png
rename to luci-app-oaf/htdocs/luci-static/resources/oaf/icons/arrow.png
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/ethernet-port.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/ethernet-port.svg
new file mode 100644
index 00000000..09ddd431
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/ethernet-port.svg
@@ -0,0 +1,16 @@
+
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/filter.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/filter.svg
new file mode 100644
index 00000000..e613e458
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/filter.svg
@@ -0,0 +1 @@
+
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/flow.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/flow.svg
new file mode 100644
index 00000000..17877d21
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/flow.svg
@@ -0,0 +1 @@
+
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/sphere.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/sphere.svg
new file mode 100644
index 00000000..b89ed9d1
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/sphere.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/user.svg b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/user.svg
new file mode 100644
index 00000000..73a01cfe
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/icons/user.svg
@@ -0,0 +1 @@
+
diff --git a/luci-app-oaf/htdocs/luci-static/resources/oaf/jquery-3.7.1.min.js b/luci-app-oaf/htdocs/luci-static/resources/oaf/jquery-3.7.1.min.js
new file mode 100644
index 00000000..7f37b5d9
--- /dev/null
+++ b/luci-app-oaf/htdocs/luci-static/resources/oaf/jquery-3.7.1.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"
","
"],col:[2,"
","
"],tr:[2,"
","
"],td:[3,"
","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0')
+ .addClass(options.className || 'oaf-generated-app-icon')
+ .css({
+ width: options.size || '20px',
+ height: options.size || '20px',
+ borderRadius: options.radius || '5px',
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ background: hashColor(name),
+ color: '#fff',
+ fontSize: options.fontSize || '11px',
+ fontWeight: '700',
+ flexShrink: 0,
+ lineHeight: 1
+ })
+ .text(firstLetter(name));
+ }
+
+ function createAppIcon(appId, name, resourceBase, options) {
+ options = options || {};
+ var appName = name || '';
+ var id = appId === undefined || appId === null ? '' : String(appId).trim();
+ var src = appIconSrc(resourceBase, id);
+ var iconDisabled = options.icon === 0 || options.icon === '0' || options.hasIcon === false;
+ var $icon;
+
+ if (id && iconDisabled) {
+ iconStatusCache[id] = 'failed';
+ }
+
+ if (id && !iconDisabled && iconStatusCache[id] === 'loaded') {
+ return $('')
+ .attr({ src: src, alt: appName })
+ .css({
+ width: options.size || '20px',
+ height: options.size || '20px',
+ borderRadius: options.radius || '5px',
+ objectFit: options.objectFit || 'cover',
+ display: 'block',
+ flexShrink: 0
+ });
+ }
+
+ $icon = createLetterIcon(appName, options);
+ if (id && !iconDisabled && iconStatusCache[id] !== 'failed') {
+ var loader = new Image();
+ loader.onload = function() {
+ iconStatusCache[id] = 'loaded';
+ $icon.replaceWith($('')
+ .attr({ src: src, alt: appName })
+ .css({
+ width: options.size || '20px',
+ height: options.size || '20px',
+ borderRadius: options.radius || '5px',
+ objectFit: options.objectFit || 'cover',
+ display: 'block',
+ flexShrink: 0,
+ border: 'none',
+ boxShadow: 'none'
+ }));
+ };
+ loader.onerror = function() {
+ iconStatusCache[id] = 'failed';
+ };
+ loader.src = src;
+ }
+
+ return $icon;
+ }
+
+ window.OAFIcon = {
+ colors: iconColors,
+ hashColor: hashColor,
+ appIconSrc: appIconSrc,
+ createLetterIcon: createLetterIcon,
+ createAppIcon: createAppIcon
+ };
+})(window, window.jQuery);
diff --git a/luci-app-oaf/luasrc/controller/appfilter.lua b/luci-app-oaf/luasrc/controller/appfilter.lua
deleted file mode 100644
index c4a59062..00000000
--- a/luci-app-oaf/luasrc/controller/appfilter.lua
+++ /dev/null
@@ -1,573 +0,0 @@
-module("luci.controller.appfilter", package.seeall)
-local utl = require "luci.util"
-
-function index()
- if not nixio.fs.access("/etc/config/appfilter") then
- return
- end
-
- local page
- entry({"admin", "services", "appfilter"}, alias("admin", "services", "appfilter", "user_list"),_("App Filter"), 10).dependent = true
-
-
- entry({"admin", "services", "appfilter", "user_list"},
- arcombine(cbi("appfilter/user_list",{hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),
- cbi("appfilter/dev_status", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true})),
- _("User List"), 20).leaf=true
-
- entry({"admin", "services", "appfilter", "time"}, cbi("appfilter/time", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Time Configuration"), 25).leaf=true
- entry({"admin", "services", "appfilter", "app_filter"}, cbi("appfilter/app_filter", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("App Filter"), 21).leaf=true
- entry({"admin", "services", "appfilter", "feature"}, cbi("appfilter/feature", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("App Feature Library"), 26).leaf=true
-
- entry({"admin", "services", "appfilter", "user"}, cbi("appfilter/user", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("User Configuration"), 24).leaf=true
- entry({"admin", "services", "appfilter", "advance"}, cbi("appfilter/advance", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Advanced Settings"), 27).leaf=true
- entry({"admin", "network", "user_status"}, call("user_status"), nil).leaf = true
- entry({"admin", "network", "get_user_list"}, call("get_user_list"), nil).leaf = true
- entry({"admin", "network", "dev_visit_list"}, call("get_dev_visit_list"), nil).leaf = true
- entry({"admin", "network", "feature_upgrade"}, call("handle_feature_upgrade"), nil).leaf = true
- entry({"admin", "network", "dev_visit_time"}, call("get_dev_visit_time"), nil).leaf = true
- entry({"admin", "network", "app_class_visit_time"}, call("get_app_class_visit_time"), nil).leaf = true
- entry({"admin", "network", "class_list"}, call("get_class_list"), nil).leaf = true
- entry({"admin", "network", "set_app_filter"}, call("set_app_filter"), nil).leaf = true
- entry({"admin", "network", "get_app_filter"}, call("get_app_filter"), nil).leaf = true
- entry({"admin", "network", "get_app_filter_base"}, call("get_app_filter_base"), nil).leaf = true
- entry({"admin", "network", "set_app_filter_base"}, call("set_app_filter_base"), nil).leaf = true
- entry({"admin", "network", "set_app_filter_time"}, call("set_app_filter_time"), nil).leaf = true
- entry({"admin", "network", "get_app_filter_time"}, call("get_app_filter_time"), nil).leaf = true
- entry({"admin", "network", "get_all_users"}, call("get_all_users"), nil).leaf = true
- entry({"admin", "network", "get_app_filter_user"}, call("get_app_filter_user"), nil).leaf = true
- entry({"admin", "network", "set_app_filter_user"}, call("set_app_filter_user"), nil).leaf = true
- entry({"admin", "network", "del_app_filter_user"}, call("del_app_filter_user"), nil).leaf = true
- entry({"admin", "network", "add_app_filter_user"}, call("add_app_filter_user"), nil).leaf = true
- entry({"admin", "network", "get_whitelist_user"}, call("get_whitelist_user"), nil).leaf = true
- entry({"admin", "network", "add_whitelist_user"}, call("add_whitelist_user"), nil).leaf = true
- entry({"admin", "network", "del_whitelist_user"}, call("del_whitelist_user"), nil).leaf = true
- entry({"admin", "network", "upload_file"}, call("handle_file_upload"), nil).leaf = true
- entry({"admin", "network", "set_nickname"}, call("set_nickname"), nil).leaf = true
- entry({"admin", "network", "get_oaf_status"}, call("get_oaf_status"), nil).leaf = true
- entry({"admin", "network", "get_app_filter_adv"}, call("get_app_filter_adv"), nil).leaf = true
- entry({"admin", "network", "set_app_filter_adv"}, call("set_app_filter_adv"), nil).leaf = true
- entry({"admin", "network", "disable_flow_offloading"}, call("disable_flow_offloading"), nil).leaf = true
- entry({"admin", "network", "cmd"}, call("handle_cmd"), nil).leaf = true
-
-
- entry({"admin", "appfilter", "feature", "info"}, call("get_feature_info"), nil).leaf = true
- entry({"admin", "appfilter", "feature", "class_list"}, call("get_feature_class_list"), nil).leaf = true
- entry({"admin", "appfilter", "feature", "upgrade_status"}, call("get_feature_upgrade_status"), nil).leaf = true
-
-
-
-
-end
-
-function get_hostname_by_mac(dst_mac)
- leasefile="/tmp/dhcp.leases"
- local fd = io.open(leasefile, "r")
- if not fd then return end
- while true do
- local ln = fd:read("*l")
- if not ln then
- break
- end
- local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)")
- if dst_mac == mac then
- fd:close()
- return name
- end
- end
- fd:close()
- return ""
-end
-
-
-function handle_feature_upgrade()
- local fs = require "nixio.fs"
- local http = require "luci.http"
- local image_tmp = "/tmp/feature.cfg"
-
- local fp
- http.setfilehandler(
- function(meta, chunk, eof)
-
- fp = io.open(image_tmp, "w")
-
- if fp and chunk then
- fp:write(chunk)
- end
- if fp and eof then
- fp:close()
- end
- end
- )
-
-
-end
-
-function get_app_name_by_id(appid)
- local class_fd = io.popen("find /tmp/appfilter/ -type f -name *.class |xargs cat |grep "..appid.."|awk '{print $2}'")
- if class_fd then
- local name = class_fd:read("*l")
- class_fd:close()
- return name
- end
- return ""
-end
-
-function cmp_func(a,b)
- return a.latest_time > b.latest_time
-end
-
-
-function user_status()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local fd = io.open("/proc/net/af_client","r")
- status_buf=fd:read('*a')
- fd:close()
- user_array=json.parse(status_buf)
-
- local visit_obj=utl.ubus("appfilter", "visit_list", {});
- local user_array=visit_obj.dev_list
- local history={}
- for i, v in pairs(user_array) do
- visit_array=user_array[i].visit_info
- for j,s in pairs(visit_array) do
- print(user_array[i].mac, user_array[i].ip,visit_array[j].appid, visit_array[j].latest_time)
- total_time=visit_array[j].latest_time - visit_array[j].first_time;
- history[#history+1]={
- mac=user_array[i].mac,
- ip=user_array[i].ip,
- hostname=get_hostname_by_mac(user_array[i].mac),
- appid=visit_array[j].appid,
- appname=get_app_name_by_id(visit_array[j].appid),
- total_num=0,
- drop_num=0,
- latest_action=visit_array[j].latest_action,
- latest_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].latest_time),
- first_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].first_time),
- total_time=total_time
- }
- end
- end
- table.sort(history, cmp_func)
- luci.http.write_json(history);
-end
-
-
-function get_user_list()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local visit_obj=utl.ubus("appfilter", "dev_list", {});
- luci.http.write_json(visit_obj);
-end
-
-function get_class_list()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local class_obj=utl.ubus("appfilter", "class_list", {});
- llog("get class list");
- luci.http.write_json(class_obj);
-end
-
-function get_all_users()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local flag = luci.http.formvalue("flag")
- local page = luci.http.formvalue("page")
- local page_size = luci.http.formvalue("page_size")
- local params = {flag=flag, page=page}
- if page_size then
- params.page_size = page_size
- end
- local class_obj=utl.ubus("appfilter", "get_all_users", params);
- luci.http.write_json(class_obj);
-end
-
-function get_oaf_status()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "get_oaf_status", {});
- luci.http.write_json(resp_obj);
-end
-
-function get_app_filter_user()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "get_app_filter_user", {});
- luci.http.write_json(resp_obj);
-end
-
-function del_app_filter_user()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.mac = luci.http.formvalue("mac")
- llog("del appfilter user "..req_obj.mac);
- local resp_obj=utl.ubus("appfilter", "del_app_filter_user", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function add_app_filter_user()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- local data_str = luci.http.formvalue("data")
- req_obj = json.parse(data_str)
-
- local resp_obj=utl.ubus("appfilter", "add_app_filter_user", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function get_whitelist_user()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "get_whitelist_user", {});
- luci.http.write_json(resp_obj);
-end
-
-function add_whitelist_user()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- local data_str = luci.http.formvalue("data")
- req_obj = json.parse(data_str)
-
- local resp_obj=utl.ubus("appfilter", "add_whitelist_user", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function del_whitelist_user()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.mac = luci.http.formvalue("mac")
- llog("del whitelist user "..req_obj.mac);
- local resp_obj=utl.ubus("appfilter", "del_whitelist_user", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function get_app_filter()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "get_app_filter", {});
- luci.http.write_json(resp_obj);
-end
-
-function set_app_filter()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
-
- local app_list_str = luci.http.formvalue("app_list")
-
- local app_list = {}
- for id in app_list_str:gmatch("([^,]+)") do
- table.insert(app_list, tonumber(id))
- end
-
- local req_obj = {
- app_list = app_list
- }
-
- local resp_obj = utl.ubus("appfilter", "set_app_filter", req_obj)
- luci.http.write_json(resp_obj)
-end
-
-function set_nickname()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.mac = luci.http.formvalue("mac")
- req_obj.nickname = luci.http.formvalue("nickname")
- llog("set nickname "..req_obj.mac.." "..req_obj.nickname);
- local resp_obj=utl.ubus("appfilter", "set_nickname", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function get_app_filter_base()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "get_app_filter_base", {});
- luci.http.write_json(resp_obj);
-end
-
-function set_app_filter_user()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.mode = luci.http.formvalue("mode")
- local resp_obj=utl.ubus("appfilter", "set_app_filter_user", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function set_app_filter_base()
- local json = require "luci.jsonc"
- llog("set appfilter base");
- luci.http.prepare_content("application/json")
- local req_obj = {}
-
-
- local enable = luci.http.formvalue("enable")
- local work_mode = luci.http.formvalue("work_mode")
- local record_enable = luci.http.formvalue("record_enable")
- local disable_quic = luci.http.formvalue("disable_quic")
- local app_filter_mode = luci.http.formvalue("app_filter_mode")
-
- llog("enable: "..enable.." work_mode: "..work_mode.." record_enable: "..record_enable.." disable_quic: "..(disable_quic or "nil").." app_filter_mode: "..(app_filter_mode or "nil"))
- req_obj.enable = enable
- req_obj.work_mode = work_mode
- req_obj.record_enable = record_enable
- if disable_quic then
- req_obj.disable_quic = disable_quic
- end
- if app_filter_mode then
- req_obj.app_filter_mode = app_filter_mode
- end
-
- local resp_obj=utl.ubus("appfilter", "set_app_filter_base", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function get_app_filter_adv()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "get_app_filter_adv", {});
- luci.http.write_json(resp_obj);
-end
-function set_app_filter_adv()
- local json = require "luci.jsonc"
- llog("set appfilter base");
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.lan_ifname = luci.http.formvalue("lan_ifname")
- req_obj.disable_hnat = luci.http.formvalue("disable_hnat")
- req_obj.auto_load_engine = luci.http.formvalue("auto_load_engine")
- local resp_obj=utl.ubus("appfilter", "set_app_filter_adv", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function disable_flow_offloading()
- local json = require "luci.jsonc"
- llog("disable flow offloading");
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "disable_flow_offloading", {});
- luci.http.write_json(resp_obj);
-end
-
-function handle_cmd()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local action = luci.http.formvalue("action")
- if not action then
- luci.http.write_json({code = -1, message = "action parameter is required"});
- return
- end
- local req_obj = {}
- req_obj.action = action
- local resp_obj=utl.ubus("appfilter", "cmd", req_obj);
- luci.http.write_json(resp_obj);
-end
-
--- data: {"mode":1,"weekday_list":[1,2,3,4,5,6,0],"start_time":"22:22","end_time":"12:00","allow_time":30,"deny_time":5}
-function set_app_filter_time()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj = json.parse(luci.http.formvalue("data"))
- local resp_obj=utl.ubus("appfilter", "set_app_filter_time", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function get_app_filter_time()
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local resp_obj=utl.ubus("appfilter", "get_app_filter_time", {});
- llog("controller get_app_filter_time: ubus response received");
- if resp_obj and resp_obj.data then
- llog("controller get_app_filter_time: mode=" .. tostring(resp_obj.data.mode or "nil"));
- if resp_obj.data.time_list then
- llog("controller get_app_filter_time: time_list length=" .. tostring(#resp_obj.data.time_list));
- for i, time_item in ipairs(resp_obj.data.time_list) do
- local weekday_str = "nil"
- if time_item.weekday_list then
- weekday_str = table.concat(time_item.weekday_list, ",")
- end
-
- end
- else
- llog("controller get_app_filter_time: time_list is nil");
- end
- local response_json = json.stringify(resp_obj);
- if response_json then
- llog("controller get_app_filter_time: final JSON response length=" .. tostring(string.len(response_json)));
- else
- llog("controller get_app_filter_time: failed to encode JSON");
- end
- else
- llog("controller get_app_filter_time: resp_obj or resp_obj.data is nil");
- end
- luci.http.write_json(resp_obj);
-end
-
-function get_dev_visit_time(mac)
-
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.mac = mac;
- local visit_obj=utl.ubus("appfilter", "dev_visit_time", req_obj);
-
- local visit_list=visit_obj.list
- luci.http.write_json(visit_list);
-end
-
-function get_app_class_visit_time(mac)
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.mac = mac;
- local visit_obj=utl.ubus("appfilter", "app_class_visit_time", req_obj);
- local class_array=visit_obj.class_list
- luci.http.write_json(class_array);
-end
-
-
-function get_dev_visit_list(mac)
- local json = require "luci.jsonc"
- luci.http.prepare_content("application/json")
- local req_obj = {}
- req_obj.mac = mac;
- local page = luci.http.formvalue("page")
- local page_size = luci.http.formvalue("page_size")
- if page then
- req_obj.page = page
- end
- if page_size then
- req_obj.page_size = page_size
- end
- local resp_obj=utl.ubus("appfilter", "dev_visit_list", req_obj);
- luci.http.write_json(resp_obj);
-end
-
-function handle_file_upload()
- local http = require "luci.http"
- local fs = require "nixio.fs"
- local upload_dir = "/tmp/uploads/"
- local file_name = "uploaded_file"
- llog("handle_file_upload started");
-
- -- Ensure the upload directory exists
- if not fs.access(upload_dir) then
- fs.mkdir(upload_dir)
- end
-
- llog("Upload directory checked/created");
-
- local file_path = upload_dir .. file_name
- local fp
-
- llog("file_path: " .. file_path);
- http.setfilehandler(
- function(meta, chunk, eof)
- -- Log metadata information
- llog("File upload metadata: " .. (meta and meta.name or "nil") .. ", " .. (meta and meta.file or "nil"))
- llog("File upload chunk size: " .. (chunk and #chunk or 0))
-
- if not fp then
- fp = io.open(file_path, "w")
- llog("File opened for writing: " .. file_path)
- end
- if fp and chunk then
- fp:write(chunk)
- llog("Chunk written to file")
- end
- if fp and eof then
- fp:close()
- llog("File upload completed and file closed")
- -- Ensure the file is processed or moved to the correct location
- process_uploaded_file(file_path)
- luci.http.prepare_content("application/json")
- luci.http.write_json({ success = true, message = "File uploaded successfully" })
- end
- end
- )
- llog("handle_file_upload setup complete");
-end
-
-function process_uploaded_file(file_path)
- -- Add logic here to process the uploaded file
- llog("Processing uploaded file: " .. file_path)
- -- Example: Move the file to a permanent location
- local permanent_path = "/etc/config/" .. file_name
- os.execute("mv " .. file_path .. " " .. permanent_path)
- llog("File moved to: " .. permanent_path)
-end
-
-function llog(message)
- local log_file = "/tmp/log/oaf_luci.log"
- local fd = io.open(log_file, "a")
- if fd then
- local timestamp = os.date("%Y-%m-%d %H:%M:%S")
- fd:write(string.format("[%s] %s\n", timestamp, message))
- fd:close()
- end
-end
-
-
-
-function get_feature_upgrade_status()
- local fs = require "nixio.fs"
- local json = require "luci.jsonc"
- local http = require "luci.http"
-
- local status_file = "/tmp/feature_upgrade.status"
- local status = 0
-
- if fs.access(status_file) then
- local content = fs.readfile(status_file) or ""
- status = tonumber(content:match("(%d+)")) or 0
- fs.writefile(status_file, "0")
- end
-
- http.prepare_content("application/json")
- http.write(json.stringify({code = 0, status = status}))
-end
-
-
-
-function get_feature_info()
- local json = require "luci.jsonc"
- local nfs = require "nixio.fs"
- local sys = require "luci.sys"
- luci.http.prepare_content("application/json")
-
- local info = {
- version = "",
- format = "v3.0",
- app_count = 0
- }
-
- if nfs.access("/tmp/feature.cfg") then
- info.app_count = tonumber(sys.exec("cat /tmp/feature.cfg | grep -v ^$ |grep -v ^# | wc -l")) or 0
- info.version = sys.exec("cat /tmp/feature.cfg |grep \"#version\" | awk '{print $2}'") or ""
- end
-
- luci.http.write(json.stringify({code = 0, data = info, message = "success"}))
-end
-
-function get_feature_class_list()
- local json = require "luci.jsonc"
- local utl = require "luci.util"
- luci.http.prepare_content("application/json")
-
- local req_obj = {}
- req_obj.api = "class_list"
- req_obj.data = {}
-
- local resp_obj = utl.ubus("fwx", "common", req_obj)
-
- if resp_obj and resp_obj.code == 2000 and resp_obj.data then
- luci.http.write(json.stringify(resp_obj.data))
- else
- luci.http.write(json.stringify({class_list = {}}))
- end
-end
\ No newline at end of file
diff --git a/luci-app-oaf/luasrc/controller/oaf.lua b/luci-app-oaf/luasrc/controller/oaf.lua
new file mode 100644
index 00000000..b071cd7a
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf.lua
@@ -0,0 +1,5 @@
+module("luci.controller.oaf", package.seeall)
+
+function index()
+ entry({"admin", "services", "oaf"}, firstchild(), _("Parental Control"), 20).dependent = true
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_about.lua b/luci-app-oaf/luasrc/controller/oaf_about.lua
new file mode 100644
index 00000000..9921a96c
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_about.lua
@@ -0,0 +1,5 @@
+module("luci.controller.oaf_about", package.seeall)
+
+function index()
+ entry({"admin", "services", "oaf", "about"}, template("oaf/about"), _("About"), 100).leaf = true
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_advanced.lua b/luci-app-oaf/luasrc/controller/oaf_advanced.lua
new file mode 100644
index 00000000..e96585cf
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_advanced.lua
@@ -0,0 +1,127 @@
+module("luci.controller.oaf_advanced", package.seeall)
+
+local util = require "luci.util"
+
+function index()
+ entry({"admin", "services", "oaf", "advanced"}, template("oaf/advanced"), _("Settings"), 90).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "get_system_info"}, call("get_system_info"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "set_system_info"}, call("set_system_info"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "get_work_mode"}, call("get_work_mode"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "set_work_mode"}, call("set_work_mode"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "get_tcp_rst"}, call("get_tcp_rst"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "set_tcp_rst"}, call("set_tcp_rst"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "get_advanced_settings"}, call("get_advanced_settings"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "system", "set_advanced_settings"}, call("set_advanced_settings"), nil).leaf = true
+end
+
+local function ubus_call(api, payload)
+ payload = payload or {}
+ payload.api = api
+ return util.ubus("fwx", "common", payload) or {code = 1}
+end
+
+function get_system_info()
+ local http = require "luci.http"
+ local resp = ubus_call("get_system_info", {data = {}})
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
+
+function set_system_info()
+ local http = require "luci.http"
+ local lan_ifname = http.formvalue("lan_ifname") or ""
+ local theme_mode = tonumber(http.formvalue("theme_mode") or "0") or 0
+
+ if theme_mode ~= 0 and theme_mode ~= 1 then
+ theme_mode = 0
+ end
+
+ local resp = ubus_call("set_system_info", {
+ data = {
+ fwx = {
+ lan_ifname = lan_ifname,
+ theme_mode = theme_mode
+ }
+ }
+ })
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
+
+function get_work_mode()
+ local http = require "luci.http"
+ local resp = ubus_call("get_work_mode", {data = {}})
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
+
+function set_work_mode()
+ local http = require "luci.http"
+ local work_mode = tonumber(http.formvalue("work_mode") or "0") or 0
+
+ if work_mode ~= 0 and work_mode ~= 1 then
+ http.prepare_content("application/json")
+ http.write_json({code = 1})
+ return
+ end
+
+ local resp = ubus_call("set_work_mode", {
+ data = {
+ work_mode = work_mode
+ }
+ })
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
+
+function get_tcp_rst()
+ local http = require "luci.http"
+ local resp = ubus_call("get_tcp_rst", {data = {}})
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
+
+function set_tcp_rst()
+ local http = require "luci.http"
+ local tcp_rst = tonumber(http.formvalue("tcp_rst") or "1") or 1
+
+ tcp_rst = tcp_rst == 0 and 0 or 1
+
+ local resp = ubus_call("set_tcp_rst", {
+ data = {
+ tcp_rst = tcp_rst
+ }
+ })
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
+
+function get_advanced_settings()
+ local http = require "luci.http"
+ local resp = ubus_call("get_advanced_settings", {data = {}})
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
+
+function set_advanced_settings()
+ local http = require "luci.http"
+ local disable_hnat = tonumber(http.formvalue("disable_hnat") or "0") or 0
+
+ disable_hnat = disable_hnat == 1 and 1 or 0
+
+ local resp = ubus_call("set_advanced_settings", {
+ data = {
+ disable_hnat = disable_hnat
+ }
+ })
+
+ http.prepare_content("application/json")
+ http.write_json(resp)
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_app_filter.lua b/luci-app-oaf/luasrc/controller/oaf_app_filter.lua
new file mode 100644
index 00000000..9cff55c4
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_app_filter.lua
@@ -0,0 +1,371 @@
+module("luci.controller.oaf_app_filter", package.seeall)
+local utl = require "luci.util"
+local nixio = require "nixio"
+
+function index()
+ if not nixio.fs.access("/etc/config/appfilter") then
+ return
+ end
+ entry({"admin", "services", "oaf", "app_filter"}, alias("admin", "services", "oaf", "app_filter", "rules"), _("App Filter"), 30).dependent = true
+ entry({"admin", "services", "oaf", "app_filter", "rules"}, cbi("oaf/app_filter/rules", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Filter Rules"), 10).leaf=true
+ entry({"admin", "services", "oaf", "api", "app_filter", "class_list"}, call("get_class_list"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "get_all_users"}, call("get_all_users"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "get_filter_rules"}, call("get_filter_rules"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "add_filter_rule"}, call("add_filter_rule"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "update_filter_rule"}, call("update_filter_rule"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "delete_filter_rule"}, call("delete_filter_rule"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "get_appfilter_whitelist"}, call("get_appfilter_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "add_appfilter_whitelist"}, call("add_appfilter_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "del_appfilter_whitelist"}, call("del_appfilter_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "get_app_filter_adv"}, call("get_app_filter_adv"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_filter", "set_app_filter_adv"}, call("set_app_filter_adv"), nil).leaf = true
+end
+
+
+function get_class_list()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.CopyRight = "www.fanchmwrt.com"
+ req_obj.api = "class_list"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ llog("get class list");
+ luci.http.write_json(resp_obj.data)
+ else
+ llog("get class list failed");
+ luci.http.write_json({class_list = {}})
+ end
+end
+
+function get_all_users()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_all_users"
+ req_obj.data = {
+ flag = luci.http.formvalue("flag"),
+ page = luci.http.formvalue("page")
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json({data = resp_obj.data})
+ else
+ luci.http.write_json({data = resp_obj or {}})
+ end
+end
+
+
+
+function handle_file_upload()
+ local http = require "luci.http"
+ local fs = require "nixio.fs"
+ local upload_dir = "/tmp/uploads/"
+ local file_name = "uploaded_file"
+ llog("handle_file_upload started");
+
+ if not fs.access(upload_dir) then
+ fs.mkdir(upload_dir)
+ end
+
+ llog("Upload directory checked/created");
+
+ local file_path = upload_dir .. file_name
+ local fp
+
+ llog("file_path: " .. file_path);
+ http.setfilehandler(
+ function(meta, chunk, eof)
+ llog("File upload metadata: " .. (meta and meta.name or "nil") .. ", " .. (meta and meta.file or "nil"))
+ llog("File upload chunk size: " .. (chunk and #chunk or 0))
+
+ if not fp then
+ fp = io.open(file_path, "w")
+ llog("File opened for writing: " .. file_path)
+ end
+ if fp and chunk then
+ fp:write(chunk)
+ llog("Chunk written to file")
+ end
+ if fp and eof then
+ fp:close()
+ llog("File upload completed and file closed")
+ process_uploaded_file(file_path)
+ luci.http.prepare_content("application/json")
+ luci.http.write_json({ success = true, message = "File uploaded successfully" })
+ end
+ end
+ )
+ llog("handle_file_upload setup complete");
+end
+
+function process_uploaded_file(file_path)
+ llog("Processing uploaded file: " .. file_path)
+ local permanent_path = "/etc/config/" .. file_name
+ os.execute("mv " .. file_path .. " " .. permanent_path)
+ llog("File moved to: " .. permanent_path)
+end
+
+function llog(message)
+ local log_file = "/tmp/log/oaf_luci.log"
+ local fd = io.open(log_file, "a")
+ if fd then
+ local timestamp = os.date("%Y-%m-%d %H:%M:%S")
+ fd:write(string.format("[%s] %s\n", timestamp, message))
+ fd:close()
+ end
+end
+
+function get_filter_rules()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_filter_rules"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data and resp_obj.data.list then
+ local rules_data = resp_obj.data.list
+ llog("get_filter_rules: returning " .. #rules_data .. " rules")
+ local json_str = json.stringify({code = 0, data = rules_data, message = "success"})
+ luci.http.write(json_str)
+ else
+ llog("get_filter_rules: failed, returning empty array")
+ local json_str = json.stringify({code = 0, data = {}, message = "success"})
+ luci.http.write(json_str)
+ end
+end
+
+
+function add_filter_rule()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local data_str = luci.http.formvalue("data")
+ if not data_str then
+ luci.http.write_json({code = 1, message = "Invalid request data"})
+ return
+ end
+
+ local rule_data = json.parse(data_str)
+ llog("add_filter_rule: " .. json.stringify(rule_data))
+
+
+ if not rule_data.name or not rule_data.mode or not rule_data.time_rules or not rule_data.app_ids then
+ luci.http.write_json({code = 1, message = "Missing required fields"})
+ return
+ end
+
+ local get_req_obj = {}
+ get_req_obj.api = "get_filter_rules"
+ get_req_obj.data = {}
+ local get_resp_obj = utl.ubus("fwx", "common", get_req_obj)
+
+ if get_resp_obj and get_resp_obj.code == 2000 and get_resp_obj.data and get_resp_obj.data.data then
+ local existing_rules = get_resp_obj.data.data
+ if #existing_rules >= 32 then
+ luci.http.write_json({code = 1, message = "Maximum 32 rules allowed"})
+ return
+ end
+ end
+
+ local req_obj = {}
+ req_obj.api = "add_filter_rule"
+ req_obj.data = rule_data
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "Rule added successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to add rule"})
+ end
+end
+
+function update_filter_rule()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local data_str = luci.http.formvalue("data")
+ if not data_str then
+ luci.http.write_json({code = 1, message = "Invalid request data"})
+ return
+ end
+
+ local rule_data = json.parse(data_str)
+ llog("update_filter_rule: " .. json.stringify(rule_data))
+
+ if not rule_data.id then
+ luci.http.write_json({code = 1, message = "Missing rule id"})
+ return
+ end
+
+ local req_obj = {}
+ req_obj.api = "update_filter_rule"
+ req_obj.data = rule_data
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "Rule updated successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to update rule"})
+ end
+end
+
+function delete_filter_rule()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local rule_id = luci.http.formvalue("rule_id")
+ if not rule_id then
+ luci.http.write_json({code = 1, message = "Invalid rule_id"})
+ return
+ end
+
+ llog("delete_filter_rule: " .. rule_id)
+
+ local req_obj = {}
+ req_obj.api = "delete_filter_rule"
+ req_obj.data = {
+ id = tonumber(rule_id)
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "Rule deleted successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to delete rule"})
+ end
+end
+
+function get_appfilter_whitelist()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_appfilter_whitelist"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json({code = 2000, data = resp_obj.data, message = "success"})
+ else
+ luci.http.write_json({code = 2000, data = {list = {}}, message = "success"})
+ end
+end
+
+function add_appfilter_whitelist()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local data_str = luci.http.formvalue("data")
+ if not data_str then
+ luci.http.write_json({code = 1, message = "Invalid request data"})
+ return
+ end
+
+ local whitelist_data = json.parse(data_str)
+ llog("add_appfilter_whitelist: " .. json.stringify(whitelist_data))
+
+ if not whitelist_data.mac_list or type(whitelist_data.mac_list) ~= "table" then
+ luci.http.write_json({code = 1, message = "Invalid mac_list"})
+ return
+ end
+
+ local req_obj = {}
+ req_obj.api = "add_appfilter_whitelist"
+ req_obj.data = whitelist_data
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 2000, message = "Whitelist added successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to add whitelist"})
+ end
+end
+
+function del_appfilter_whitelist()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local mac = luci.http.formvalue("mac")
+ if not mac then
+ luci.http.write_json({code = 1, message = "Invalid mac address"})
+ return
+ end
+
+ llog("del_appfilter_whitelist: " .. mac)
+
+ local req_obj = {}
+ req_obj.api = "del_appfilter_whitelist"
+ req_obj.data = {
+ mac = mac
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 2000, message = "Whitelist deleted successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to delete whitelist"})
+ end
+end
+
+function get_app_filter_adv()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_app_filter_adv"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json({code = 0, data = resp_obj.data, message = "success"})
+ end
+end
+
+function set_app_filter_adv()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local enable = tonumber(luci.http.formvalue("enable")) or 1
+
+ local req_obj = {}
+ req_obj.api = "set_app_filter_adv"
+ req_obj.data = {
+ enable = enable,
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "Saved successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to save"})
+ end
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_app_record.lua b/luci-app-oaf/luasrc/controller/oaf_app_record.lua
new file mode 100644
index 00000000..e1fe0169
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_app_record.lua
@@ -0,0 +1,83 @@
+module("luci.controller.oaf_app_record", package.seeall)
+
+local function normalize_page(value, default_value)
+ local n = tonumber(value or "") or default_value
+ if n < 1 then
+ n = default_value
+ end
+ return n
+end
+
+local function write_empty_list(page, page_size)
+ luci.http.write_json({
+ total_num = 0,
+ total_page = 1,
+ page = page,
+ page_size = page_size,
+ list = {}
+ })
+end
+
+function index()
+ entry({"admin", "services", "oaf", "app_record"}, template("oaf/app_record"), _("App Record"), 70).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_record", "get_active_app_records"}, call("get_active_app_records")).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_record", "get_app_history_records"}, call("get_app_history_records")).leaf = true
+end
+
+function get_active_app_records()
+ local util = require "luci.util"
+ local page = normalize_page(luci.http.formvalue("page"), 1)
+ local page_size = normalize_page(luci.http.formvalue("page_size"), 15)
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {
+ api = "get_active_app_records",
+ data = {
+ page = page,
+ page_size = page_size
+ }
+ }
+
+ local resp_obj = util.ubus("fwx", "common", req_obj)
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ write_empty_list(page, page_size)
+ end
+end
+
+function get_app_history_records()
+ local util = require "luci.util"
+ local mac = luci.http.formvalue("mac")
+ local start_time = tonumber(luci.http.formvalue("start_time") or "0") or 0
+ local end_time = tonumber(luci.http.formvalue("end_time") or "0") or 0
+ local appid = tonumber(luci.http.formvalue("appid") or "0") or 0
+ local page = normalize_page(luci.http.formvalue("page"), 1)
+ local page_size = normalize_page(luci.http.formvalue("page_size"), 15)
+
+ if appid < 0 then
+ appid = 0
+ end
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {
+ api = "get_app_history_records",
+ data = {
+ mac = mac,
+ start_time = start_time,
+ end_time = end_time,
+ appid = appid,
+ page = page,
+ page_size = page_size
+ }
+ }
+
+ local resp_obj = util.ubus("fwx", "common", req_obj)
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ write_empty_list(page, page_size)
+ end
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_dashboard.lua b/luci-app-oaf/luasrc/controller/oaf_dashboard.lua
new file mode 100644
index 00000000..0cf7f4de
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_dashboard.lua
@@ -0,0 +1,229 @@
+module("luci.controller.oaf_dashboard", package.seeall)
+
+function index()
+ entry({"admin", "services", "oaf", "dashboard"}, cbi("oaf/dashboard", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),
+ _("Dashboard"), 10).leaf = true
+
+ entry({"admin", "services", "oaf", "api", "dashboard", "get_dashboard_common"}, call("get_dashboard_common")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "get_init_status"}, call("get_init_status")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "set_init_status"}, call("set_init_status")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "set_notice_status"}, call("set_notice_status")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "get_daily_top_users"}, call("get_daily_top_users")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "get_history_traffic_stats"}, call("get_history_traffic_stats")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "get_global_traffic_stats"}, call("get_global_traffic_stats")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "get_active_users"}, call("get_active_users")).leaf = true
+ entry({"admin", "services", "oaf", "api", "dashboard", "get_app_type_stats"}, call("get_app_type_stats")).leaf = true
+end
+
+function get_dashboard_common()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_dashboard_common"
+ req_obj.CopyRight = "www.fanchmwrt.com"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ system_status = {},
+ network_status = {},
+ active_app = {total = 0, list = {}},
+ advanced = {disable_hnat = 0, notice_status = 0},
+ interface_traffic = {interface = "wan", traffic = {}}
+ })
+ end
+end
+
+function get_global_traffic_stats()
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_global_traffic_stats"
+ req_obj.data = {}
+
+ local date = luci.http.formvalue("date")
+ if date then
+ req_obj.data.date = tonumber(date) or 0
+ end
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ date = 0,
+ is_today = 1,
+ hourly_traffic = {}
+ })
+ end
+end
+
+function get_history_traffic_stats()
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_history_traffic_stats"
+ req_obj.data = {}
+
+ local days = luci.http.formvalue("days")
+ if days then
+ req_obj.data.days = tonumber(days) or 30
+ else
+ req_obj.data.days = 30
+ end
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ days = req_obj.data.days,
+ total_up_bytes = 0,
+ total_down_bytes = 0,
+ total_bytes = 0,
+ list = {}
+ })
+ end
+end
+
+function get_init_status()
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_init_status"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({init_status = 1})
+ end
+end
+
+function set_init_status()
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "set_init_status"
+ req_obj.data = { init_status = 1 }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({init_status = 0})
+ end
+end
+
+function set_notice_status()
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "set_dashboard_notice_status"
+ req_obj.data = { notice_status = 1 }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 2000, notice_status = 1})
+ else
+ luci.http.write_json({code = 1})
+ end
+end
+
+function get_daily_top_users()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_daily_top_users"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ date = 0,
+ total_count = 0,
+ users = {}
+ })
+ end
+end
+
+function get_active_users()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_active_users"
+ req_obj.data = {}
+
+ local count = luci.http.formvalue("count")
+ if count then
+ req_obj.data.count = tonumber(count) or 8
+ else
+ req_obj.data.count = 8
+ end
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ total_count = 0,
+ users = {}
+ })
+ end
+end
+
+function get_app_type_stats()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local stat_type = luci.http.formvalue("type") or "hourly"
+
+ local req_obj = {}
+ req_obj.api = "get_global_app_type_stats"
+ req_obj.data = {
+ type = stat_type,
+ limit = 10
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ type = stat_type,
+ limit = 10,
+ total_count = 0,
+ types = {}
+ })
+ end
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_feature.lua b/luci-app-oaf/luasrc/controller/oaf_feature.lua
new file mode 100644
index 00000000..593644ea
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_feature.lua
@@ -0,0 +1,118 @@
+module("luci.controller.oaf_feature", package.seeall)
+
+function index()
+ entry({"admin", "services", "oaf", "feature"},
+ template("oaf/feature"),
+ _("Feature Library"), 80).dependent = true
+ entry({"admin", "services", "oaf", "feature", "info"}, call("get_feature_info"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "class_list"}, call("get_feature_class_list"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "online_config"}, call("get_feature_online_config"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "online_save"}, call("set_feature_online_config"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "online_list"}, call("get_feature_online_list"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "online_start"}, call("start_feature_online_update"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "online_status"}, call("get_feature_online_update_status"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "custom_list"}, call("get_custom_feature_list"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "custom_class_list"}, call("get_custom_feature_class_list"), nil).leaf = true
+ entry({"admin", "services", "oaf", "feature", "custom_save"}, call("set_custom_feature_list"), nil).leaf = true
+end
+
+function get_feature_info()
+ local json = require "luci.jsonc"
+ local util = require "luci.util"
+ local http = require "luci.http"
+ local resp = util.ubus("fwx", "common", {api = "get_feature_info", data = {}})
+
+ http.prepare_content("application/json")
+ http.write(json.stringify(resp or {code = 4000}))
+end
+
+local function write_fwx_response(api, data)
+ local json = require "luci.jsonc"
+ local util = require "luci.util"
+ local http = require "luci.http"
+ local resp = util.ubus("fwx", "common", {api = api, data = data or {}})
+
+ http.prepare_content("application/json")
+ http.write(json.stringify(resp or {code = 4000}))
+end
+
+function get_feature_online_config()
+ write_fwx_response("get_feature_online_config", {})
+end
+
+function set_feature_online_config()
+ local http = require "luci.http"
+ write_fwx_response("set_feature_online_config", {
+ token = http.formvalue("token") or ""
+ })
+end
+
+function get_feature_online_list()
+ local http = require "luci.http"
+ local lang = http.formvalue("lang") or "cn"
+ local refresh = tonumber(http.formvalue("refresh") or "0") or 0
+ if lang ~= "cn" and lang ~= "en" then
+ lang = "cn"
+ end
+ write_fwx_response("get_feature_online_list", {
+ lang = lang,
+ device_lang = http.formvalue("device_lang") or "",
+ refresh = refresh
+ })
+end
+
+function start_feature_online_update()
+ local http = require "luci.http"
+ local lang = http.formvalue("lang") or "cn"
+ if lang ~= "cn" and lang ~= "en" then
+ lang = "cn"
+ end
+ write_fwx_response("start_feature_online_update", {
+ id = http.formvalue("id") or "",
+ lang = lang,
+ md5 = http.formvalue("md5") or ""
+ })
+end
+
+function get_feature_online_update_status()
+ write_fwx_response("get_feature_online_update_status", {})
+end
+
+function get_custom_feature_list()
+ write_fwx_response("get_custom_feature", {})
+end
+
+function get_custom_feature_class_list()
+ write_fwx_response("get_custom_feature_class_list", {})
+end
+
+function set_custom_feature_list()
+ local json = require "luci.jsonc"
+ local http = require "luci.http"
+ local data_str = http.formvalue("data")
+ local ok, data_obj = pcall(json.parse, data_str or "")
+
+ if not ok then
+ data_obj = nil
+ end
+ if type(data_obj) ~= "table" or type(data_obj.app_list) ~= "table" then
+ http.prepare_content("application/json")
+ http.write(json.stringify({code = 4000, data = {error = "invalid request data"}}))
+ return
+ end
+ write_fwx_response("set_custom_feature", data_obj)
+end
+
+function get_feature_class_list()
+ local json = require "luci.jsonc"
+ local util = require "luci.util"
+ local http = require "luci.http"
+ local resp = util.ubus("fwx", "common", {CopyRight = "www.fanchmwrt.com", api = "class_list", data = {}})
+
+ http.prepare_content("application/json")
+ if resp and resp.code == 2000 and resp.data then
+ http.write(json.stringify(resp.data))
+ else
+ http.write(json.stringify({class_list = {}}))
+ end
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_internet_audit.lua b/luci-app-oaf/luasrc/controller/oaf_internet_audit.lua
new file mode 100644
index 00000000..e25697a6
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_internet_audit.lua
@@ -0,0 +1,113 @@
+module("luci.controller.oaf_internet_audit", package.seeall)
+
+function index()
+ entry({"admin", "services", "oaf", "api", "internet_audit", "get_record_base"}, call("get_record_base"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "internet_audit", "set_record_base"}, call("set_record_base"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "internet_audit", "record_action"}, call("record_action"), nil).leaf = true
+end
+
+function ensure_record_section(cur)
+ local sid
+ cur:foreach("fwx", "record", function(s)
+ sid = s[".name"]
+ end)
+ if not sid then
+ sid = cur:add("fwx", "record")
+ end
+ return sid
+end
+
+function get_record_base()
+ local json = require "luci.jsonc"
+ local http = require "luci.http"
+ local utl = require "luci.util"
+
+ local req_obj = { api = "get_record_base", data = {} }
+ local resp = utl.ubus("fwx", "common", req_obj) or {code = 1}
+ http.prepare_content("application/json")
+ http.write(json.stringify(resp))
+end
+
+function set_record_base()
+ local json = require "luci.jsonc"
+ local http = require "luci.http"
+ local utl = require "luci.util"
+
+ local enable = tonumber(http.formvalue("enable") or 0) or 0
+ local record_time = tonumber(http.formvalue("record_time") or 0) or 0
+ local app_valid_time = tonumber(http.formvalue("app_valid_time") or 0) or 0
+ local history_data_size = http.formvalue("history_data_size") or ""
+ local history_data_path = http.formvalue("history_data_path") or ""
+ local base_data_path = http.formvalue("base_data_path") or http.formvalue("terminal_data_path") or ""
+
+ if record_time < 0 then record_time = 0 end
+ if app_valid_time < 0 then app_valid_time = 0 end
+
+ if history_data_size and history_data_size ~= "" then
+ local size_num = tonumber(history_data_size)
+ if not size_num or size_num < 1 or size_num > 1024 or size_num ~= math.floor(size_num) then
+ http.prepare_content("application/json")
+ http.write(json.stringify({code = 1, msg = "History data size must be an integer between 1 and 1024 MB"}))
+ return
+ end
+ end
+
+ if not history_data_path or history_data_path == "" or history_data_path == "/" then
+ http.prepare_content("application/json")
+ http.write(json.stringify({code = 1, msg = "History data path cannot be empty or /"}))
+ return
+ end
+
+ if not base_data_path or base_data_path == "" or base_data_path == "/" then
+ http.prepare_content("application/json")
+ http.write(json.stringify({code = 1, msg = "Base data path cannot be empty or /"}))
+ return
+ end
+
+ if #history_data_path > 64 then
+ http.prepare_content("application/json")
+ http.write(json.stringify({code = 1, msg = "History data path maximum length is 64 characters"}))
+ return
+ end
+
+ if #base_data_path > 64 then
+ http.prepare_content("application/json")
+ http.write(json.stringify({code = 1, msg = "Base data path maximum length is 64 characters"}))
+ return
+ end
+
+ local req_obj = {
+ api = "set_record_base",
+ data = {
+ enable = enable,
+ record_time = record_time,
+ app_valid_time = app_valid_time,
+ history_data_size = history_data_size,
+ history_data_path = history_data_path,
+ base_data_path = base_data_path
+ }
+ }
+ local resp = utl.ubus("fwx", "common", req_obj) or {code = 1}
+
+ http.prepare_content("application/json")
+ http.write(json.stringify(resp))
+end
+
+function record_action()
+ local json = require "luci.jsonc"
+ local http = require "luci.http"
+ local utl = require "luci.util"
+
+ local action = http.formvalue("action") or ""
+
+ local req_obj = {
+ api = "record_action",
+ data = {
+ action = action
+ }
+ }
+ local resp = utl.ubus("fwx", "common", req_obj) or {code = 1}
+
+ http.prepare_content("application/json")
+ http.write(json.stringify(resp))
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_mac_filter.lua b/luci-app-oaf/luasrc/controller/oaf_mac_filter.lua
new file mode 100644
index 00000000..e9e5b2d8
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_mac_filter.lua
@@ -0,0 +1,461 @@
+module("luci.controller.oaf_mac_filter", package.seeall)
+local utl = require "luci.util"
+local nixio = require "nixio"
+
+function index()
+ if not nixio.fs.access("/etc/config/macfilter") then
+ return
+ end
+
+ entry({"admin", "services", "oaf", "mac_filter"}, alias("admin", "services", "oaf", "mac_filter", "rules"), _("Access Control"), 40).dependent = true
+ entry({"admin", "services", "oaf", "mac_filter", "rules"}, cbi("oaf/mac_filter/rules", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Filter Rules"), 10).leaf=true
+
+ entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_base"}, call("get_mac_filter_base"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_base"}, call("set_mac_filter_base"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_time"}, call("set_mac_filter_time"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_time"}, call("get_mac_filter_time"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_user"}, call("get_mac_filter_user"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_user"}, call("set_mac_filter_user"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "del_mac_filter_user"}, call("del_mac_filter_user"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "add_mac_filter_user"}, call("add_mac_filter_user"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "get_mf_status"}, call("get_mf_status"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_whitelist"}, call("get_mac_filter_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "del_mac_filter_whitelist"}, call("del_mac_filter_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "add_mac_filter_whitelist"}, call("add_mac_filter_whitelist"), nil).leaf = true
+
+
+ entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_rules"}, call("get_mac_filter_rules"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "add_mac_filter_rule"}, call("add_mac_filter_rule"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "update_mac_filter_rule"}, call("update_mac_filter_rule"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "delete_mac_filter_rule"}, call("delete_mac_filter_rule"), nil).leaf = true
+
+
+ entry({"admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_adv"}, call("get_mac_filter_adv"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "mac_filter", "set_mac_filter_adv"}, call("set_mac_filter_adv"), nil).leaf = true
+end
+
+function get_mf_status()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "get_mf_status"
+ req_obj.data = {}
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+function get_mac_filter_user()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "get_mac_filter_user"
+ req_obj.data = {}
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+function del_mac_filter_user()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ local mac = luci.http.formvalue("mac")
+ llog("del macfilter user "..mac);
+ req_obj.api = "del_mac_filter_user"
+ req_obj.data = {
+ mac = mac
+ }
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+function add_mac_filter_user()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "add_mac_filter_user"
+ local data_str = luci.http.formvalue("data")
+ local data = json.parse(data_str)
+ req_obj.data = data
+
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+
+function get_mac_filter_base()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "get_mac_filter_base"
+ req_obj.data = {}
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+function set_mac_filter_user()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "set_mac_filter_user"
+ local mode = luci.http.formvalue("mode")
+ req_obj.data = {
+ mode = mode
+ }
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+function set_mac_filter_base()
+ local json = require "luci.jsonc"
+ llog("set macfilter base");
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "set_mac_filter_base"
+ local enable = luci.http.formvalue("enable")
+ req_obj.data = {
+ enable = enable
+ }
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+
+
+function set_mac_filter_time()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "set_mac_filter_time"
+ local data_str = luci.http.formvalue("data")
+ local data = json.parse(data_str)
+ req_obj.data = data
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+function get_mac_filter_time()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "get_mac_filter_time"
+ req_obj.data = {}
+ local resp_obj=utl.ubus("fwx", "common", req_obj);
+ luci.http.write_json(resp_obj);
+end
+
+
+function get_mac_filter_whitelist()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+
+ local req_obj = {}
+ req_obj.api = "get_mac_filter_whitelist"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json({code = 2000, data = resp_obj.data, message = "success"})
+ else
+
+ luci.http.write_json({code = 2000, data = {list = {}}, message = "success"})
+ end
+end
+
+
+function add_mac_filter_whitelist()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local data_str = luci.http.formvalue("data")
+ if not data_str then
+ luci.http.write_json({code = 1, message = "Invalid request data"})
+ return
+ end
+
+ local whitelist_data = json.parse(data_str)
+ llog("add_mac_filter_whitelist: " .. json.stringify(whitelist_data))
+
+ if not whitelist_data.mac_list or type(whitelist_data.mac_list) ~= "table" then
+ luci.http.write_json({code = 1, message = "Invalid mac_list"})
+ return
+ end
+
+
+ local req_obj = {}
+ req_obj.api = "add_mac_filter_whitelist"
+ req_obj.data = whitelist_data
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+
+ if resp_obj and resp_obj.code == 2000 then
+
+ luci.http.write_json({code = 2000, message = "Whitelist added successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to add whitelist"})
+ end
+end
+
+
+function del_mac_filter_whitelist()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local mac = luci.http.formvalue("mac")
+ if not mac then
+ luci.http.write_json({code = 1, message = "Invalid mac address"})
+ return
+ end
+
+ llog("del_mac_filter_whitelist: " .. mac)
+
+
+ local req_obj = {}
+ req_obj.api = "del_mac_filter_whitelist"
+ req_obj.data = {
+ mac = mac
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+
+ if resp_obj and resp_obj.code == 2000 then
+
+ luci.http.write_json({code = 2000, message = "Whitelist deleted successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to delete whitelist"})
+ end
+end
+
+function llog(message)
+ local log_file = "/tmp/log/oaf_luci.log"
+ local fd = io.open(log_file, "a")
+ if fd then
+ local timestamp = os.date("%Y-%m-%d %H:%M:%S")
+ fd:write(string.format("[%s] %s\n", timestamp, message))
+ fd:close()
+ end
+end
+
+
+function get_mac_filter_rules()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+
+ local req_obj = {}
+ req_obj.api = "get_mac_filter_rules"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data and resp_obj.data.list then
+ local rules_data = resp_obj.data.list
+ local json_str = json.stringify({code = 0, data = rules_data, message = "success"})
+ luci.http.write(json_str)
+ else
+
+ local json_str = json.stringify({code = 0, data = {}, message = "success"})
+ luci.http.write(json_str)
+ end
+end
+
+
+function add_mac_filter_rule()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local data_str = luci.http.formvalue("data")
+ if not data_str then
+ luci.http.write_json({code = 1, message = "Invalid request data"})
+ return
+ end
+
+ local rule_data = json.parse(data_str)
+ llog("add_mac_filter_rule: " .. json.stringify(rule_data))
+
+
+ if not rule_data.name or not rule_data.mode then
+ luci.http.write_json({code = 1, message = "Missing required fields"})
+ return
+ end
+
+ local time_mode = tonumber(rule_data.time_mode) or 1
+ if time_mode == 1 then
+ if (not rule_data.time_list or #rule_data.time_list == 0) and (not rule_data.time_rules or #rule_data.time_rules == 0) then
+ luci.http.write_json({code = 1, message = "Missing required time_list"})
+ return
+ end
+ elseif time_mode == 2 then
+ if (not rule_data.time_limit or rule_data.time_limit == "") and (not rule_data.time_rules or #rule_data.time_rules == 0) then
+ luci.http.write_json({code = 1, message = "Missing required time_limit"})
+ return
+ end
+ elseif time_mode == 3 then
+ if (not rule_data.flow_limit or rule_data.flow_limit == "") and (not rule_data.time_rules or #rule_data.time_rules == 0) then
+ luci.http.write_json({code = 1, message = "Missing required flow_limit"})
+ return
+ end
+ end
+
+
+ local get_req_obj = {}
+ get_req_obj.api = "get_mac_filter_rules"
+ get_req_obj.data = {}
+ local get_resp_obj = utl.ubus("fwx", "common", get_req_obj)
+
+ if get_resp_obj and get_resp_obj.code == 2000 and get_resp_obj.data and get_resp_obj.data.data then
+ local existing_rules = get_resp_obj.data.data
+ if #existing_rules >= 32 then
+ luci.http.write_json({code = 1, message = "Maximum 32 rules allowed"})
+ return
+ end
+ end
+
+
+ local req_obj = {}
+ req_obj.api = "add_mac_filter_rule"
+ req_obj.data = rule_data
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "Rule added successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to add rule"})
+ end
+end
+
+
+function update_mac_filter_rule()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local data_str = luci.http.formvalue("data")
+ if not data_str then
+ luci.http.write_json({code = 1, message = "Invalid request data"})
+ return
+ end
+
+ local rule_data = json.parse(data_str)
+ llog("update_mac_filter_rule: " .. json.stringify(rule_data))
+
+ if not rule_data.id then
+ luci.http.write_json({code = 1, message = "Missing rule id"})
+ return
+ end
+
+
+ local req_obj = {}
+ req_obj.api = "update_mac_filter_rule"
+ req_obj.data = rule_data
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 then
+
+ luci.http.write_json({code = 0, message = "Rule updated successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to update rule"})
+ end
+end
+
+
+function delete_mac_filter_rule()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local rule_id = luci.http.formvalue("rule_id")
+ if not rule_id then
+ luci.http.write_json({code = 1, message = "Invalid rule_id"})
+ return
+ end
+
+ llog("delete_mac_filter_rule: " .. rule_id)
+
+
+ local req_obj = {}
+ req_obj.api = "delete_mac_filter_rule"
+ req_obj.data = {
+ id = tonumber(rule_id)
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "Rule deleted successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to delete rule"})
+ end
+end
+
+
+function get_mac_filter_adv()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+
+ local req_obj = {}
+ req_obj.api = "get_mac_filter_adv"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json({code = 0, data = resp_obj.data, message = "success"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to load"})
+ end
+end
+
+
+function set_mac_filter_adv()
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+ luci.http.prepare_content("application/json")
+
+ local enable = tonumber(luci.http.formvalue("enable")) or 0
+
+
+ if enable ~= 0 and enable ~= 1 then
+ luci.http.write_json({code = 1, message = "Invalid enable value, must be 0 or 1"})
+ return
+ end
+
+ local req_obj = {}
+ req_obj.api = "set_mac_filter_adv"
+ req_obj.data = {
+ enable = enable
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 then
+ llog("Set macfilter enable: " .. enable)
+ luci.http.write_json({code = 0, message = "Saved successfully"})
+ else
+ luci.http.write_json({code = 1, message = "Failed to save"})
+ end
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_user.lua b/luci-app-oaf/luasrc/controller/oaf_user.lua
new file mode 100644
index 00000000..a201b626
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_user.lua
@@ -0,0 +1,452 @@
+module("luci.controller.oaf_user", package.seeall)
+local utl = require "luci.util"
+
+function index()
+ local page
+ entry({"admin", "services", "oaf", "users"}, alias("admin", "services", "oaf", "users", "list"), _("User List"), 20).dependent = true
+ entry({"admin", "services", "oaf", "users", "list"}, cbi("oaf/user_list", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}),
+ nil).leaf = true
+
+ entry({"admin", "services", "oaf", "users", "detail"}, cbi("oaf/user_detail", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "user_status"}, call("user_status"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "dev_visit_list"}, call("get_dev_visit_list"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "dev_visit_time"}, call("get_dev_visit_time"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "app_class_visit_time"}, call("get_app_class_visit_time"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_class_list"}, call("get_class_list"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_all_users"}, call("get_all_users"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_system_base_info"}, call("get_system_base_info"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_mac_blacklist"}, call("get_mac_blacklist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "add_mac_blacklist"}, call("add_mac_blacklist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "del_mac_blacklist"}, call("del_mac_blacklist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_parental_control_detail"}, call("get_parental_control_detail"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "set_nickname"}, call("set_nickname"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_hourly_stats"}, call("get_hourly_stats"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_user_basic_info"}, call("get_user_basic_info"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_online_offline_records"}, call("get_online_offline_records"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "get_user_parental_control_rules"}, call("get_user_parental_control_rules"), nil).leaf = true
+end
+
+function get_hostname_by_mac(dst_mac)
+ leasefile="/tmp/dhcp.leases"
+ local fd = io.open(leasefile, "r")
+ if not fd then return end
+ while true do
+ local ln = fd:read("*l")
+ if not ln then
+ break
+ end
+ local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)")
+ if dst_mac == mac then
+ fd:close()
+ return name
+ end
+ end
+ fd:close()
+ return ""
+end
+
+
+function get_app_name_by_id(appid)
+ local class_fd = io.popen("find /tmp/appfilter/ -type f -name *.class |xargs cat |grep "..appid.."|awk '{print $2}'")
+ if class_fd then
+ local name = class_fd:read("*l")
+ class_fd:close()
+ if name and name ~= "" then
+ return name
+ end
+ end
+ if tonumber(appid) and tonumber(appid) > 0 then
+ return "App" .. tostring(appid)
+ end
+ return ""
+end
+
+function cmp_func(a,b)
+ return a.latest_time > b.latest_time
+end
+
+function normalize_mac(mac)
+ if not mac then
+ return ""
+ end
+ return tostring(mac):gsub("^%s+", ""):gsub("%s+$", ""):upper()
+end
+
+function call_fwx_common(api, data)
+ local req_obj = {}
+ req_obj.api = api
+ req_obj.data = data or {}
+ return utl.ubus("fwx", "common", req_obj)
+end
+
+function user_status()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+ local fd = io.open("/proc/net/af_client","r")
+ status_buf=fd:read('*a')
+ fd:close()
+ user_array=json.parse(status_buf)
+
+ local req_obj = {}
+ req_obj.api = "visit_list"
+ req_obj.data = {}
+ local visit_obj = utl.ubus("fwx", "common", req_obj)
+
+ local user_array = {}
+ if visit_obj and visit_obj.code == 2000 and visit_obj.data and visit_obj.data.dev_list then
+ user_array = visit_obj.data.dev_list
+ end
+ local history={}
+ for i, v in pairs(user_array) do
+ visit_array=user_array[i].visit_info
+ for j,s in pairs(visit_array) do
+ print(user_array[i].mac, user_array[i].ip,visit_array[j].appid, visit_array[j].latest_time)
+ total_time=visit_array[j].latest_time - visit_array[j].first_time;
+ history[#history+1]={
+ mac=user_array[i].mac,
+ ip=user_array[i].ip,
+ hostname=get_hostname_by_mac(user_array[i].mac),
+ appid=visit_array[j].appid,
+ appname=get_app_name_by_id(visit_array[j].appid),
+ total_num=0,
+ drop_num=0,
+ latest_action=visit_array[j].latest_action,
+ latest_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].latest_time),
+ first_time=os.date("%Y/%m/%d %H:%M:%S", visit_array[j].first_time),
+ total_time=total_time
+ }
+ end
+ end
+ table.sort(history, cmp_func)
+ luci.http.write_json(history);
+end
+
+function get_class_list()
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.CopyRight = "www.fanchmwrt.com"
+ req_obj.api = "class_list"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({class_list = {}})
+ end
+end
+
+
+function get_all_users()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_all_users"
+ req_obj.data = {
+ flag = luci.http.formvalue("flag"),
+ page = luci.http.formvalue("page"),
+ page_size = luci.http.formvalue("page_size")
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json({data = resp_obj.data})
+ else
+ luci.http.write_json({data = resp_obj or {}})
+ end
+end
+
+function get_system_base_info()
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_system_base_info"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ user_session_enable = 0
+ })
+ end
+end
+
+function get_mac_blacklist()
+ luci.http.prepare_content("application/json")
+
+ local resp_obj = call_fwx_common("get_mac_blacklist", {})
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json({code = 0, data = resp_obj.data, message = "success"})
+ else
+ luci.http.write_json({code = 1, data = {list = {}}, message = "failed"})
+ end
+end
+
+function add_mac_blacklist()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local mac_list = {}
+ local mac = normalize_mac(luci.http.formvalue("mac"))
+ if mac ~= "" then
+ table.insert(mac_list, mac)
+ else
+ local data_str = luci.http.formvalue("data")
+ if data_str and data_str ~= "" then
+ local data = json.parse(data_str)
+ if data and type(data.mac_list) == "table" then
+ for _, item in ipairs(data.mac_list) do
+ local normalized_mac = normalize_mac(item)
+ if normalized_mac ~= "" then
+ table.insert(mac_list, normalized_mac)
+ end
+ end
+ end
+ end
+ end
+
+ if #mac_list == 0 then
+ luci.http.write_json({code = 1, message = "Invalid mac"})
+ return
+ end
+
+ local resp_obj = call_fwx_common("add_mac_blacklist", {mac_list = mac_list})
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "success"})
+ else
+ luci.http.write_json({code = 1, message = "failed"})
+ end
+end
+
+function del_mac_blacklist()
+ luci.http.prepare_content("application/json")
+ local mac = normalize_mac(luci.http.formvalue("mac"))
+ if mac == "" then
+ luci.http.write_json({code = 1, message = "Invalid mac"})
+ return
+ end
+
+ local resp_obj = call_fwx_common("del_mac_blacklist", {mac = mac})
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json({code = 0, message = "success"})
+ else
+ luci.http.write_json({code = 1, message = "failed"})
+ end
+end
+
+function get_parental_control_detail()
+ luci.http.prepare_content("application/json")
+ local req_obj = {}
+ req_obj.api = "get_parental_control_detail"
+ req_obj.data = {
+ mac = luci.http.formvalue("mac")
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ pc_status = "unlimited",
+ pc_status_key = "unlimited",
+ af_whitelist = 0,
+ mf_whitelist = 0,
+ appfilter_rules = {},
+ macfilter_rules = {}
+ })
+ end
+end
+
+function get_user_parental_control_rules()
+ luci.http.prepare_content("application/json")
+
+ local target_mac = normalize_mac(luci.http.formvalue("mac"))
+ if target_mac == "" then
+ luci.http.write_json({
+ mac = "",
+ af_whitelist = 0,
+ mf_whitelist = 0,
+ list = {}
+ })
+ return
+ end
+
+ local req_obj = {
+ api = "get_user_parental_control_rules",
+ data = {
+ mac = target_mac
+ }
+ }
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and type(resp_obj.data) == "table" then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ mac = target_mac,
+ af_whitelist = 0,
+ mf_whitelist = 0,
+ list = {}
+ })
+ end
+end
+
+function get_oaf_status()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_oaf_status"
+ req_obj.data = {}
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json(resp_obj or {})
+ end
+end
+
+
+function set_nickname()
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "set_nickname"
+ req_obj.data = {
+ mac = luci.http.formvalue("mac"),
+ nickname = luci.http.formvalue("nickname")
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj);
+
+ if resp_obj and resp_obj.code == 2000 then
+ luci.http.write_json(resp_obj.data or {})
+ else
+ luci.http.write_json(resp_obj or {})
+ end
+end
+
+
+function get_dev_visit_time(mac)
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "dev_visit_time"
+ req_obj.data = {
+ mac = mac
+ }
+
+ local visit_obj = utl.ubus("fwx", "common", req_obj)
+
+ local visit_list = {}
+ if visit_obj and visit_obj.code == 2000 and visit_obj.data and visit_obj.data.list then
+ visit_list = visit_obj.data.list
+ end
+ luci.http.write_json(visit_list)
+end
+
+function get_app_class_visit_time(mac)
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "app_class_visit_time"
+ req_obj.data = {
+ mac = mac
+ }
+
+ local visit_obj = utl.ubus("fwx", "common", req_obj)
+
+ local class_array = {}
+ if visit_obj and visit_obj.code == 2000 and visit_obj.data and visit_obj.data.class_list then
+ class_array = visit_obj.data.class_list
+ end
+ luci.http.write_json(class_array)
+end
+
+
+function get_dev_visit_list(mac)
+ local json = require "luci.jsonc"
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "dev_visit_list"
+ req_obj.data = {
+ mac = mac
+ }
+
+ local page = luci.http.formvalue("page")
+ local page_size = luci.http.formvalue("page_size")
+ if page then
+ req_obj.data.page = tonumber(page) or 1
+ end
+ if page_size then
+ req_obj.data.page_size = tonumber(page_size) or 15
+ end
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json(resp_obj or {})
+ end
+end
+
+function get_hourly_stats(mac)
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_hourly_top_apps"
+ req_obj.data = {
+ mac = mac
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({
+ mac = mac,
+ date = 0,
+ is_today = 1,
+ hourly_stats = {}
+ })
+ end
+end
+
+function get_user_basic_info(mac)
+ local json = require "luci.jsonc"
+ local utl = require "luci.util"
+
+ luci.http.prepare_content("application/json")
+
+ local req_obj = {}
+ req_obj.api = "get_user_basic_info"
+ req_obj.data = {
+ mac = mac
+ }
+
+ local resp_obj = utl.ubus("fwx", "common", req_obj)
+
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ luci.http.write_json(resp_obj.data)
+ else
+ luci.http.write_json({})
+ end
+end
diff --git a/luci-app-oaf/luasrc/controller/oaf_whitelist.lua b/luci-app-oaf/luasrc/controller/oaf_whitelist.lua
new file mode 100644
index 00000000..63ce59fb
--- /dev/null
+++ b/luci-app-oaf/luasrc/controller/oaf_whitelist.lua
@@ -0,0 +1,181 @@
+module("luci.controller.oaf_whitelist", package.seeall)
+
+local nixio = require "nixio"
+local util = require "luci.util"
+local jsonc = require "luci.jsonc"
+
+local function normalize_mac(mac)
+ return string.upper((mac or ""):gsub("^%s+", ""):gsub("%s+$", ""))
+end
+
+local function is_valid_mac(mac)
+ return mac and mac:match("^%x%x:%x%x:%x%x:%x%x:%x%x:%x%x$") ~= nil
+end
+
+local function write_json(data)
+ luci.http.prepare_content("application/json")
+ luci.http.write_json(data)
+end
+
+local function get_record_whitelist_page(page, page_size)
+ local req_obj = {
+ api = "get_record_whitelist",
+ data = {
+ page = page,
+ page_size = page_size
+ }
+ }
+
+ return util.ubus("fwx", "common", req_obj)
+end
+
+function index()
+ local has_app_filter = nixio.fs.access("/etc/config/appfilter")
+ local has_access_control = nixio.fs.access("/etc/config/macfilter")
+ local has_record = nixio.fs.access("/etc/config/fwx_record")
+
+ if not has_app_filter and not has_access_control and not has_record then
+ return
+ end
+
+ entry({"admin", "services", "oaf", "whitelist"}, cbi("oaf/whitelist", {hideapplybtn=true, hidesavebtn=true, hideresetbtn=true}), _("Whitelist"), 50).leaf = true
+ entry({"admin", "services", "oaf", "api", "record_whitelist", "get_record_whitelist"}, call("get_record_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "record_whitelist", "get_record_whitelist_all"}, call("get_record_whitelist_all"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "record_whitelist", "add_record_whitelist"}, call("add_record_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "record_whitelist", "del_record_whitelist"}, call("del_record_whitelist"), nil).leaf = true
+ entry({"admin", "services", "oaf", "api", "record_whitelist", "get_all_users"}, call("get_all_users"), nil).leaf = true
+end
+
+function get_record_whitelist()
+ local page = tonumber(luci.http.formvalue("page") or "1") or 1
+ local page_size = tonumber(luci.http.formvalue("page_size") or "200") or 200
+
+ if page < 1 then page = 1 end
+ if page_size < 1 then page_size = 200 end
+ if page_size > 200 then page_size = 200 end
+
+ local resp_obj = get_record_whitelist_page(page, page_size)
+ if resp_obj and resp_obj.code == 2000 and resp_obj.data then
+ write_json({code = 2000, data = resp_obj.data, message = "success"})
+ else
+ write_json({code = 2000, data = {total_num = 0, total_page = 1, page = page, page_size = page_size, list = {}}, message = "success"})
+ end
+end
+
+function get_record_whitelist_all()
+ local all_list = {}
+ local total_page = 1
+ local page = 1
+ local page_size = 200
+
+ while page <= total_page do
+ local resp_obj = get_record_whitelist_page(page, page_size)
+ if not (resp_obj and resp_obj.code == 2000 and resp_obj.data and type(resp_obj.data.list) == "table") then
+ break
+ end
+
+ local i
+ for i = 1, #resp_obj.data.list do
+ all_list[#all_list + 1] = resp_obj.data.list[i]
+ end
+
+ total_page = tonumber(resp_obj.data.total_page or 1) or 1
+ if total_page < 1 then total_page = 1 end
+ page = page + 1
+ end
+
+ write_json({code = 2000, data = {list = all_list}, message = "success"})
+end
+
+function add_record_whitelist()
+ local data_str = luci.http.formvalue("data")
+ local mac = normalize_mac(luci.http.formvalue("mac"))
+ local mac_list = {}
+
+ if data_str and data_str ~= "" then
+ local parsed = jsonc.parse(data_str)
+ if parsed and type(parsed.mac_list) == "table" then
+ local i
+ for i = 1, #parsed.mac_list do
+ local one_mac = normalize_mac(parsed.mac_list[i])
+ if is_valid_mac(one_mac) then
+ mac_list[#mac_list + 1] = one_mac
+ end
+ end
+ end
+ end
+
+ if #mac_list == 0 and is_valid_mac(mac) then
+ mac_list = {mac}
+ end
+
+ if #mac_list == 0 then
+ write_json({code = 1, message = "Invalid mac_list"})
+ return
+ end
+
+ local resp_obj = util.ubus("fwx", "common", {
+ api = "add_record_whitelist",
+ data = {mac_list = mac_list}
+ })
+
+ if resp_obj and resp_obj.code == 2000 then
+ write_json({code = 2000, message = "Whitelist added successfully"})
+ else
+ write_json({code = 1, message = "Failed to add whitelist"})
+ end
+end
+
+function del_record_whitelist()
+ local mac = normalize_mac(luci.http.formvalue("mac"))
+
+ if not is_valid_mac(mac) then
+ write_json({code = 1, message = "Invalid mac address"})
+ return
+ end
+
+ local resp_obj = util.ubus("fwx", "common", {
+ api = "del_record_whitelist",
+ data = {mac = mac}
+ })
+
+ if resp_obj and resp_obj.code == 2000 then
+ write_json({code = 2000, message = "Whitelist deleted successfully"})
+ else
+ write_json({code = 1, message = "Failed to delete whitelist"})
+ end
+end
+
+function get_all_users()
+ local all_list = {}
+ local total_page = 1
+ local page = 1
+ local page_size = 200
+
+ while page <= total_page do
+ local req_obj = {
+ api = "get_all_users",
+ data = {
+ flag = 2,
+ page = page,
+ page_size = page_size
+ }
+ }
+
+ local resp_obj = util.ubus("fwx", "common", req_obj)
+ if not (resp_obj and resp_obj.code == 2000 and resp_obj.data and type(resp_obj.data.list) == "table") then
+ break
+ end
+
+ local i
+ for i = 1, #resp_obj.data.list do
+ all_list[#all_list + 1] = resp_obj.data.list[i]
+ end
+
+ total_page = tonumber(resp_obj.data.total_page or 1) or 1
+ if total_page < 1 then total_page = 1 end
+ page = page + 1
+ end
+
+ write_json({code = 2000, data = {list = all_list}, message = "success"})
+end
\ No newline at end of file
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/feature.lua b/luci-app-oaf/luasrc/model/cbi/appfilter/feature.lua
deleted file mode 100644
index 690d531f..00000000
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/feature.lua
+++ /dev/null
@@ -1,177 +0,0 @@
-
-local nfs = require "nixio.fs"
-local sys = require "luci.sys"
-local SYS = require "luci.sys"
-local http = luci.http
-
-local m, s
-
-m = Map("appfilter", translate("App Feature Library"), translate("The App feature library is used to describe the packet protocol of applications, including port, domain, and Layer7 payload. It is the core of the DPI engine and affects the effectiveness of OAF. You can also add or modify App features according to the official website tutorial."))
-s = m:section(SimpleSection)
-s.template = "admin_network/feature"
-s.anonymous = true
-
-local dir, fd
-dir = "/tmp/upload/"
-nixio.fs.mkdir(dir)
-
-local STATUS_FILE = "/tmp/feature_upgrade.status"
-local MAX_SIZE = 20 * 1024 * 1024
-
-local function write_status(code)
- local f = io.open(STATUS_FILE, "w+")
- if f then
- f:write(tostring(code))
- f:close()
- end
-end
-
-local function log(msg)
- local f = io.open("/tmp/log/luci.log", "a+")
- if f then
- f:write(os.date("%Y-%m-%d %H:%M:%S") .. " [fwx_feature_upload] " .. tostring(msg) .. "\n")
- f:close()
- end
-end
-
-local function get_overlay_free_space()
- local df_output = SYS.exec("df -k /overlay 2>/dev/null | tail -1 | awk '{print $4}'")
- if df_output then
- df_output = string.gsub(df_output, "%s+", "")
- local free_kb = tonumber(df_output)
- if free_kb then
- return free_kb * 1024 -- 转换为字节
- end
- end
- return 0
-end
-
-local function get_dir_size(dir_path)
- local du_output = SYS.exec("du -sb " .. dir_path .. " 2>/dev/null | awk '{print $1}'")
- if du_output then
- du_output = string.gsub(du_output, "%s+", "")
- local size_bytes = tonumber(du_output)
- if size_bytes then
- return size_bytes
- end
- end
- return 0
-end
-
-http.setfilehandler(function(meta, chunk, eof)
- local feature_file = "/etc/appfilter/feature.cfg"
- local f_format = "v3.0"
- local format = "v3.0"
- if not fd then
- if not meta then
- return
- end
- if meta and chunk then
- log("start upload filename=" .. (meta.file or ""))
- fd = nixio.open(dir .. meta.file, "w")
- write_status(1)
- end
- if not fd then
- log("open file failed: " .. (dir .. (meta.file or "")))
- write_status(401)
- return
- end
- end
- if chunk and fd then
- fd:write(chunk)
- end
- if eof and fd then
- fd:close()
- log("upload finished, saved to " .. dir .. (meta.file or ""))
- local meta_size = 0
- do
- local file_path = dir .. (meta.file or "")
- local stat = nixio.fs.stat(file_path)
- if stat and stat.size then
- meta_size = stat.size
- end
- end
- log("meta_size: " .. tostring(meta_size) .. ", MAX_SIZE: " .. tostring(MAX_SIZE))
- if meta_size > MAX_SIZE then
- log("file too large: " .. tostring(meta_size))
- write_status(402)
- os.execute("rm /tmp/upload/* -fr")
- return
- end
-
- local tar_cmd = "tar -zxvf /tmp/upload/" .. meta.file .. " -C /tmp/upload/ >/dev/null"
- local success = os.execute(tar_cmd)
- if success ~= 0 then
- log("tar extract failed: " .. tar_cmd)
- write_status(401)
- return
- end
-
- local feature_dir = "/tmp/upload/feature"
- local fd2 = io.open("/tmp/upload/feature.cfg")
- if not fd2 then
- log("feature.cfg not found after extract")
- write_status(401)
- os.execute("rm /tmp/upload/* -fr")
- return
- end
- local version_line = fd2:read("*l")
- local format_line = fd2:read("*l")
- fd2:close()
- local ret = string.match(version_line, "#version")
- if ret ~= nil then
- if string.match(format_line, "#format") then
- f_format = SYS.exec("echo '"..format_line.."'|awk '{print $2}'")
- end
- if not string.match(f_format, format) then
- log("format mismatch: got " .. f_format .. ", expected " .. format)
- write_status(401)
- os.execute("rm /tmp/upload/* -fr")
- return
- end
- local cmd = "cp /tmp/upload/feature.cfg " .. feature_file
- os.execute(cmd)
-
- local app_icons_src = "/tmp/upload/app_icons"
- local app_icons_dst = "/www/luci-static/resources/app_icons"
-
- if nixio.fs.stat(app_icons_src) then
- local app_icons_size = get_dir_size(app_icons_src)
- local overlay_free = get_overlay_free_space()
-
- log("app_icons size: " .. tostring(app_icons_size) .. " bytes, overlay free: " .. tostring(overlay_free) .. " bytes")
-
- if overlay_free >= app_icons_size then
- log("overlay space sufficient, copying app_icons to /www")
- os.execute("rm -rf " .. app_icons_dst .. "/*")
- cmd = "cp -r " .. app_icons_src .. "/* " .. app_icons_dst .. "/ >/dev/null 2>&1"
- os.execute(cmd)
- log("app_icons copied to /www/luci-static/resources/app_icons")
- else
- log("overlay space insufficient (" .. tostring(overlay_free) .. " < " .. tostring(app_icons_size) .. "), skipping app_icons copy")
- end
- else
- log("app_icons directory not found in upload package, skipping")
- end
- os.execute("chmod 666 " .. feature_file)
- luci.sys.exec("killall -SIGUSR1 oafd")
- log("feature updated successfully")
- write_status(200)
- else
- log("missing #version marker")
- write_status(401)
- end
- os.execute("rm /tmp/upload/* -fr")
- end
-
-end)
-
-if luci.http.formvalue("upload") then
- local f = luci.http.formvalue("ulfile")
- if #f <= 0 then
- end
-elseif luci.http.formvalue("download") then
-end
-
-return m
-
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/time_setting.lua b/luci-app-oaf/luasrc/model/cbi/appfilter/time_setting.lua
deleted file mode 100644
index 52a8aa96..00000000
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/time_setting.lua
+++ /dev/null
@@ -1,49 +0,0 @@
-local ds = require "luci.dispatcher"
-local nxo = require "nixio"
-local nfs = require "nixio.fs"
-local ipc = require "luci.ip"
-local sys = require "luci.sys"
-local utl = require "luci.util"
-local dsp = require "luci.dispatcher"
-local uci = require "luci.model.uci"
-local lng = require "luci.i18n"
-local jsc = require "luci.jsonc"
-local http = luci.http
-local SYS = require "luci.sys"
-local m, s
-
-m = Map("appfilter", translate(""), translate(""))
-
-s = m:section(TypedSection, "time", translate("Time Setting"),translate("The second time is optional, the end time must be greater than the start time"))
-s.anonymous = true
-
-
-o=s:option(ListValue, "time_mode", translate("Time Mode"),translate(""))
-o.default=0
-o:value(0,translate("Blacklist mode"))
-o:value(1,translate("Whitelist mode"))
-
-days = s:option(MultiValue, "days", "", translate(""))
-days.widget = "checkbox"
-days.size = 10
-days:value("0", translate("Sun"));
-days:value("1", translate("Mon"));
-days:value("2", translate("Tue"));
-days:value("3", translate("Wed"));
-days:value("4", translate("Thur"));
-days:value("5", translate("Fri"));
-days:value("6", translate("Sat"));
-
-hv = s:option(Value, "start_time", translate("Start Time1"),translate("xx:xx"))
-hv.optional = false
-hv = s:option(Value, "end_time", translate("End Time1"))
-hv.optional = false
-
-hv = s:option(Value, "start_time2", translate("Start Time2"))
-hv.optional = false
-hv = s:option(Value, "end_time2", translate("End Time2"))
-hv.optional = false
-
-
-
-return m
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/app_filter.lua b/luci-app-oaf/luasrc/model/cbi/oaf/app_filter/rules.lua
similarity index 81%
rename from luci-app-oaf/luasrc/model/cbi/appfilter/app_filter.lua
rename to luci-app-oaf/luasrc/model/cbi/oaf/app_filter/rules.lua
index 1961a489..d9c51577 100644
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/app_filter.lua
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/app_filter/rules.lua
@@ -11,10 +11,10 @@ local jsc = require "luci.jsonc"
local m, s
arg[1] = arg[1] or ""
-m = Map("appfilter", translate(""), translate(""))
+m = Map("app_filter", translate(""), translate(""))
local v
v = m:section(SimpleSection)
-v.template = "admin_network/app_filter"
+v.template = "oaf/app_filter/rules"
return m
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/time.lua b/luci-app-oaf/luasrc/model/cbi/oaf/dashboard.lua
similarity index 82%
rename from luci-app-oaf/luasrc/model/cbi/appfilter/time.lua
rename to luci-app-oaf/luasrc/model/cbi/oaf/dashboard.lua
index baa1afca..31947d2a 100644
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/time.lua
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/dashboard.lua
@@ -11,9 +11,9 @@ local jsc = require "luci.jsonc"
local m, s
arg[1] = arg[1] or ""
-m = Map("appfilter", translate(""), translate(""))
+m = Map("fwx_dashboard", translate(""), translate(""))
local v
v = m:section(SimpleSection)
-v.template = "admin_network/time"
+v.template = "oaf/dashboard"
return m
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/dev_status.lua b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/mac_filter.lua
similarity index 65%
rename from luci-app-oaf/luasrc/model/cbi/appfilter/dev_status.lua
rename to luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/mac_filter.lua
index 90d64024..a48a8a59 100644
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/dev_status.lua
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/mac_filter.lua
@@ -11,11 +11,10 @@ local jsc = require "luci.jsonc"
local m, s
arg[1] = arg[1] or ""
-m = Map("appfilter", translate("Data Statistics") .. "(" .. arg[1] .. ")", translate(""))
+m = Map("macfilter", translate(""), translate(""))
+
local v
v = m:section(SimpleSection)
-v.template = "admin_network/dev_status"
-v.mac = arg[1]
-m.redirect = luci.dispatcher.build_url("admin", "services", "appfilter")
+v.template = "oaf/mac_filter/mac_filter"
return m
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/advance.lua b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/rules.lua
similarity index 82%
rename from luci-app-oaf/luasrc/model/cbi/appfilter/advance.lua
rename to luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/rules.lua
index 966950b8..f39c0dc1 100644
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/advance.lua
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/rules.lua
@@ -11,9 +11,9 @@ local jsc = require "luci.jsonc"
local m, s
arg[1] = arg[1] or ""
-m = Map("appfilter", translate(""), translate(""))
+m = Map("macfilter", translate(""), translate(""))
local v
v = m:section(SimpleSection)
-v.template = "admin_network/advance"
+v.template = "oaf/mac_filter/rules"
return m
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/user.lua b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/user.lua
similarity index 80%
rename from luci-app-oaf/luasrc/model/cbi/appfilter/user.lua
rename to luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/user.lua
index 29c02409..2719dbd0 100644
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/user.lua
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/mac_filter/user.lua
@@ -11,9 +11,9 @@ local jsc = require "luci.jsonc"
local m, s
arg[1] = arg[1] or ""
-m = Map("appfilter", translate(""), translate(""))
+m = Map("macfilter", translate(""), translate(""))
local v
v = m:section(SimpleSection)
-v.template = "admin_network/user"
-return m
\ No newline at end of file
+v.template = "oaf/mac_filter/user"
+return m
diff --git a/luci-app-oaf/luasrc/model/cbi/oaf/user_detail.lua b/luci-app-oaf/luasrc/model/cbi/oaf/user_detail.lua
new file mode 100644
index 00000000..04aae98b
--- /dev/null
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/user_detail.lua
@@ -0,0 +1,9 @@
+local ds = require "luci.dispatcher"
+local m, s
+
+m = Map("appfilter", translate(""), translate(""))
+
+m:section(SimpleSection).template = "oaf/user_detail"
+
+return m
+
diff --git a/luci-app-oaf/luasrc/model/cbi/appfilter/user_list.lua b/luci-app-oaf/luasrc/model/cbi/oaf/user_list.lua
similarity index 87%
rename from luci-app-oaf/luasrc/model/cbi/appfilter/user_list.lua
rename to luci-app-oaf/luasrc/model/cbi/oaf/user_list.lua
index 6a0366b2..1a458534 100644
--- a/luci-app-oaf/luasrc/model/cbi/appfilter/user_list.lua
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/user_list.lua
@@ -14,6 +14,6 @@ local m, s
m = Map("appfilter", translate(""), translate(""))
-m:section(SimpleSection).template = "admin_network/user_status"
+m:section(SimpleSection).template = "oaf/user_status"
return m
diff --git a/luci-app-oaf/luasrc/model/cbi/oaf/whitelist.lua b/luci-app-oaf/luasrc/model/cbi/oaf/whitelist.lua
new file mode 100644
index 00000000..ec7c8886
--- /dev/null
+++ b/luci-app-oaf/luasrc/model/cbi/oaf/whitelist.lua
@@ -0,0 +1,15 @@
+local nfs = require "nixio.fs"
+local config = "fwx_record"
+
+if nfs.access("/etc/config/appfilter") then
+ config = "appfilter"
+elseif nfs.access("/etc/config/macfilter") then
+ config = "macfilter"
+end
+
+local m = Map(config, translate(""), translate(""))
+local s = m:section(SimpleSection)
+
+s.template = "oaf/whitelist"
+
+return m
\ No newline at end of file
diff --git a/luci-app-oaf/luasrc/view/admin_network/advance.htm b/luci-app-oaf/luasrc/view/admin_network/advance.htm
deleted file mode 100644
index 26618fc4..00000000
--- a/luci-app-oaf/luasrc/view/admin_network/advance.htm
+++ /dev/null
@@ -1,321 +0,0 @@
-
-
-
-
-
-
-
-
<%:Settings saved successfully%>
-
-
-
-
-
-
-
-
- --
-
-
-
-
- --
-
-
-
-
-
-
-
-
- <%:If the OAF driver cannot be manually unloaded or the current driver is unstable, you can turn off auto-loading at startup and manually install a suitable driver. It is recommended to use the official stable OpenWrt firmware.%>
-
-
-
-
-
-
-
-
- <%:Disable hardware acceleration (HNAT/ECM) and flow offloading to ensure application filtering works correctly. When enabled, this will automatically disable hardware acceleration and flow offloading features.%>
-
-
-
-
-
-
-
-
- <%:The name of the LAN interface, used for detecting client info, supports fuzzy matching, but a complete interface name must be specified in bypass mode %>
-
- <%:App Filter is a powerful parental control software%>
-
- ?
-
- <%:OAF is now relatively stable. If the test fails, please disable ad filtering, proxy, acceleration, and other modules. If you are unsure which modules are conflicting, it is recommended to reset the device and then disable the acceleration module in the firewall for testing. If only some Apps are not working, you need to update the App feature library, as the filtering effect is related to the App feature library.%>
-
-
-
-
-
-
- <%:Running%>
-
-
-
-
-
- <%:Current hardware acceleration module is not disabled, which may affect filtering functionality. For better filtering results, please disable the acceleration module in Advanced Settings.%>
-
-
-
-
-
-
- ⏱ <%:Today Time Usage%>
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:Basic Settings%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:In bypass mode, down rate cannot be measured, because data is directly forwarded from the gateway to the terminal.%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:Filter Rules%>
-
-
-
-
-
-
-
-
-
-
-
- <%:In this mode, after the rule takes effect, all apps will be unable to connect to the network, similar to scheduled internet disconnection%>
-
-
-
-
-
-
-
-
-
- <%:Some Apps use encrypted QUIC protocol, which cannot be distinguished for filtering, Such as Youtube, Instagram, etc. For better filtering results, you can try disabling the QUIC protocol.%>
-
- <%:You can download the App feature library from the website(www.openappfilter.com),then upload it here.Please note that after downloading, you need to extract the files and select the one in .bin format.%>
-
- <%:Dynamic time mode refers to dynamically adjusting app filter switches, such as allowing children to play games for 20 minutes after studying for 1 hour, automatically enabling filtering after exceeding entertainment time, and repeating the cycle.%>
-
-
-
-
-
-
- <%:minutes%>
-
-
-
-
- <%:minutes%>
-
-
-
-
-
- <%:Advanced Settings%>
-
-
-
-
-
<%:Mon%>
-
<%:Tue%>
-
<%:Wed%>
-
<%:Thur%>
-
<%:Fri%>
-
<%:Sat%>
-
<%:Sun%>
-
-
-
-
-
-
- -
-
-
-
- <%:This time range represents the daily school time period, which is a continuous time period. During this time period, filtering rules are dynamically enabled and disabled. Outside the time range, filtering is disabled by default.%>
-
-
-
-
-
-
-
-
-
- <%:Daily time limit mode allows you to set a daily internet time limit for each weekday. Once the limit is reached, internet access will be restricted.%>
-
-
-
-
-
-
- <%:Today Time Usage%>
-
-
-
-
-
-
-
-
-
-
-
-
<%:Weekday%>
-
<%:Enable%>
-
<%:Morning Time Limit (minutes)%>
-
<%:Afternoon Time Limit (minutes)%>
-
-
-
-
-
<%:Monday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Tuesday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Wednesday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Thursday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Friday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Saturday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<%:Sunday%>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:Note: Set to 0 to disable time limit for that period.%>
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/luci-app-oaf/luasrc/view/admin_network/user.htm b/luci-app-oaf/luasrc/view/admin_network/user.htm
deleted file mode 100644
index 7f07a40e..00000000
--- a/luci-app-oaf/luasrc/view/admin_network/user.htm
+++ /dev/null
@@ -1,768 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <%:In manual mode, only the following added terminals are controlled%>
-
-
diff --git a/luci-app-oaf/luasrc/view/cbi/oaf_dvalue.htm b/luci-app-oaf/luasrc/view/cbi/oaf_dvalue.htm
deleted file mode 100644
index adf6a2b3..00000000
--- a/luci-app-oaf/luasrc/view/cbi/oaf_dvalue.htm
+++ /dev/null
@@ -1,10 +0,0 @@
-<%+cbi/valueheader%>
-
-<%
- local val = self:cfgvalue(section) or self.default or ""
- write(pcdata(val))
-%>
-
-
-
-<%+cbi/valuefooter%>
diff --git a/luci-app-oaf/luasrc/view/cbi/oaf_upload.htm b/luci-app-oaf/luasrc/view/cbi/oaf_upload.htm
deleted file mode 100644
index bdbae284..00000000
--- a/luci-app-oaf/luasrc/view/cbi/oaf_upload.htm
+++ /dev/null
@@ -1,32 +0,0 @@
-<%+cbi/valueheader%>
-
-
-
-
-
-
-
-
-
-
<%:Updating, please wait...%>
-
-
-
- <%:Feature library files can be downloaded from the official website. After downloading, upload to upgrade. Note the feature code format version, which needs to be consistent with the current feature code format!%>
-
-<%+cbi/valuefooter%>
-
-
-
-
diff --git a/luci-app-oaf/luasrc/view/oaf/.dashboard.htm.swp b/luci-app-oaf/luasrc/view/oaf/.dashboard.htm.swp
new file mode 100644
index 00000000..c0245d23
Binary files /dev/null and b/luci-app-oaf/luasrc/view/oaf/.dashboard.htm.swp differ
diff --git a/luci-app-oaf/luasrc/view/oaf/about.htm b/luci-app-oaf/luasrc/view/oaf/about.htm
new file mode 100644
index 00000000..7d5ecb48
--- /dev/null
+++ b/luci-app-oaf/luasrc/view/oaf/about.htm
@@ -0,0 +1,128 @@
+<%+header%>
+
+
+
+
+
OpenAppFilter
+
+ <%:OAF is a powerful parental control software. FanchmWrt integrates OAF by default and includes more plugins developed by the author.%>
+
<%:Bypass mode cannot count downstream traffic because traffic is directly forwarded to terminals at Layer 2 through the gateway.%>
+
<%:In bypass mode, it is recommended to disable IPv6 on terminals to prevent terminals from forwarding directly to the main router through IPv6.%>
+
+
+
+
+
+
+
+
<%:LAN interface name for terminal detection. Default is bridge interface (br-lan). If LAN port is changed to physical interface, please modify to the corresponding name, such as eth0.%>
+
+
+
+
+
+
+
+
<%:After enabled, OAF sends TCP RST for filtered TCP connections.%>
+
+
+
+ <% if has_app_filter then %>
+
+
+
+
+ <% end %>
+
+ <% if has_access_control then %>
+
+
+
+
+ <% end %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<%:Internet record retention time%>
+
+
+
+
+
+
+
+
<%:App records minimum duration%>
+
+
+
+
+
+
+
+
+ MB
+
+
<%:History data size limit%>
+
+
+
+
+
+
+
+
<%:User's App record data will be stored in this directory.%>
+
+
+
+
+
+
+
+
<%:The user's basic data and real-time data are stored in this directory, such as online duration, traffic usage and other information. The default is a temporary directory and will not be retained after reboot. You can change it to a persistent directory, such as /etc/fwx.%>
+
+
+
+
+
+
+
+
+
+
+
+
+
<%:After enabled, related acceleration modules will be automatically disabled, including hardware acceleration, software acceleration and others, to prevent filtering failures caused by acceleration. It is recommended to restart the device after modification.%>
+ <%:Tip%>:
+ <%:Some modules may affect filtering and identification. If it does not work, please disable related functions such as software and hardware acceleration, ad filtering, mwan3, QoS, etc.%>
+
<%:Visit%> www.openappfilter.com <%:to obtain the advanced feature library update Key.%>
+
+
+
+
+
+
+
+
+
+
+
+
<%:When the Key is empty, the free version can be used. After setting the Key, the premium version can be used. The premium version supports more applications, is updated regularly, and the latest supported application list can be viewed on the official website.%>
+ <%:Users in the whitelist are not subject to control. For terminals using random MAC addresses, you can use the whitelist mechanism together with filtering rules: select All Users in a filtering rule, and the rule will apply to all new users.%>
+
+
+
+ <% if has_app_filter then %>
+
+ <% end %>
+ <% if has_access_control then %>
+
+ <% end %>
+ <% if has_record then %>
+
+ <% end %>
+
+
+ <% if has_app_filter then %>
+
+
+
<%:App Filter Whitelist%>
+
+
+
+
+
<%:No.%>
<%:MAC Address%>
<%:Hostname%>
<%:Nickname%>
<%:Operation%>
+
<%:Loading...%>
+
+
+
+ <% end %>
+
+ <% if has_access_control then %>
+
+
+
<%:Access Control Whitelist%>
+
+
+
+
+
<%:No.%>
<%:MAC Address%>
<%:Hostname%>
<%:Nickname%>
<%:Operation%>
+
<%:Loading...%>
+
+
+
+ <% end %>
+
+ <% if has_record then %>
+
+
+
<%:Record Whitelist%>
+
+
+
+
+
<%:No.%>
<%:MAC Address%>
<%:Hostname%>
<%:Nickname%>
<%:Operation%>
+
<%:Loading...%>
+
+
+
+ <% end %>
+
+
+
+
+
<%:Add to Whitelist%>
+
+
+ <%:Selected%>: 0 / 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/luci-app-oaf/po/zh_Hans/oaf.po b/luci-app-oaf/po/zh_Hans/oaf.po
index d0b30ae1..488464c4 100644
--- a/luci-app-oaf/po/zh_Hans/oaf.po
+++ b/luci-app-oaf/po/zh_Hans/oaf.po
@@ -126,6 +126,17 @@ msgstr "刷新"
msgid "Visiting"
msgstr "正在访问"
+msgid "Band"
+msgstr "频段"
+
+msgid "Wireless Interface"
+msgstr "无线接口"
+
+msgid "Wireless Tx Rate"
+msgstr "无线发送速率"
+
+msgid "Wireless Rx Rate"
+msgstr "无线接收速率"
msgid "Update the feature file successfully, please refresh the page"
msgstr "更新特征库成功,请刷新页面!"
@@ -506,6 +517,9 @@ msgstr "暂无用户"
msgid "Users in whitelist are not controlled"
msgstr "白名单中的用户不受管控"
+msgid "Users in the whitelist are not subject to control. For terminals using random MAC addresses, you can use the whitelist mechanism together with filtering rules: select All Users in a filtering rule, and the rule will apply to all new users."
+msgstr "白名单中的用户不受管控。对于使用随机MAC地址的终端,可以通过白名单机制配合过滤规则实现控制:在过滤规则中选择全部用户,该规则会针对所有新用户生效。"
+
msgid "Whitelist Users"
msgstr "白名单用户"
@@ -817,3 +831,1131 @@ msgstr "禁用硬件加速(HNAT/ECM)和流加速,以确保应用过滤功
msgid "Current hardware acceleration module is not disabled, which may affect filtering functionality. For better filtering results, please disable the acceleration module in Advanced Settings."
msgstr "当前未关闭加速模块,可能影响过滤功能,为了更好的过滤效果,请在高级设置中关闭加速模块。"
+
+msgid "Version"
+msgstr "版本"
+
+msgid "Service Version"
+msgstr "服务版本"
+
+msgid "OAF Status"
+msgstr "OAF状态"
+
+msgid "Engine Version"
+msgstr "引擎版本"
+
+msgid "Internet Audit"
+msgstr "上网审计"
+
+msgid "Feature Library"
+msgstr "特征库"
+
+msgid "Feature Library Type"
+msgstr "特征库类型"
+
+msgid "Feature Format"
+msgstr "特征码格式"
+
+msgid "App Count"
+msgstr "应用个数"
+
+msgid "App Record Count"
+msgstr "应用记录数"
+
+msgid "Enabled"
+msgstr "开启"
+
+msgid "Disabled"
+msgstr "关闭"
+
+msgid "Lite"
+msgstr "Lite"
+
+msgid "Plus"
+msgstr "Plus"
+
+msgid "Free"
+msgstr "免费版"
+
+msgid "Premium"
+msgstr "高级版"
+
+msgid "Last 30 Days Traffic"
+msgstr "近30天流量"
+
+msgid "Traffic Statistics"
+msgstr "流量统计"
+
+msgid "Today"
+msgstr "今日"
+
+msgid "Last 30 Days"
+msgstr "近30天"
+
+msgid "Active Terminals"
+msgstr "活跃终端"
+
+msgid "Active Users"
+msgstr "活跃用户"
+
+msgid "Online App"
+msgstr "在线应用"
+
+msgid "Offline App"
+msgstr "离线应用"
+
+msgid "Online User"
+msgstr "在线用户"
+
+msgid "Offline User"
+msgstr "离线用户"
+
+msgid "Access Control Rules"
+msgstr "上网控制规则"
+
+msgid "Access Time"
+msgstr "访问时间"
+
+msgid "Active Apps"
+msgstr "活跃APP"
+
+msgid "Active Terminals Top8"
+msgstr "活跃终端Top8"
+
+msgid "Active URL"
+msgstr "活跃URL"
+
+msgid "App Duration Statistics"
+msgstr "App时长统计"
+
+msgid "App ID"
+msgstr "APP ID"
+
+msgid "App Usage Distribution"
+msgstr "APP 使用时长分布"
+
+msgid "Boot Space"
+msgstr "Boot空间"
+
+msgid "Close"
+msgstr "关闭"
+
+msgid "Connections"
+msgstr "连接数"
+
+msgid "Current App"
+msgstr "当前APP"
+
+msgid "Day"
+msgstr "天"
+
+msgid "Destination IP"
+msgstr "目的IP地址"
+
+msgid "Destination Port"
+msgstr "目的端口"
+
+msgid "Device"
+msgstr "终端"
+
+msgid "Device Model"
+msgstr "设备型号"
+
+msgid "Disk Space"
+msgstr "磁盘空间"
+
+msgid "DNS"
+msgstr "DNS"
+
+msgid "Downstream"
+msgstr "下行"
+
+msgid "Downstream Rate"
+msgstr "下行速率"
+
+msgid "Downstream Traffic"
+msgstr "下行流量"
+
+msgid "Duplex"
+msgstr "双工"
+
+msgid "Expanded"
+msgstr "扩容版"
+
+msgid "Firmware Version"
+msgstr "固件版本"
+
+msgid "Gateway"
+msgstr "网关"
+
+msgid "Got it"
+msgstr "知道了"
+
+msgid "Hour"
+msgstr "小时"
+
+msgid "IP"
+msgstr "IP"
+
+msgid "Internet Duration"
+msgstr "上网时长"
+
+msgid "Internet Traffic"
+msgstr "上网流量"
+
+msgid "Kernel Version"
+msgstr "内核版本"
+
+msgid "Last Hour"
+msgstr "近1小时"
+
+msgid "Mask"
+msgstr "掩码"
+
+msgid "Matched Domain"
+msgstr "匹配域名"
+
+msgid "Memory"
+msgstr "内存"
+
+msgid "Minute"
+msgstr "分"
+
+msgid "Name"
+msgstr "名称"
+
+msgid "Network"
+msgstr "网络"
+
+msgid "Net Time"
+msgstr "上网时长"
+
+msgid "Net Traffic"
+msgstr "上网流量"
+
+msgid "No Data"
+msgstr "暂无数据"
+
+msgid "Port Status"
+msgstr "网口状态"
+
+msgid "Protocol"
+msgstr "协议"
+
+msgid "Permission"
+msgstr "权限"
+
+msgid "Quick Guide"
+msgstr "设置向导"
+
+msgid "Real-time Traffic"
+msgstr "实时流量"
+
+msgid "Rx Bytes"
+msgstr "接收字节"
+
+msgid "Rx Error Packets"
+msgstr "接收错误包数"
+
+msgid "Rx Packets"
+msgstr "接收包数"
+
+msgid "Some modules may affect filtering and identification. If it does not work, please disable related functions such as software and hardware acceleration, ad filtering, mwan3, QoS, etc."
+msgstr "某些模块可能会影响过滤和识别。如果不生效,请关闭软件/硬件加速、广告过滤、mwan3、QoS 等相关功能。"
+
+msgid "Source IP"
+msgstr "源IP地址"
+
+msgid "Source Port"
+msgstr "源端口"
+
+msgid "Speed"
+msgstr "速率"
+
+msgid "Status"
+msgstr "状态"
+
+msgid "System Info"
+msgstr "系统信息"
+
+msgid "Temp Space"
+msgstr "临时空间"
+
+msgid "Temperature"
+msgstr "温度"
+
+msgid "Tip"
+msgstr "提示"
+
+msgid "Tip: Click here to switch modes."
+msgstr "提示:点击这里可以切换模式。"
+
+msgid "Today Traffic"
+msgstr "今日流量"
+
+msgid "Tx Bytes"
+msgstr "发送字节"
+
+msgid "Tx Error Packets"
+msgstr "发送错误包数"
+
+msgid "URL"
+msgstr "URL"
+
+msgid "Tx Packets"
+msgstr "发送包数"
+
+msgid "Unknown"
+msgstr "未知"
+
+msgid "Upload"
+msgstr "上传"
+
+msgid "Upstream"
+msgstr "上行"
+
+msgid "Upstream Rate"
+msgstr "上行速率"
+
+msgid "Upstream Traffic"
+msgstr "上行流量"
+
+msgid "Uptime"
+msgstr "运行时间"
+
+msgid "User"
+msgstr "用户"
+
+msgid "User Traffic Top8"
+msgstr "用户流量Top8"
+
+msgid "Access Control"
+msgstr "上网控制"
+
+msgid "Added to blacklist"
+msgstr "已加入黑名单"
+
+msgid "All Users"
+msgstr "全部用户"
+
+msgid "App Restricted"
+msgstr "应用受限"
+
+msgid "Are you sure you want to join the blacklist? After joining, this terminal will be disconnected from the Internet."
+msgstr "确定要加入黑名单吗?加入后该终端将会断网。"
+
+msgid "Back"
+msgstr "返回"
+
+msgid "Back to User List"
+msgstr "返回终端列表"
+
+msgid "Blacklist"
+msgstr "黑名单"
+
+msgid "Block"
+msgstr "拉黑"
+
+msgid "Collecting data..."
+msgstr "正在采集数据..."
+
+msgid "Common App"
+msgstr "常用APP"
+
+msgid "Confirm"
+msgstr "确认"
+
+msgid "Current Status"
+msgstr "当前状态"
+
+msgid "Daily Duration"
+msgstr "每日时长"
+
+msgid "Daily Flow"
+msgstr "每日流量"
+
+msgid "Detail"
+msgstr "详情"
+
+msgid "Disconnected"
+msgstr "未联网"
+
+msgid "Edit Remark"
+msgstr "修改备注"
+
+msgid "Effective Condition"
+msgstr "生效条件"
+
+msgid "Failed to update remark"
+msgstr "备注修改失败"
+
+msgid "First Visit"
+msgstr "开始访问时间"
+
+msgid "Flow"
+msgstr "流量"
+
+msgid "Function Module"
+msgstr "功能模块"
+
+msgid "Hit Detail"
+msgstr "命中详情"
+
+msgid "Internet Permission"
+msgstr "联网权限"
+
+msgid "Internet Permission Detail"
+msgstr "联网权限详情"
+
+msgid "IPv6"
+msgstr "IPv6"
+
+msgid "Joined Internet Blacklist"
+msgstr "命中上网黑名单"
+
+msgid "Last Visit"
+msgstr "最后访问时间"
+
+msgid "Limit"
+msgstr "限制"
+
+msgid "Matched"
+msgstr "已匹配"
+
+msgid "Matched app filter whitelist, all app filter rules are ineffective."
+msgstr "匹配到应用过滤白名单,所有应用过滤规则不生效。"
+
+msgid "Matched MAC filter whitelist, all MAC filter rules are ineffective."
+msgstr "匹配到MAC过滤白名单,所有MAC过滤规则不生效。"
+
+msgid "Matched Rule"
+msgstr "匹配规则"
+
+msgid "Missing MAC parameter"
+msgstr "缺少MAC参数"
+
+msgid "Mode"
+msgstr "模式"
+
+msgid "Next Page"
+msgstr "下一页"
+
+msgid "No app usage in this period"
+msgstr "该时段无APP使用记录"
+
+msgid "No parental control rules"
+msgstr "暂无行为管理规则"
+
+msgid "No records"
+msgstr "暂无记录"
+
+msgid "Not effective today"
+msgstr "今日不生效"
+
+msgid "Not Matched"
+msgstr "未匹配"
+
+msgid "Offline Event"
+msgstr "离线"
+
+msgid "Online Event"
+msgstr "上线"
+
+msgid "Online/Offline Records"
+msgstr "上下线记录"
+
+msgid "Operation failed"
+msgstr "操作失败"
+
+msgid "Operation succeeded"
+msgstr "设置成功"
+
+msgid "Parental Control Rules"
+msgstr "行为管理规则"
+
+msgid "Prev Page"
+msgstr "上一页"
+
+msgid "Rate"
+msgstr "速率"
+
+msgid "Records"
+msgstr "条记录"
+
+msgid "Remark updated successfully"
+msgstr "备注修改成功"
+
+msgid "Removed from blacklist"
+msgstr "已解除黑名单"
+
+msgid "Restricted Apps"
+msgstr "受限应用"
+
+msgid "Rule"
+msgstr "规则"
+
+msgid "Rule Detail"
+msgstr "规则详情"
+
+msgid "Rule Name"
+msgstr "规则名称"
+
+msgid "Rule Type"
+msgstr "规则类型"
+
+msgid "s"
+msgstr "秒"
+
+msgid "Selected User"
+msgstr "指定终端"
+
+msgid "Session Count"
+msgstr "会话数"
+
+msgid "Sessions"
+msgstr "会话"
+
+msgid "Showing"
+msgstr "显示"
+
+msgid "Signal"
+msgstr "信号强度"
+
+msgid "Terminal"
+msgstr "终端"
+
+msgid "Terminal Detail"
+msgstr "终端详情"
+
+msgid "Thu"
+msgstr "周四"
+
+msgid "Time"
+msgstr "时间"
+
+msgid "Time Rule"
+msgstr "时间规则"
+
+msgid "Today Downstream Traffic"
+msgstr "今日下行流量"
+
+msgid "Today Internet Duration"
+msgstr "今日上网时长"
+
+msgid "Today Online Duration"
+msgstr "今日在线时长"
+
+msgid "Today Top Apps"
+msgstr "今日常用APP"
+
+msgid "Today Top Apps Statistics (24 Hours)"
+msgstr "今日常用APP统计(24小时)"
+
+msgid "Today Traffic Statistics (24 Hours)"
+msgstr "今日流量统计(24小时)"
+
+msgid "Today Upstream Traffic"
+msgstr "今日上行流量"
+
+msgid "Today's Status"
+msgstr "今日状态"
+
+msgid "Traffic"
+msgstr "流量"
+
+msgid "Type"
+msgstr "类型"
+
+msgid "Unblock"
+msgstr "解除"
+
+msgid "Unlimited today"
+msgstr "今日不限"
+
+msgid "Unrestricted"
+msgstr "无限制"
+
+msgid "User Detail"
+msgstr "终端详情"
+
+msgid "Visit Records"
+msgstr "访问记录"
+
+msgid "Visiting URL"
+msgstr "正在访问URL"
+
+msgid "0 apps"
+msgstr "0个应用"
+
+msgid "Access Control Whitelist"
+msgstr "上网控制白名单"
+
+msgid "Add Duration Rule"
+msgstr "添加时长规则"
+
+msgid "Add failed"
+msgstr "添加失败"
+
+msgid "Add Flow Rule"
+msgstr "添加流量规则"
+
+msgid "Add Rule"
+msgstr "添加规则"
+
+msgid "Add success"
+msgstr "添加成功"
+
+msgid "Add Time Rule"
+msgstr "添加时间规则"
+
+msgid "App Filter Whitelist"
+msgstr "应用过滤白名单"
+
+msgid "Apps"
+msgstr "应用"
+
+msgid "Are you sure you want to delete this rule?"
+msgstr "确定要删除这条规则吗?"
+
+msgid "Are you sure you want to remove this device from whitelist?"
+msgstr "确定要从白名单中移除此设备吗?"
+
+msgid "Auto mode"
+msgstr "自动模式"
+
+msgid "Daily Traffic"
+msgstr "每日流量"
+
+msgid "Delete failed"
+msgstr "删除失败"
+
+msgid "Delete success"
+msgstr "删除成功"
+
+msgid "Device nickname"
+msgstr "设备昵称"
+
+msgid "Disconnect Mode"
+msgstr "断网模式"
+
+msgid "Duration (minutes)"
+msgstr "时长(分钟)"
+
+msgid "Duration Rules"
+msgstr "时长规则"
+
+msgid "Edit"
+msgstr "编辑"
+
+msgid "Edit Rule"
+msgstr "编辑规则"
+
+msgid "Enable Rule"
+msgstr "启用规则"
+
+msgid "Enter rule name"
+msgstr "输入规则名称"
+
+msgid "Failed to add rule"
+msgstr "添加规则失败"
+
+msgid "Failed to delete rule"
+msgstr "删除规则失败"
+
+msgid "Failed to update rule"
+msgstr "更新规则失败"
+
+msgid "Filter QUIC"
+msgstr "过滤QUIC协议"
+
+msgid "Flow (MB)"
+msgstr "流量(MB)"
+
+msgid "Flow Rules"
+msgstr "流量规则"
+
+msgid "Hover to view time chart"
+msgstr "悬停查看时间图表"
+
+msgid "In auto mode, all new devices will be managed, including those with random MAC addresses"
+msgstr "自动模式下,所有新设备都将被管理,包括随机MAC地址的设备"
+
+msgid "In manual mode, only specified devices will be managed"
+msgstr "手动模式下,仅管理指定的设备"
+
+msgid "Loading app list..."
+msgstr "加载应用列表..."
+
+msgid "MAC"
+msgstr "MAC地址"
+
+msgid "Maximum 16 time rules allowed"
+msgstr "最大只能添加16条规则"
+
+msgid "Maximum 32 rules allowed. Please delete some rules before adding new ones."
+msgstr "最多只能添加32条规则,请先删除一些规则后再添加新规则。"
+
+msgid "Mode switched successfully"
+msgstr "模式切换成功"
+
+msgid "Network error"
+msgstr "网络错误"
+
+msgid "Nickname"
+msgstr "昵称"
+
+msgid "No available devices to add"
+msgstr "没有可添加的设备"
+
+msgid "No rules configured"
+msgstr "未配置规则"
+
+msgid "No whitelist entries yet"
+msgstr "暂无白名单条目"
+
+msgid "No."
+msgstr "序号"
+
+msgid "Operation"
+msgstr "操作"
+
+msgid "Please add at least one time rule"
+msgstr "请至少添加一条时间规则"
+
+msgid "Please enter rule name"
+msgstr "请输入规则名称"
+
+msgid "Please enter valid duration minutes"
+msgstr "请输入有效的时长分钟数"
+
+msgid "Please enter valid flow MB"
+msgstr "请输入有效的流量MB值"
+
+msgid "Please select a user"
+msgstr "请选择用户"
+
+msgid "Please select at least one app"
+msgstr "请至少选择一个应用"
+
+msgid "Please select at least one device"
+msgstr "请至少选择一个设备"
+
+msgid "Remove"
+msgstr "移除"
+
+msgid "Rule added successfully"
+msgstr "规则添加成功"
+
+msgid "Rule deleted successfully"
+msgstr "规则删除成功"
+
+msgid "Rule Mode"
+msgstr "规则模式"
+
+msgid "Rule updated successfully"
+msgstr "规则更新成功"
+
+msgid "Select Apps"
+msgstr "选择应用"
+
+msgid "Select User"
+msgstr "选择用户"
+
+msgid "Setting success"
+msgstr "设置成功"
+
+msgid "Single User"
+msgstr "单个用户"
+
+msgid "Some apps need QUIC disabled to take effect, such as taobao, youtube, etc. This may also affect other apps. Please choose this switch according to actual conditions."
+msgstr "少部分应用需要禁用QUIC协议才会有效果,比如taobao,youtube等,注意也可能影响其他应用,根据实际情况选择开关"
+
+msgid "T"
+msgstr "时间"
+
+msgid "Unknown error"
+msgstr "未知错误"
+
+msgid "Whitelist"
+msgstr "白名单"
+
+msgid "A maximum of 15 payload matches is allowed"
+msgstr "每个特征最多允许15个负载匹配项"
+
+msgid "A maximum of 16 features is allowed"
+msgstr "最多允许16个特征"
+
+msgid "A maximum of 64 custom applications is allowed"
+msgstr "最多允许添加64个自定义应用"
+
+msgid "A valid subscription is required"
+msgstr "Key没有权限"
+
+msgid "Add Application"
+msgstr "添加应用"
+
+msgid "Add Feature"
+msgstr "添加特征"
+
+msgid "After enabled, related acceleration modules will be automatically disabled, including hardware acceleration, software acceleration and others, to prevent filtering failures caused by acceleration. It is recommended to restart the device after modification."
+msgstr "启用后会自动关闭相关的加速模块,包括硬件加速、软件加速等,以防止加速导致过滤失效。修改后建议重启设备。"
+
+msgid "App"
+msgstr "应用"
+
+msgid "App records minimum duration"
+msgstr "应用记录最小时长"
+
+msgid "App Valid Time"
+msgstr "应用有效时长"
+
+msgid "Application Count"
+msgstr "应用个数"
+
+msgid "Application Features"
+msgstr "应用特征"
+
+msgid "Application Name"
+msgstr "应用名称"
+
+msgid "Applying"
+msgstr "正在应用"
+
+msgid "Are you sure you want to clean all history data? This action cannot be undone."
+msgstr "确认要清理全部历史数据吗?该操作不可恢复。"
+
+msgid "Are you sure you want to delete this application?"
+msgstr "确定要删除这个应用吗?"
+
+msgid "At least one match field must be configured for each feature"
+msgstr "每个特征至少需要配置一个匹配字段"
+
+msgid "Auto Disable Acceleration"
+msgstr "自动关闭加速"
+
+msgid "Base Data Path"
+msgstr "基础数据目录"
+
+msgid "Base data path cannot be empty or /"
+msgstr "基础数据目录不能为空或 /"
+
+msgid "Base data path maximum length is 64 characters"
+msgstr "基础数据目录最大长度为 64 个字符"
+
+msgid "Bypass mode cannot count downstream traffic because traffic is directly forwarded to terminals at Layer 2 through the gateway."
+msgstr "旁路模式无法统计下行流量,因为流量是通过网关直接二层转发到了终端。"
+
+msgid "Category"
+msgstr "分类"
+
+msgid "Chinese"
+msgstr "中文"
+
+msgid "Clean"
+msgstr "清除"
+
+msgid "Clean failed"
+msgstr "清理失败"
+
+msgid "Click Update List to get the latest feature libraries"
+msgstr "点击更新列表可以获取最新的特征库"
+
+msgid "Click Update List to load available feature libraries"
+msgstr "点击更新列表加载可用特征库"
+
+msgid "Current Version Number"
+msgstr "当前版本号"
+
+msgid "Custom Applications"
+msgstr "自定义应用"
+
+msgid "Data cleaned successfully"
+msgstr "数据清理成功"
+
+msgid "days"
+msgstr "天"
+
+msgid "Destination port must be a single port or port range between 1 and 65535"
+msgstr "目的端口必须是1到65535之间的单个端口或端口范围"
+
+msgid "Domain"
+msgstr "域名"
+
+msgid "Downloading"
+msgstr "正在下载"
+
+msgid "Edit Application"
+msgstr "编辑应用"
+
+msgid "Enable"
+msgstr "启用"
+
+msgid "Enable Access Control"
+msgstr "启用上网控制"
+
+msgid "English"
+msgstr "英文"
+
+msgid "Extracting"
+msgstr "正在解压"
+
+msgid "Feature"
+msgstr "特征"
+
+msgid "Feature Count"
+msgstr "特征数量"
+
+msgid "Feature library format error. Please use the feature library for this version."
+msgstr "特征库格式错误,请使用对应版本的特征库"
+
+msgid "Feature library update failed"
+msgstr "特征库更新失败"
+
+msgid "Feature library update timeout"
+msgstr "特征库更新超时"
+
+msgid "Feature library updated successfully"
+msgstr "特征库更新成功"
+
+msgid "Features"
+msgstr "特征"
+
+msgid "Filter"
+msgstr "过滤"
+
+msgid "Hide"
+msgstr "隐藏"
+
+msgid "History Data Path"
+msgstr "历史数据目录"
+
+msgid "History data path cannot be empty or /"
+msgstr "历史数据目录不能为空或 /"
+
+msgid "History data path maximum length is 64 characters"
+msgstr "历史数据目录最大长度为 64 个字符"
+
+msgid "History Data Size"
+msgstr "历史数据大小"
+
+msgid "History data size limit"
+msgstr "历史数据大小限制"
+
+msgid "History data size must be an integer between 1 and 1024 MB"
+msgstr "历史数据大小必须是 1-1024 MB 的整数"
+
+msgid "History Data Usage"
+msgstr "历史数据占用"
+
+msgid "History Records"
+msgstr "历史记录"
+
+msgid "In bypass mode, it is recommended to disable IPv6 on terminals to prevent terminals from forwarding directly to the main router through IPv6."
+msgstr "旁路模式下建议关闭终端的IPv6,防止终端走IPv6直接转发到主路由。"
+
+msgid "Internet record retention time"
+msgstr "上网记录保留时间"
+
+msgid "Invalid application name"
+msgstr "应用名称无效"
+
+msgid "Invalid domain"
+msgstr "域名无效"
+
+msgid "Invalid LAN interface name"
+msgstr "无效的LAN接口名称"
+
+msgid "Invalid payload match format"
+msgstr "负载匹配格式无效"
+
+msgid "Invalid subscription key"
+msgstr "Key无效"
+
+msgid "Invalid URI"
+msgstr "URI无效"
+
+msgid "LAN interface name for terminal detection. Default is bridge interface (br-lan). If LAN port is changed to physical interface, please modify to the corresponding name, such as eth0."
+msgstr "用于终端探测的LAN接口名称,系统默认为桥接接口(br-lan)。如果将LAN口修改为物理接口,请修改为对应的名称,例如eth0。"
+
+msgid "Language"
+msgstr "语言"
+
+msgid "Leave blank for no restriction"
+msgstr "留空表示不限制"
+
+msgid "Load failed"
+msgstr "加载失败"
+
+msgid "Matches hexadecimal Layer 7 payload content at specified positions. Format: pos:value,pos:value, for example 00:0a,02:ab. Position 0 is the first byte."
+msgstr "匹配七层协议内容中指定位置的十六进制值,格式为pos:value,pos:value,例如00:0a,02:ab,位置0表示第一个字节。"
+
+msgid "Official Website"
+msgstr "官网"
+
+msgid "No apps"
+msgstr "暂无应用"
+
+msgid "No custom applications"
+msgstr "暂无自定义应用"
+
+msgid "Online Feature Library Update"
+msgstr "在线特征库更新"
+
+msgid "Online Records"
+msgstr "在线记录"
+
+msgid "Online Update"
+msgstr "在线更新"
+
+msgid "Payload Match"
+msgstr "负载匹配"
+
+msgid "Payload position must be a signed integer"
+msgstr "负载位置必须是有符号整数"
+
+msgid "Please select a category"
+msgstr "请选择分类"
+
+msgid "Position"
+msgstr "位置"
+
+msgid "Record Time"
+msgstr "记录时间"
+
+msgid "Release Date"
+msgstr "发布日期"
+
+msgid "Reset"
+msgstr "重置"
+
+msgid "Save failed"
+msgstr "保存失败"
+
+msgid "Saved successfully"
+msgstr "保存成功"
+
+msgid "Search"
+msgstr "查询"
+
+msgid "Select TCP or UDP."
+msgstr "选择TCP或UDP协议。"
+
+msgid "Settings"
+msgstr "设置"
+
+msgid "Show"
+msgstr "显示"
+
+msgid "Subscription service is unavailable"
+msgstr "订阅服务暂不可用"
+
+msgid "Subscription Key"
+msgstr "订阅Key"
+
+msgid "Supports a single destination port or port range, for example 80 or 80-443."
+msgstr "支持单个目的端口或端口范围,例如80或80-443。"
+
+msgid "Switch"
+msgstr "切换"
+
+msgid "Switched successfully"
+msgstr "切换成功"
+
+msgid "TCP Connection Reset"
+msgstr "TCP连接重置"
+
+msgid "After enabled, OAF sends TCP RST for filtered TCP connections."
+msgstr "开启后,OAF会为命中过滤规则的TCP连接发送RST重置。"
+
+msgid "Terminal MAC"
+msgstr "终端MAC"
+
+msgid "The downloaded file is too large"
+msgstr "下载文件过大"
+
+msgid "The feature library is used to describe app features, and determines the effectiveness of app filtering and internet usage records"
+msgstr "特征库用于描述应用特征,决定应用过滤和上网记录的效果"
+
+msgid "The HTTP request URI. HTTPS does not support URI matching."
+msgstr "HTTP的请求URI,HTTPS不支持URI匹配。"
+
+msgid "The subscription device limit has been reached"
+msgstr "订阅设备数超过限制"
+
+msgid "The user's basic data and real-time data are stored in this directory, such as online duration, traffic usage and other information. The default is a temporary directory and will not be retained after reboot. You can change it to a persistent directory, such as /etc/oaf."
+msgstr "用户的基础数据和实时数据存放在该目录,比如上网时长、上网流量等信息,默认为临时目录重启不会保存,可以自行修改为持久目录,比如/etc/oaf等。"
+
+msgid "to obtain the advanced feature library update Key."
+msgstr "可获取高级特征库更新Key"
+
+msgid "Too many requests. Please try again later."
+msgstr "访问过于频繁,请稍后再试"
+
+msgid "Update List"
+msgstr "更新列表"
+
+msgid "Updated successfully"
+msgstr "更新成功"
+
+msgid "Updating list, please wait..."
+msgstr "正在更新列表,请稍候..."
+
+msgid "Use this feature library version now?"
+msgstr "确定立即使用该版本的特征库吗?"
+
+msgid "User's App record data will be stored in this directory."
+msgstr "用户的应用记录会存储在该目录。"
+
+msgid "Validating"
+msgstr "正在校验"
+
+msgid "Value"
+msgstr "值"
+
+msgid "Visit"
+msgstr "访问"
+
+msgid "When the Key is empty, the free version can be used. After setting the Key, the premium version can be used. The premium version supports more applications, is updated regularly, and the latest supported application list can be viewed on the official website."
+msgstr "Key为空可以使用免费版本,设置Key后可以使用高级版本,高级版本支持的应用更多,定期更新,最新支持的应用列表可以在官网查看。"
+
+msgid "Parental Control"
+msgstr "OAF行为管理"
+
+msgid "Dashboard"
+msgstr "仪表板"
+
+msgid "About"
+msgstr "关于"
+
+msgid "OAF is a powerful parental control software. FanchmWrt integrates OAF by default and includes more plugins developed by the author."
+msgstr "OAF是一款强大的行为管理软件。FanchmWrt中默认集成了OAF,并且包含了更多作者开发的插件。"
+
+msgid "OAF already supports mobile app management. Mobile management is more convenient."
+msgstr "OAF已经支持手机App管理,手机管理更方便。"
+
+msgid "Go to download"
+msgstr "去下载"
+
+msgid "Author"
+msgstr "作者"
+
+msgid "GitHub Source"
+msgstr "GitHub源码"
+
+msgid "Record Whitelist"
+msgstr "上网记录白名单"
+
+msgid "Feature library checksum verification failed"
+msgstr "特征库文件校验失败"
+
+msgid "Please enter a valid Key. You can apply for a Key on the official website."
+msgstr "请输入正确的Key,Key可以通过官网申请"
+
+msgid "Enter the Key to use this feature library version"
+msgstr "输入Key后可以使用该版本特征库"
+
+msgid "Invalid Key. Please get the correct Key from the official website and enable the advanced feature library permission."
+msgstr "无效的Key,请通过官网获取正确的Key,并开通高级版特征库权限"
+
+msgid "No data"
+msgstr "暂无数据"
diff --git a/luci-app-oaf/root/etc/uci-defaults/94_feature_3.0 b/luci-app-oaf/root/etc/uci-defaults/94_feature_3.0
deleted file mode 100755
index b9050bce..00000000
--- a/luci-app-oaf/root/etc/uci-defaults/94_feature_3.0
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-
-uci -q batch <<-EOF >/dev/null
- set appfilter.feature.format='v3.0'
- set appfilter.rule='rule'
- set appfilter.global.tcp_rst='1'
- set appfilter.global.lan_ifname='br-lan'
- set appfilter.global.auto_load_engine='1'
- commit appfilter
-EOF
\ No newline at end of file
diff --git a/luci-app-oaf/root/etc/uci-defaults/95_time_daily_limit b/luci-app-oaf/root/etc/uci-defaults/95_time_daily_limit
deleted file mode 100755
index 7913a952..00000000
--- a/luci-app-oaf/root/etc/uci-defaults/95_time_daily_limit
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-
-if ! uci -q get appfilter.time.daily_limit_0 >/dev/null 2>&1; then
- uci -q batch <<-EOF >/dev/null
- set appfilter.time.daily_limit_0='0:0:0'
- set appfilter.time.daily_limit_1='0:0:0'
- set appfilter.time.daily_limit_2='0:0:0'
- set appfilter.time.daily_limit_3='0:0:0'
- set appfilter.time.daily_limit_4='0:0:0'
- set appfilter.time.daily_limit_5='0:0:0'
- set appfilter.time.daily_limit_6='0:0:0'
- set appfilter.global.disable_quic='0'
- commit appfilter
- EOF
-fi
-
diff --git a/luci-app-oaf/root/usr/share/rpcd/acl.d/luci-app-oaf.json b/luci-app-oaf/root/usr/share/rpcd/acl.d/luci-app-oaf.json
index 2d554435..8043690a 100644
--- a/luci-app-oaf/root/usr/share/rpcd/acl.d/luci-app-oaf.json
+++ b/luci-app-oaf/root/usr/share/rpcd/acl.d/luci-app-oaf.json
@@ -9,22 +9,3 @@
}
}
}
-{
- "luci-app-oaf": {
- "description": "Grant access to OpenAppFilter configuration",
- "read": {
- "uci": [ "appfilter", "user_info" ],
- "ubus": {
- "appfilter": [ "get_all_users", "get_oaf_status", "get_app_filter", "set_app_filter", "class_list", "dev_list", "app_class_visit_time", "dev_visit_time", "dev_visit_list", "set_app_filter_base", "get_app_filter_base", "set_app_filter_adv", "get_app_filter_adv", "set_app_filter_time", "get_app_filter_time", "get_app_filter_user", "set_app_filter_user", "del_app_filter_user", "add_app_filter_user", "set_nickname", "get_whitelist_user", "add_whitelist_user", "del_whitelist_user", "disable_flow_offloading", "cmd", "service_config"]
- }
-
- },
- "write": {
- "cgi-io": [ "upload" ],
- "file": {
- "/etc/appfilter/*": [ "write" ]
- },
- "uci": [ "appfilter" ]
- }
- }
-}
diff --git a/luci-app-passwall/Makefile b/luci-app-passwall/Makefile
index 2f1c768a..8474684f 100644
--- a/luci-app-passwall/Makefile
+++ b/luci-app-passwall/Makefile
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.8.26
-PKG_RELEASE:=244
+PKG_RELEASE:=245
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
diff --git a/luci-app-passwall/luasrc/passwall/server_app.lua b/luci-app-passwall/luasrc/passwall/server_app.lua
index 2d0b5757..0a692e2d 100644
--- a/luci-app-passwall/luasrc/passwall/server_app.lua
+++ b/luci-app-passwall/luasrc/passwall/server_app.lua
@@ -39,6 +39,24 @@ local function start()
if enabled == nil or enabled == 0 then
return
end
+
+ -- ==================== 启动前:清理所有旧防火墙规则 ====================
+ local fw_changed = false
+ local to_delete = {}
+ uci:foreach("firewall", "rule", function(rule)
+ if rule[".name"]:find(CONFIG) == 1 then
+ table.insert(to_delete, rule[".name"])
+ end
+ end)
+ for _, name in ipairs(to_delete) do
+ cmd("uci delete firewall." .. name)
+ fw_changed = true
+ end
+ if fw_changed then
+ cmd("uci commit firewall")
+ end
+ -- =========================================================================
+
cmd(string.format("mkdir -p %s %s", CONFIG_PATH, TMP_BIN_PATH))
cmd(string.format("touch %s", LOG_APP_FILE))
local firewall_num = 0
@@ -151,16 +169,23 @@ local function start()
if firewall_allow == "1" then
firewall_num = firewall_num + 1
local uid = CONFIG .. "_" .. id
- uci:section("firewall", "rule", uid)
- uci:set("firewall", uid, "name", uid)
- uci:set("firewall", uid, "src", server.firewall_allow_src or "wan")
- uci:set("firewall", uid, "dest_port", port)
- uci:set("firewall", uid, "target", "ACCEPT")
+ cmd("uci set firewall." .. uid .. "=rule")
+ cmd("uci set firewall." .. uid .. ".name='".. uid .."'")
+ cmd("uci set firewall." .. uid .. ".src='" .. (server.firewall_allow_src or "wan") .. "'")
+ cmd("uci set firewall." .. uid .. ".dest_port='" .. port .. "'")
+ cmd("uci set firewall." .. uid .. ".target='ACCEPT'")
+ -- uci:section("firewall", "rule", uid)
+ -- uci:set("firewall", uid, "name", uid)
+ -- uci:set("firewall", uid, "src", server.firewall_allow_src or "wan")
+ -- uci:set("firewall", uid, "dest_port", port)
+ -- uci:set("firewall", uid, "target", "ACCEPT")
+
end
end
end)
- if firewall_num > 0 then
- api.uci_save(uci, "firewall", true, true)
+ if firewall_num > 0 or fw_changed then
+ -- api.uci_save(uci, "firewall", true, true)
+ cmd("uci commit firewall")
cmd("/etc/init.d/firewall reload >/dev/null 2>&1")
end
end
@@ -168,17 +193,19 @@ end
local function stop()
cmd(string.format("/bin/busybox top -bn1 | grep -v 'grep' | grep '%s/' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1", CONFIG_PATH))
if true then
- local num = 0
- uci:foreach("firewall", "rule", function(rule)
- if rule[".name"]:find(CONFIG) == 1 then
- num = num + 1
- uci:delete("firewall", rule[".name"])
- end
- end)
- if num > 0 then
- api.uci_save(uci, "firewall", true, true)
- cmd("/etc/init.d/firewall reload >/dev/null 2>&1")
- end
+ local to_delete = {}
+ uci:foreach("firewall", "rule", function(rule)
+ if rule[".name"]:find(CONFIG) == 1 then
+ table.insert(to_delete, rule[".name"])
+ end
+ end)
+ if #to_delete > 0 then
+ for _, name in ipairs(to_delete) do
+ cmd("uci delete firewall." .. name)
+ end
+ cmd("uci commit firewall")
+ cmd("/etc/init.d/firewall reload >/dev/null 2>&1")
+ end
end
cmd(string.format("rm -rf %s %s", CONFIG_PATH, LOG_APP_FILE))
end
diff --git a/luci-app-sqm-autorate/docs/images/01-overview.png b/luci-app-sqm-autorate/docs/images/01-overview.png
new file mode 100644
index 00000000..5a2fa5d2
Binary files /dev/null and b/luci-app-sqm-autorate/docs/images/01-overview.png differ
diff --git a/luci-app-sqm-autorate/docs/images/02-qdisc.png b/luci-app-sqm-autorate/docs/images/02-qdisc.png
new file mode 100644
index 00000000..38b10bb8
Binary files /dev/null and b/luci-app-sqm-autorate/docs/images/02-qdisc.png differ
diff --git a/luci-app-sqm-autorate/docs/images/03-advanced.png b/luci-app-sqm-autorate/docs/images/03-advanced.png
new file mode 100644
index 00000000..06d72ea1
Binary files /dev/null and b/luci-app-sqm-autorate/docs/images/03-advanced.png differ
diff --git a/luci-app-sqm-autorate/docs/images/04-linklayer.png b/luci-app-sqm-autorate/docs/images/04-linklayer.png
new file mode 100644
index 00000000..8db75237
Binary files /dev/null and b/luci-app-sqm-autorate/docs/images/04-linklayer.png differ
diff --git a/luci-app-sqm-autorate/docs/images/05-autorate.png b/luci-app-sqm-autorate/docs/images/05-autorate.png
new file mode 100644
index 00000000..f994f328
Binary files /dev/null and b/luci-app-sqm-autorate/docs/images/05-autorate.png differ
diff --git a/luci-app-sqm-autorate/docs/sqm-guide.md b/luci-app-sqm-autorate/docs/sqm-guide.md
new file mode 100644
index 00000000..f9b3e60a
--- /dev/null
+++ b/luci-app-sqm-autorate/docs/sqm-guide.md
@@ -0,0 +1,107 @@
+# SQM QoS — User Guide
+
+`luci-app-sqm-autorate` is OpenWrt's standard SQM (Smart Queue Management)
+LuCI app, packaged for OMR with an added **Autorate settings** tab that
+wires each queue into `sqm-autorate` — a userspace daemon that
+continuously re-measures a link's real achievable bandwidth (by pinging
+reflectors and watching for bufferbloat) and pushes updated `cake` rate
+limits, instead of relying on the fixed **Base download/upload speed**
+you'd otherwise have to guess. It's a single page — **SQM QoS** under
+**Network** — with one instance per interface you want shaped.
+
+Screenshots were taken on a test router (`v0.64-snapshot`) with four
+queue instances already defined: `wan1`/`wan2`/`wan3` (plain, disabled)
+and `omrvpn` (carries real autorate/reflector tuning from prior testing —
+`delay_thr_ms=300.0`, `reflector_ping_interval_s=0.5`). All four are
+currently **disabled**; the **Enable SQM autorate** checkbox on `omrvpn`
+was turned on just for the Autorate-tab screenshot below, then reverted —
+it does not reflect this bench's normal running config.
+
+```
+https:///cgi-bin/luci/admin/network/sqm
+```
+
+
+
+If `/var/run/sqm/available_qdiscs` comes back empty (the `sqm` init
+script has never run), the page replaces the form with a notice and an
+**Enable SQM** button instead — not pictured here since this bench
+already has qdiscs available (`cake`, `codel`, `fq_codel`, `sfq`).
+
+Each queue is a `TypedSection` instance (add more with the **Add** button
+at the bottom, remove one with its own **Delete**), with 3 tabs always
+present plus a 4th that only appears once autorate is turned on for that
+instance:
+
+### Basic Settings
+
+| Field | Meaning |
+|---|---|
+| **Enable this SQM instance** | Per-queue on/off. Checking it on any instance auto-enables the `sqm` init script itself (with a one-time notification reminding you to disable it manually in System Startup if that wasn't intended). |
+| **Enable SQM autorate** | Turns this queue over to `sqm-autorate` instead of the fixed Base speeds — reveals the **Autorate settings** tab (see below) and makes **Minimum**/**Maximum download/upload speed** required. |
+| **Interface name** | The underlying device (e.g. `eth1`, `tun0`) — a live device picker, not the OMR interface alias. |
+| **Base download/upload speed (kbit/s)** | The fixed shaping rate when autorate is off; the *starting* rate autorate adjusts from when it's on. |
+| **Minimum/Maximum download/upload speed** *(autorate only)* | The floor/ceiling autorate is allowed to move the rate between. |
+| **Debug logging** / **Verbosity** | Per-instance log file under `/var/run/sqm/${interface}.[start\|stop]-sqm.log`, and syslog verbosity (silent → trace). |
+
+**A validation gotcha caught live**: ticking **Enable SQM autorate**
+immediately makes the four Min/Max speed fields required — if they were
+never filled in (as on this bench's `omrvpn`, which normally runs with
+autorate off), they show up red/invalid right away, as in the screenshot
+above. Fill in all four before Save & Apply once autorate is on.
+
+### Queue Discipline
+
+
+
+| Field | Meaning |
+|---|---|
+| **Queuing disciplines useable on this system** | Populated from `/var/run/sqm/available_qdiscs`; must be `cake` if autorate is used. Installing a new qdisc kernel module needs a router restart before it shows up here. |
+| **Queue setup script** | Which `/usr/lib/sqm/*.qos` script builds the actual tc/qdisc hierarchy — each option's help text (read from that script's `.help` file) is shown live under the dropdown, as in the screenshot: `piece_of_cake` (simplest cake-only setup, this bench's default), `layer_cake` (cake + diffserv layering), `simple`/`simplest`/`simplest_tbf` (non-cake, HTB/TBF-based alternatives). |
+
+Checking **Show and Use Advanced Configuration** reveals more fields —
+DSCP squashing/ignoring and ECN behavior on ingress/egress:
+
+
+
+A further **Show and Use Dangerous Configuration** checkbox (bottom of
+that screenshot) reveals raw hard limits and latency targets
+(**ilimit**/**elimit**, **itarget**/**etarget**) plus free-form
+**iqdisc_opts**/**eqdisc_opts** strings passed straight to the qdisc with
+no validation — this is where this bench's `wan1`/`wan2`/`wan3` actually
+carry real tuning already (`iqdisc_opts: autorate-ingress dual-dsthost`,
+`eqdisc_opts: dual-srchost`), enabling cake's dual-hashing so autorate's
+ingress shaping and per-host fairness cooperate — not shown expanded here
+since both checkboxes reset on every page load and only one could be
+captured cleanly per screenshot.
+
+### Link Layer Adaptation
+
+
+
+| Field | Meaning |
+|---|---|
+| **Which link layer to account for** | `none` (default — plain Ethernet WAN, this bench's setting), `ethernet` (VDSL2-style overhead accounting), or `atm` (ADSL1/2/2+ cell overhead). |
+| **Per Packet Overhead (byte)** *(ethernet/atm only)* | Bytes to add per packet before rate-limiting, to account for lower-layer framing your ISP's link adds. |
+| **Show Advanced Linklayer Options** *(ethernet/atm only)* | Reveals `tcMTU`/`tcTSIZE`/`tcMPU` size-table tuning and a linklayer adaptation mechanism override (`cake`/`htb_private`/`tc_stab`) — only relevant if your MTU exceeds 1500. |
+
+### Autorate settings
+
+Only present as a 4th tab once **Enable SQM autorate** is checked for
+that instance — LuCI omits the tab entirely (not just its fields) when
+every option inside it is hidden by that same dependency.
+
+
+
+| Field | Meaning |
+|---|---|
+| **Output monitoring lines showing processing stats / cake bandwidth changes** | Verbose per-cycle log lines from the autorate daemon, for tuning/debugging. |
+| **Debug** | Even more verbose daemon logging. |
+| **Starlink support** | Adjusts autorate's compensation for Starlink's own periodic latency spikes (dish reposition/satellite handoff). |
+| **Reflector ping interval in seconds** | How often autorate pings its reflector set to sample latency (`0.5`s here). |
+| **Pingers numbers** | How many concurrent reflectors to ping each cycle (`4` here). |
+| **delay threshold in ms** | Latency increase over baseline that autorate treats as bufferbloat and reacts to (`300.0` here) — **must be written as a float string** (`300.0`, not `300`); the field has no datatype validation to catch a plain integer, but `cake-autorate.sh` fails to start on one. |
+| **Sleep functionality** | Let the daemon idle its pingers when the link itself is idle (off on this bench). |
+| **Threshold in Kbit/s below which dl/ul is considered idle** | The idle-detection cutoff feeding the Sleep functionality above (`500` here). |
+| **Time threshold to put pingers to sleep on sustained idle** | How long the link has to stay under that threshold before pingers actually sleep (`60`s here). |
+| **Number of seconds to wait on startup** | Delay before autorate starts adjusting rates after boot/service start, to let the link settle first (`60.0`s here). |
diff --git a/luci-app-store/Makefile b/luci-app-store/Makefile
index 603458f8..545d6dd4 100644
--- a/luci-app-store/Makefile
+++ b/luci-app-store/Makefile
@@ -12,7 +12,7 @@ LUCI_DEPENDS:=@(x86_64||aarch64) +curl +tar +libuci-lua +mount-utils +luci-lib-t
LUCI_EXTRA_DEPENDS:=luci-lib-taskd (>=1.0.19)
LUCI_PKGARCH:=all
-PKG_VERSION:=0.2.0-r3
+PKG_VERSION:=0.2.1-r1
# PKG_RELEASE MUST be empty for luci.mk
PKG_RELEASE:=
diff --git a/luci-app-store/root/bin/is-opkg b/luci-app-store/root/bin/is-opkg
index 49ab4ec0..99035320 100755
--- a/luci-app-store/root/bin/is-opkg
+++ b/luci-app-store/root/bin/is-opkg
@@ -435,6 +435,7 @@ fi
unrun() {
local id="$1"
[ -s "/usr/share/istore/run-records/$id.txt" ] || { echo "record not found: $id" >&2; return 1; }
+ mkdir -p /tmp/is-root/tmp
echo "The following packages will be removed:"
tail -n +2 "/usr/share/istore/run-records/$id.txt" > "/tmp/is-root/tmp/to_unrun_$id.txt"
cat "/tmp/is-root/tmp/to_unrun_$id.txt"
diff --git a/luci-proto-tun2socks/htdocs/luci-static/resources/protocol/t2s.js b/luci-proto-tun2socks/htdocs/luci-static/resources/protocol/t2s.js
index e3e1ecde..201a7c38 100644
--- a/luci-proto-tun2socks/htdocs/luci-static/resources/protocol/t2s.js
+++ b/luci-proto-tun2socks/htdocs/luci-static/resources/protocol/t2s.js
@@ -44,6 +44,7 @@ return network.registerProtocol('t2s', {
o.value('socks4', 'SOCKS4');
o.value('socks5', 'SOCKS5');
o.value('ss', 'Shadowsocks');
+ o.value('ssh', 'SSH');
o.value('relay', _('Relay'));
o.value('direct', _('Direct'));
o.value('reject', _('Reject'));
@@ -63,11 +64,11 @@ return network.registerProtocol('t2s', {
o.depends({'socket': '1' , 'proxy': 'socks5' });
o = s.taboption('general', form.Flag, 'advanced', _('Autentification'), _('Authentification and encryption.'));
- o.depends({'proxy': /socks4|socks5|relay|ss/ });
+ o.depends({'proxy': /socks4|socks5|relay|ss|ssh/ });
o.rmempty = true;
o = s.taboption('general', form.Value, 'username', _('Proxy USER'));
- o.depends({'advanced': '1', 'proxy': /socks|relay/});
+ o.depends({'advanced': '1', 'proxy': /socks|ssh|relay/});
o = s.taboption('general', form.ListValue, 'encrypt', _('Encryption'));
o.value('none','none');
@@ -96,7 +97,18 @@ return network.registerProtocol('t2s', {
o = s.taboption('general', form.Value, 'password', _('Proxy Password'));
o.password = true;
- o.depends({'advanced': '1', 'proxy': /socks5|relay|ss/ });
+ o.depends({'advanced': '1', 'proxy': /socks5|relay|ssh|ss/ });
+
+ o = s.taboption('general', form.FileUpload, 'ssh_key', _('SSH Key'));
+ o.root_directory = '/etc/tun2socks/keys';
+ o.browser = false;
+ o.enable_upload = true;
+ o.enable_remove = true;
+ o.depends({ 'proxy': 'ssh' });
+
+ o = s.taboption('general', form.Value, 'ssh_passphrase', _('SSH Passphrase'));
+ o.rmempty = true;
+ o.depends({ 'proxy': /ssh/ });
o = s.taboption('general',form.Flag, 'base64enc', _('Encrypt base64'));
o.depends({'advanced': '1', 'proxy': 'ss' });
diff --git a/luci-proto-tun2socks/root/etc/uci-defaults/luci-app-tun2socks b/luci-proto-tun2socks/root/etc/uci-defaults/luci-app-tun2socks
new file mode 100644
index 00000000..c8a5020e
--- /dev/null
+++ b/luci-proto-tun2socks/root/etc/uci-defaults/luci-app-tun2socks
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+[ -d /etc/tun2socks/keys ] || mkdir -p /etc/tun2socks/keys
diff --git a/luci-theme-aurora/.claude/agents/aurora-runtime-reviewer.md b/luci-theme-aurora/.claude/agents/aurora-runtime-reviewer.md
new file mode 100644
index 00000000..a3d1181f
--- /dev/null
+++ b/luci-theme-aurora/.claude/agents/aurora-runtime-reviewer.md
@@ -0,0 +1,52 @@
+---
+name: aurora-runtime-reviewer
+description: Reviews a diff in this repository for the client runtime of the Aurora theme — menu-aurora.js, router-aurora.js, page JS patches, header inline scripts — against L.require semantics, router invariants, SPA teardown, polling hygiene and keyboard/ARIA reachability. Read-only.
+tools: Read, Grep, Glob, Bash
+effort: high
+skills: luci-knowledge
+color: purple
+---
+
+You review changes to `.dev/src/resource/**`, inline `
+
+ {% include('footer') %}
+ ```
+
+2. luci-base `ucode/template/header.ut` first includes the **theme** header
+ (`include(\`themes/${theme}/header\`)`, same scope — the theme renders the
+ whole `` and opens ``), then emits (header.ut:10-31):
+
+ ```
+
+
+ ```
+
+ Consequences: the theme `` is parsed **before** `luci.js` exists;
+ `luci.js` itself sits in the body region after `#tabmenu`; the `?v=` of
+ `luci.js` (and therefore `L.env.resource_version`, used for every
+ `L.require` URL) changes whenever any package is installed, because
+ `pkgs_update_time` is the mtime of `/lib/apk/db/installed` or
+ `/usr/lib/opkg/status` (`ucode/runtime.uc:182`). On `openwrt-23.05` the
+ tag is a bare `luci.js` (preceded by `promis.min.js`) and
+ `pkgs_update_time` does not exist (`23.05 header.ut:10-11`,
+ `runtime.uc:179-182`), so `resource_version` is undefined and module
+ URLs are unversioned there.
+
+ `_()` is **not** in `luci.js`: it is defined by `cbi.js`
+ (`resources/cbi.js:154-157`), reads `window.TR` lazily at call time and
+ falls back to the source string. The catalog script and `cbi.js` must
+ therefore have executed before any module that calls `_()` runs — and
+ this theme's `menu-aurora.js` calls `_()` synchronously in `__init__`.
+
+3. `view.ut`'s inline `L.require('ui')` runs synchronously in parser order
+ right after `luci.js`. A wrapper installed from the theme **footer** is too
+ late for that call, but early enough for `ui.instantiateView(view)` →
+ `L.require(view)` and for `L.require('menu-aurora')`.
+
+4. luci-base `ucode/template/footer.ut` includes the theme footer and, when
+ an apply/revert/rollback is pending, an inline script bound to the
+ `luci-loaded` event.
+
+## 2. Template scope the theme can use
+
+Globals set by `runtime.uc` / `dispatcher.uc` and visible inside
+`themes//header.ut`: `media`, `resource`, `theme`, `dispatcher`
+(`.lang`, `.build_url()`, `.lookup()`), `dispatched` (the node: `title`,
+`css`, …), `ctx.request_path`, `ctx.path`, `config`, `version`,
+`pkgs_update_time`, `http`, `ubus`, `_()`, `striptags()`, `entityencode()`.
+Callers may add scope: `sysauth.ut` passes `blank_page`, and the theme adds
+its own `prefetched_*` (aurora `sysauth.ut:17-21`).
+
+`{{ expr }}` prints raw — the shipped templates call `entityencode(v, true)`
+for attributes and `striptags()` for text explicitly. Do the same.
+
+## 3. Module loader — `L.require`
+
+- URL: `${env.base_url}/${name.replace(/\./g,'/')}.js` + `?v=${env.resource_version}`
+ (`luci.js:2474`); both env values are parsed from the `luci.js` script src
+ at boot (`luci.js:2257`).
+- Loads the source, lexes the leading string literals `'require x [as y]'`
+ (works on a minified one-line head), then loads dependencies **serially per
+ level** — a dependency depth of *n* costs *n* round trips.
+- Returns the **instance**: `__init__` has already run, so re-requiring a
+ view returns the rendered singleton. To render again use
+ `new instance.constructor()` (aurora `router-aurora.js:713`).
+- Six names are seeded in memory (`luci.js:2237-2244`) and have **no file**
+ on disk: `baseclass`, `dom`, `poll`, `request`, `session`, `view`.
+ Prefetchers must skip them or they 404 on every page.
+
+## 4. Menu, session store, polling
+
+- `ui.menu.load()` (`ui.js:3733`): `session.getLocalData('menu')` first —
+ sessionStorage key `luci-session-store`, namespaced by session id
+ (`luci.js:1883-1932`) — else one `GET admin/menu` per **session**, not per
+ page. `ui.menu.flushCache()` (`ui.js:3748`) has no caller in luci-base or
+ the bootstrap theme.
+- `L.Poll` is a singleton inside `luci.js` (`luci.js:1067`): `queue`
+ (`:1070`), `start()` (`:1158`, dispatches `poll-start` at `:1167`),
+ `stop()` (`:1183`, dispatches `poll-stop`), `active()`, `tick`. Core's
+ `setupDOM` shows the `poll-status` indicator on `poll-start`
+ (`luci.js:2743`) and flips it to "Paused" on `poll-stop` (`:2749`); nothing
+ in luci-base hides it.
+- `dom.content(node, children)` (`luci.js:1442-1449`) deletes the
+ `data-idref` registry entries under `node` — the only call that does;
+ without it a departed subtree and its class instances stay referenced.
+- Session expiry is signalled two ways (`luci.js:2715-2733`): a ubus error
+ `-32002` on anything but `session.access` triggers a `session.access`
+ probe, and an HTTP 403 carrying `X-LuCI-Login-Required: yes` is the
+ login-required path.
+- `hasViewPermission()` is `!env.nodespec.readonly` (`luci.js:3263-3267`);
+ the form footer's Save/Apply state reads it (`luci.js:2195`).
+- `env.pollinterval` defaults to 5 s; a hidden tab keeps polling unless a
+ theme stops it.
+
+## 5. Dispatcher responses and caching
+
+- `http.uc` `write_headers()` (`http.uc:497-512`): unless the action set
+ its own, every dispatcher response gets `Cache-Control: no-cache` and
+ `Expires: 0`, plus `X-Frame-Options: SAMEORIGIN`, `X-XSS-Protection`,
+ `X-Content-Type-Options: nosniff`. No `ETag`, no `Last-Modified` — nothing
+ to revalidate against, so browsers refetch every dispatcher response.
+- `admin/translations/` (`ucode/controller/admin/index.uc:124-136`,
+ `action_translations`) streams `window.TR={"%08x":"…",…};`. It is a full
+ CGI dispatch per document and uncacheable by the rule above. Themes link it
+ as a synchronous `` script (bootstrap `header.ut:44`, aurora
+ `header.ut:222`), so it is parser-blocking on every MPA navigation. Its
+ menu node is `type: function` with `auth: {}` (`menu.d/luci-base.json:62-69`),
+ which only skips the login redirect: `resolve_page()` still runs
+ `is_authenticated()` for the `admin` node's cookie methods
+ (`dispatcher.uc:650-651`, `:533-551`), so the dispatch pays the
+ `session.get` + `session.access` round-trip like any page.
+- An action can override the default: `http.header()` stores keys
+ lower-cased (`http.uc:466-469`), so a `Cache-Control` set by the action
+ suppresses the `no-cache` default. No `.lmo` exists for `en`
+ (`luci.mk:10`, `src/lib/lmo.c:237`), so a file-mtime cache key is
+ undefined for the English catalog; `pkgs_update_time` is not.
+- A query string never changes which node is dispatched: uhttpd splits it
+ off before path lookup and the dispatcher reads `PATH_INFO` only
+ (`dispatcher.uc:929`; `QUERY_STRING` is parsed separately, `http.uc:606`).
+ `?v=` on a dispatcher URL is therefore safe.
+- Menu tree: `dispatcher.uc:19,366-374` — index cache
+ `/tmp/luci-indexcache..json`, hashed over the `menu.d` file list; each
+ dispatch stats the `menu.d` files and parses the cached JSON.
+- ACL: `dispatcher.uc` folds every `depends.acl` on the dispatch path into a
+ single `check_acl_depends()`; `env.nodespec.readonly` is what
+ `hasViewPermission()` reads, which is what disables Save/Apply.
+- Alias / firstchild are resolved server-side **without a redirect**: the
+ URL stays, `requestpath`/`dispatchpath`/`nodespec`/`ctx.path` already point
+ at the leaf. `resolve_firstchild()` / `node_weight()` semantics are ported
+ line for line in `router-aurora.js:25-98`.
+
+## 6. Branch differences (23.05 / 24.10 / 25.12 / master)
+
+Source-checked 2026-08 (see `.dev/docs/router.md` §Compatibility):
+
+| Surface | 23.05 | 24.10 | 25.12 | master |
+|---|---|---|---|---|
+| `node.css` in the page tree | — | — | — | yes (7c6d8ff, 2026-08) |
+| `wildcardaction` + descend-into-satisfied-child rule | — | — | yes | yes (df90c60a7, 2026-01-17) |
+| `pkgs_update_time` template global; `luci.js?v=PKG-mtime` (so `L.env.resource_version` and versioned `L.require` URLs) | — (`runtime.uc:179-182`, `header.ut:11`) | yes | yes | yes |
+| `action_translations` body, `http.uc` `no-cache` default | same | same | same | same |
+| everything else the router uses (`L.require` instance cache, `dom.content`, `Poll`, `ui.menu.load` session cache, interceptors, `-32002` probe, `view.ut` shell) | same | same | same | same |
+
+The theme targets 23.05+ (ucode templates); anything relying on a newer
+surface must degrade to the older behaviour, and the finding must name the
+branch.
+
+## 7. Where to look
+
+- `$LUCI_SRC/modules/luci-base/ucode/{dispatcher,http,runtime}.uc`
+- `$LUCI_SRC/modules/luci-base/ucode/template/{header,footer,view}.ut`
+- `$LUCI_SRC/modules/luci-base/ucode/controller/admin/index.uc`
+- `$LUCI_SRC/modules/luci-base/ucode/uhttpd.uc` (in-process handler entry)
+- `$LUCI_SRC/modules/luci-base/htdocs/luci-static/resources/{luci,ui,rpc,uci,form,network}.js` (`Poll`, `dom`, `session`, `view` live inside `luci.js`)
+- `$LUCI_SRC/luci.mk`, `$LUCI_SRC/themes/luci-theme-bootstrap/`
+- Other branches: `git -C $LUCI_SRC show :` (fetch
+ `openwrt-23.05`, `openwrt-24.10`, `openwrt-25.12` first if missing).
diff --git a/luci-theme-aurora/.claude/skills/luci-knowledge/references/packaging.md b/luci-theme-aurora/.claude/skills/luci-knowledge/references/packaging.md
new file mode 100644
index 00000000..5e3a4118
--- /dev/null
+++ b/luci-theme-aurora/.claude/skills/luci-knowledge/references/packaging.md
@@ -0,0 +1,35 @@
+# Packaging facts (luci.mk, Makefile, feed)
+
+## luci.mk rewrites at package build (`$LUCI_SRC/luci.mk:287-298`, `SubstituteVersion`)
+
+Applied to every `*.ut` / `*.htm` under the installed template dir:
+
+1. `{# PKG_VERSION #}` → the package version (aurora `header.ut:227` uses it
+ for `body[data-asset-version]`; luci-base `header.ut:10` for `luci.js?v=`).
+2. Quoted `"{{ media }}…/x.css"` and `"{{ resource }}…/x.js"` links get
+ `?v=$(PKG_VERSION)` appended. The regex requires the literal `.css"`/`.js"`
+ at the end of the quoted string, so `{{ resource }}/{{ dispatched.css }}`
+ is **not** rewritten (same as bootstrap) while
+ `{{ media }}/patches/{{ patch }}.css` **is**.
+
+Never judge versioning from the source `.ut`; inspect the installed file or
+the live HTML.
+
+## Theme Makefile
+
+- `PKG_VERSION` / `PKG_RELEASE` (date) in `Makefile`. `LUCI_MINIFY_CSS:=`
+ blanks luci.mk's default `LUCI_MINIFY_CSS?=1` (`luci.mk:18`), so the
+ csstidy pass (`luci.mk:274-280`) is skipped for the already-minified
+ `htdocs/`. `LUCI_MINIFY_JS` is **not** blanked: with `CONFIG_LUCI_JSMIN`
+ the package build runs jsmin over `htdocs/**/*.js` (`luci.mk:261-268`),
+ so on-device JS sizes can differ from the repo's — measure on the device
+ when a JS budget is the question.
+- `root/etc/uci-defaults/30_luci-theme-aurora` sets `luci.themes.Aurora` and
+ `luci.main.mediaurlbase` on fresh installs only (`PKG_UPGRADE != 1`).
+- The OpenWrt buildbot has no Node: `htdocs/luci-static/` is the committed
+ output of `pnpm build`; the package copies it verbatim.
+- Floors: OpenWrt 23.05 (ucode templates); Chrome 111 / Safari 16.4 /
+ Firefox 128; the Navigation API is an optional enhancement.
+- CI: `.github/workflows/build-theme-package.yml` builds ipk + apk on tags,
+ manual dispatch, and branch pushes whose head commit message contains
+ `[build]`.
diff --git a/luci-theme-aurora/.claude/skills/luci-knowledge/references/spa-pitfalls.md b/luci-theme-aurora/.claude/skills/luci-knowledge/references/spa-pitfalls.md
new file mode 100644
index 00000000..08a168a9
--- /dev/null
+++ b/luci-theme-aurora/.claude/skills/luci-knowledge/references/spa-pitfalls.md
@@ -0,0 +1,29 @@
+# SPA pitfalls that apply to this router
+
+Distilled from luci-theme-footstrap's router work (public:
+https://github.com/VizzleTF/luci-theme-footstrap/blob/main/docs/spa-router.md)
+and re-checked against this theme's implementation; Aurora's handling is
+specified in `.dev/docs/router.md`. Self-contained — no other note is needed.
+
+| pitfall | why it bites | aurora |
+|---|---|---|
+| `L.require()` returns the rendered **instance** | re-requiring a view paints nothing; `new` on the instance's class without teardown = double render + double pollers, invisible to the eye | `new instance.constructor()` only after teardown, `router-aurora.js:713` |
+| the class rendered by the full load must be marked "seen" | otherwise the first return to that page takes the cold path and renders twice | `initialRoute()` |
+| generation check at **paint** time, and a superseded render must never resolve | an empty node blanks the live page, a throw lets luci-base paint an error box into the new page | `navigate()` gen gate |
+| poll teardown is three steps: `queue.length = 0`, `stop()`, `start()` | `start()` resets the tick that arms the incoming view's first poll | `teardown()` |
+| the `poll-status` indicator outlives polling | core shows it on `poll-start` and never hides it | `hideIndicator('poll-status')` |
+| bare `setInterval` in apps survives a same-document swap | a full load killed it; the router must | `hookIntervals()` |
+| `alias` / `firstchild` are the most-clicked links and resolve server-side **without a redirect** | `requestpath`/`dispatchpath`/`nodespec` already point at the leaf while the URL stays; port `resolve_firstchild()`/`node_weight()` literally | `resolve()` |
+| `env.nodespec.readonly` is not decoration | `hasViewPermission()` reads it → Save/Apply enabled for read-only users if lost | `readonlyAlong()` |
+| links with `?query` / `#hash` stay full loads | views read `location.search`; a hash-only change is not a navigation | `onNavigate()` |
+| content lands outside `#view` | `
` siblings from server templates, `ui.addNotification()` inserted into `#maincontent` — a full load cleared them, a swap must | `region()`, `commit()` |
+| `body[data-page]` and `document.title` | restamp `data-page` from the resolved leaf (page-scoped CSS keys on it); cache the title host once, third-party views overwrite `document.title` | `setEnvironment()` |
+| view-injected `
-
-
-