mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-10-30 07:35:13 +08:00
update 2026-09-24 16:56:26
This commit is contained in:
1 parent
b31a5c3878
commit
e4cee331e9
6885 files changed
+3240523
No files matched your search
@@ -0,0 +1,5 @@
|
||||
module("luci.controller.oaf", package.seeall)
|
||||
|
||||
function index()
|
||||
entry({"admin", "services", "oaf"}, firstchild(), _("Parental Control"), 20).dependent = true
|
||||
end
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,371 @@
|
||||
module("luci.controller.oaf_app_filter", package.seeall)
|
||||
local utl = require "luci.util"
|
||||
|
||||
function index()
|
||||
local fs = require "nixio.fs"
|
||||
if not 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,461 @@
|
||||
module("luci.controller.oaf_mac_filter", package.seeall)
|
||||
local utl = require "luci.util"
|
||||
|
||||
function index()
|
||||
local fs = require "nixio.fs"
|
||||
if not 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
|
||||
@@ -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
|
||||
@@ -0,0 +1,181 @@
|
||||
module("luci.controller.oaf_whitelist", package.seeall)
|
||||
|
||||
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 fs = require "nixio.fs"
|
||||
local has_app_filter = fs.access("/etc/config/appfilter")
|
||||
local has_access_control = fs.access("/etc/config/macfilter")
|
||||
local has_record = 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
|
||||
@@ -0,0 +1,20 @@
|
||||
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 m, s
|
||||
arg[1] = arg[1] or ""
|
||||
m = Map("app_filter", translate(""), translate(""))
|
||||
|
||||
|
||||
local v
|
||||
v = m:section(SimpleSection)
|
||||
v.template = "oaf/app_filter/rules"
|
||||
return m
|
||||
@@ -0,0 +1,19 @@
|
||||
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 m, s
|
||||
arg[1] = arg[1] or ""
|
||||
m = Map("fwx_dashboard", translate(""), translate(""))
|
||||
|
||||
local v
|
||||
v = m:section(SimpleSection)
|
||||
v.template = "oaf/dashboard"
|
||||
return m
|
||||
@@ -0,0 +1,20 @@
|
||||
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 m, s
|
||||
arg[1] = arg[1] or ""
|
||||
m = Map("macfilter", translate(""), translate(""))
|
||||
|
||||
|
||||
local v
|
||||
v = m:section(SimpleSection)
|
||||
v.template = "oaf/mac_filter/mac_filter"
|
||||
return m
|
||||
@@ -0,0 +1,19 @@
|
||||
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 m, s
|
||||
arg[1] = arg[1] or ""
|
||||
m = Map("macfilter", translate(""), translate(""))
|
||||
|
||||
local v
|
||||
v = m:section(SimpleSection)
|
||||
v.template = "oaf/mac_filter/rules"
|
||||
return m
|
||||
@@ -0,0 +1,19 @@
|
||||
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 m, s
|
||||
arg[1] = arg[1] or ""
|
||||
m = Map("macfilter", translate(""), translate(""))
|
||||
|
||||
local v
|
||||
v = m:section(SimpleSection)
|
||||
v.template = "oaf/mac_filter/user"
|
||||
return m
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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(""))
|
||||
|
||||
m:section(SimpleSection).template = "oaf/user_status"
|
||||
|
||||
return m
|
||||
@@ -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
|
||||
Binary file not shown.
@@ -0,0 +1,128 @@
|
||||
<%+header%>
|
||||
<link rel="stylesheet" href="<%=resource%>/oaf/css/common.css">
|
||||
|
||||
<div class="oaf-about-page">
|
||||
<section class="oaf-about-card">
|
||||
<div class="oaf-about-brand">OpenAppFilter</div>
|
||||
<p class="oaf-about-intro">
|
||||
<%:OAF is a powerful parental control software. FanchmWrt integrates OAF by default and includes more plugins developed by the author.%>
|
||||
</p>
|
||||
|
||||
<div class="oaf-about-info">
|
||||
<div class="oaf-about-row">
|
||||
<div class="oaf-about-label"><%:Author%></div>
|
||||
<div class="oaf-about-value">destan19(TT)</div>
|
||||
</div>
|
||||
<div class="oaf-about-row">
|
||||
<div class="oaf-about-label">OAF</div>
|
||||
<div class="oaf-about-value">
|
||||
<a href="http://www.openappfilter.com" target="_blank" rel="noopener noreferrer">www.openappfilter.com</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oaf-about-row">
|
||||
<div class="oaf-about-label">FanchmWrt</div>
|
||||
<div class="oaf-about-value">
|
||||
<a href="http://www.fanchmwrt.com" target="_blank" rel="noopener noreferrer">www.fanchmwrt.com</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oaf-about-row">
|
||||
<div class="oaf-about-label"><%:GitHub Source%></div>
|
||||
<div class="oaf-about-value">
|
||||
<a href="https://github.com/destan19/OpenAppFilter" target="_blank" rel="noopener noreferrer">https://github.com/destan19/OpenAppFilter</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oaf-about-copyright">
|
||||
Copyright © 2026 FanchmWrt. All rights reserved.
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.oaf-about-page {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.oaf-about-card {
|
||||
padding: 22px;
|
||||
border: 1px solid var(--border-color-low, #e5e7eb);
|
||||
border-radius: 8px;
|
||||
background: var(--background-color-high, #fff);
|
||||
color: var(--text-color-high, #333);
|
||||
max-width: 860px;
|
||||
}
|
||||
|
||||
.oaf-about-brand {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.oaf-about-intro {
|
||||
margin: 0 0 20px;
|
||||
color: var(--text-color-high, #333);
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.oaf-about-info {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
max-width: 760px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.oaf-about-row {
|
||||
display: grid;
|
||||
grid-template-columns: 130px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border-color-low, #e5e7eb);
|
||||
border-radius: 6px;
|
||||
background: var(--background-color-low, #f5f5f5);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.oaf-about-label {
|
||||
color: var(--text-color-medium, #6b7280);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.oaf-about-value {
|
||||
min-width: 0;
|
||||
color: var(--text-color-high, #333);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.oaf-about-value a {
|
||||
color: var(--primary-color, #2563eb);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.oaf-about-value a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.oaf-about-copyright {
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--border-color-low, #e5e7eb);
|
||||
color: var(--text-color-medium, #6b7280);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.oaf-about-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.oaf-about-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<%+footer%>
|
||||
@@ -0,0 +1,462 @@
|
||||
<%
|
||||
local nfs = require "nixio.fs"
|
||||
local has_app_filter = nfs.access("/etc/config/appfilter")
|
||||
local has_access_control = nfs.access("/etc/config/macfilter")
|
||||
%>
|
||||
<%+header%>
|
||||
<link rel="stylesheet" href="<%=resource%>/oaf/css/common.css">
|
||||
<script src="<%=resource%>/oaf/jquery-3.7.1.min.js"></script>
|
||||
<script src="<%=resource%>/oaf/common.js"></script>
|
||||
|
||||
<div class="settings-page">
|
||||
<div class="settings-heading"><%:Settings%></div>
|
||||
|
||||
<section class="settings-panel">
|
||||
<button type="button" class="settings-panel-header" aria-expanded="true" aria-controls="basic-settings-panel">
|
||||
<span><%:Basic Settings%></span>
|
||||
<span class="settings-chevron" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="settings-panel-body" id="basic-settings-panel">
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="work-mode"><%:Work Mode%></label>
|
||||
<select id="work-mode">
|
||||
<option value="0"><%:Gateway Mode%></option>
|
||||
<option value="1"><%:Bypass Mode%></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<ol class="work-mode-warning" id="work-mode-warning">
|
||||
<li><%:Bypass mode cannot count downstream traffic because traffic is directly forwarded to terminals at Layer 2 through the gateway.%></li>
|
||||
<li><%:In bypass mode, it is recommended to disable IPv6 on terminals to prevent terminals from forwarding directly to the main router through IPv6.%></li>
|
||||
</ol>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="lan-ifname"><%:LAN Interface%></label>
|
||||
<div class="settings-field">
|
||||
<input id="lan-ifname" type="text" placeholder="br-lan" class="input-short">
|
||||
<div class="field-error" id="lan-ifname-error"></div>
|
||||
<div class="form-desc-inline"><%: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.%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="tcp-rst"><%:TCP Connection Reset%></label>
|
||||
<div class="settings-field">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="tcp-rst">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<div class="form-desc-inline"><%:After enabled, OAF sends TCP RST for filtered TCP connections.%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if has_app_filter then %>
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="app-filter-enable"><%:Enable App Filter%></label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="app-filter-enable">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if has_access_control then %>
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="access-control-enable"><%:Enable Access Control%></label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="access-control-enable">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-panel">
|
||||
<button type="button" class="settings-panel-header" aria-expanded="true" aria-controls="audit-settings-panel">
|
||||
<span><%:Internet Audit%></span>
|
||||
<span class="settings-chevron" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="settings-panel-body" id="audit-settings-panel">
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="rec-enable"><%:Enable%></label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="rec-enable">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row" id="data-size-group" style="display:none;">
|
||||
<label class="form-label"><%:History Data Usage%></label>
|
||||
<div class="data-size-field">
|
||||
<span id="data-size-display"></span>
|
||||
<button type="button" class="btn btn-secondary data-clean-btn" id="clean-history"><%:Clean%></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="rec-keep"><%:Record Time%></label>
|
||||
<div class="settings-field">
|
||||
<select id="rec-keep">
|
||||
<option value="1">1 <%:days%></option>
|
||||
<option value="2">2 <%:days%></option>
|
||||
<option value="3">3 <%:days%></option>
|
||||
<option value="4">4 <%:days%></option>
|
||||
<option value="5">5 <%:days%></option>
|
||||
<option value="6">6 <%:days%></option>
|
||||
<option value="7">7 <%:days%></option>
|
||||
<option value="10">10 <%:days%></option>
|
||||
<option value="15">15 <%:days%></option>
|
||||
<option value="30">30 <%:days%></option>
|
||||
</select>
|
||||
<div class="form-desc-inline"><%:Internet record retention time%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="rec-valid"><%:App Valid Time%></label>
|
||||
<div class="settings-field">
|
||||
<select id="rec-valid">
|
||||
<option value="1">1 <%:minutes%></option>
|
||||
<option value="2">2 <%:minutes%></option>
|
||||
<option value="3">3 <%:minutes%></option>
|
||||
<option value="4">4 <%:minutes%></option>
|
||||
<option value="5">5 <%:minutes%></option>
|
||||
<option value="10">10 <%:minutes%></option>
|
||||
<option value="15">15 <%:minutes%></option>
|
||||
<option value="20">20 <%:minutes%></option>
|
||||
<option value="30">30 <%:minutes%></option>
|
||||
<option value="60">60 <%:minutes%></option>
|
||||
</select>
|
||||
<div class="form-desc-inline"><%:App records minimum duration%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="rec-history-size"><%:History Data Size%></label>
|
||||
<div class="settings-field">
|
||||
<div class="size-field">
|
||||
<input type="text" id="rec-history-size">
|
||||
<span class="unit">MB</span>
|
||||
</div>
|
||||
<div class="form-desc-inline"><%:History data size limit%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="rec-history-path"><%:History Data Path%></label>
|
||||
<div class="settings-field">
|
||||
<input type="text" id="rec-history-path">
|
||||
<div class="form-desc-inline"><%:User's App record data will be stored in this directory.%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="rec-base-path"><%:Base Data Path%></label>
|
||||
<div class="settings-field">
|
||||
<input type="text" id="rec-base-path">
|
||||
<div class="form-desc-inline"><%: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.%></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-panel">
|
||||
<button type="button" class="settings-panel-header" aria-expanded="true" aria-controls="advanced-settings-panel">
|
||||
<span><%:Advanced Settings%></span>
|
||||
<span class="settings-chevron" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="settings-panel-body" id="advanced-settings-panel">
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="disable-hnat"><%:Auto Disable Acceleration%></label>
|
||||
<div class="settings-field">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="disable-hnat">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<div class="form-desc-inline"><%: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.%></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="settings-actions">
|
||||
<button class="btn btn-secondary" type="button" id="settings-refresh"><%:Refresh%></button>
|
||||
<button class="btn" type="button" id="settings-save" disabled><%:Save%></button>
|
||||
</div>
|
||||
<div class="status-tip" id="settings-tip"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.settings-page{max-width:860px;margin:0 auto 16px;padding:0 10px}.settings-heading{font-size:20px;font-weight:600;margin:8px 0 14px}.settings-panel{border:1px solid var(--border-color-low,#e5e7eb);border-radius:7px;margin-bottom:12px;background:var(--background-color-high,transparent);overflow:hidden}.settings-panel-header{width:100%;border:0;border-bottom:1px solid var(--border-color-low,#e5e7eb);background:var(--background-color-low,rgba(0,0,0,.025));color:inherit;display:flex;align-items:center;justify-content:space-between;padding:12px 16px;font-size:16px;font-weight:600;text-align:left;cursor:pointer}.settings-panel-header.collapsed{border-bottom-color:transparent}.settings-chevron{width:8px;height:8px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .15s ease;margin:0 4px 4px 12px}.settings-panel-header.collapsed .settings-chevron{transform:rotate(-45deg);margin-bottom:0}.settings-panel-body{padding:16px}.settings-panel-body .form-row{align-items:flex-start;margin-bottom:16px}.settings-panel-body .switch{margin-top:2px;flex:none}.settings-field{flex:1;min-width:0}.settings-field>input,.settings-field>select{width:360px;max-width:100%}.form-desc-inline{margin-top:5px;font-size:12px;line-height:1.5;color:var(--text-color-medium,#6b7280)}.field-error{display:none;color:#f87171;font-size:12px;margin-top:4px}.work-mode-warning{display:none;margin:0 0 16px 150px;padding:10px 14px 10px 30px;border:1px solid rgba(239,68,68,.4);border-radius:6px;background:rgba(239,68,68,.08);color:#dc2626;font-size:13px;line-height:1.6}.work-mode-warning li{margin:2px 0}.data-size-field,.size-field{display:flex;align-items:center;gap:10px}.data-clean-btn{min-width:64px}.size-field input{width:320px!important}.size-field .unit{color:var(--text-color-medium,#6b7280)}.settings-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:4px}.settings-page>.status-tip{text-align:right}.settings-panel-header:focus-visible{outline:2px solid #3b82f6;outline-offset:-2px}[data-darkmode="true"] .work-mode-warning{border-color:rgba(248,113,113,.55);background:rgba(127,29,29,.26);color:#fca5a5}
|
||||
@media(max-width:600px){.settings-page{padding:0 4px}.settings-panel-header{padding:11px 12px}.settings-panel-body{padding:13px 12px}.settings-panel-body .form-row{display:block}.settings-panel-body .form-label{display:block;width:auto;margin-bottom:7px}.work-mode-warning{margin-left:0}.settings-field>input,.settings-field>select,.settings-panel-body>.form-row>select{width:100%}.size-field input{width:100%!important;min-width:0}.settings-actions{padding-right:2px}}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
(function() {
|
||||
var text = {
|
||||
saved: '<%:Saved successfully%>',
|
||||
saveFailed: '<%:Save failed%>',
|
||||
loading: '<%:Loading...%>',
|
||||
loadFailed: '<%:Load failed%>',
|
||||
invalidLan: '<%:Invalid LAN interface name%>',
|
||||
invalidSize: '<%:History data size must be an integer between 1 and 1024 MB%>',
|
||||
emptyHistoryPath: '<%:History data path cannot be empty or /%>',
|
||||
longHistoryPath: '<%:History data path maximum length is 64 characters%>',
|
||||
emptyBasePath: '<%:Base data path cannot be empty or /%>',
|
||||
longBasePath: '<%:Base data path maximum length is 64 characters%>',
|
||||
cleanConfirm: '<%:Are you sure you want to clean all history data? This action cannot be undone.%>',
|
||||
cleanSuccess: '<%:Data cleaned successfully%>',
|
||||
cleanFailed: '<%:Clean failed%>'
|
||||
};
|
||||
var api = {
|
||||
workModeGet: '<%=url("admin/services/oaf/api/system/get_work_mode")%>',
|
||||
workModeSet: '<%=url("admin/services/oaf/api/system/set_work_mode")%>',
|
||||
systemGet: '<%=url("admin/services/oaf/api/system/get_system_info")%>',
|
||||
systemSet: '<%=url("admin/services/oaf/api/system/set_system_info")%>',
|
||||
tcpRstGet: '<%=url("admin/services/oaf/api/system/get_tcp_rst")%>',
|
||||
tcpRstSet: '<%=url("admin/services/oaf/api/system/set_tcp_rst")%>',
|
||||
appFilterGet: '<%=url("admin/services/oaf/api/app_filter/get_app_filter_adv")%>',
|
||||
appFilterSet: '<%=url("admin/services/oaf/api/app_filter/set_app_filter_adv")%>',
|
||||
accessControlGet: '<%=url("admin/services/oaf/api/mac_filter/get_mac_filter_adv")%>',
|
||||
accessControlSet: '<%=url("admin/services/oaf/api/mac_filter/set_mac_filter_adv")%>',
|
||||
advancedGet: '<%=url("admin/services/oaf/api/system/get_advanced_settings")%>',
|
||||
advancedSet: '<%=url("admin/services/oaf/api/system/set_advanced_settings")%>',
|
||||
recordGet: '<%=url("admin/services/oaf/api/internet_audit/get_record_base")%>',
|
||||
recordSet: '<%=url("admin/services/oaf/api/internet_audit/set_record_base")%>',
|
||||
recordAction: '<%=url("admin/services/oaf/api/internet_audit/record_action")%>'
|
||||
};
|
||||
var currentThemeMode = 0;
|
||||
var settingsLoaded = false;
|
||||
|
||||
function isSuccess(resp) {
|
||||
return !!resp && (resp.code === 0 || resp.code === 2000);
|
||||
}
|
||||
|
||||
function showResult(message, success) {
|
||||
if (typeof showCommonModal === 'function') {
|
||||
showCommonModal(message || '', 2000, success ? 'success' : 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function runTasks(tasks, done) {
|
||||
var remaining = tasks.length;
|
||||
var success = true;
|
||||
|
||||
if (!remaining) {
|
||||
done(true);
|
||||
return;
|
||||
}
|
||||
tasks.forEach(function(task) {
|
||||
task(function(ok) {
|
||||
success = success && !!ok;
|
||||
remaining--;
|
||||
if (!remaining) done(success);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadTask(url, applyData) {
|
||||
return function(done) {
|
||||
$.getJSON(url).done(function(resp) {
|
||||
if (!isSuccess(resp) || !resp.data) {
|
||||
done(false);
|
||||
return;
|
||||
}
|
||||
done(applyData(resp.data) !== false);
|
||||
}).fail(function() {
|
||||
done(false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function postTask(url, data) {
|
||||
return function(done) {
|
||||
$.ajax({url:url,method:'POST',dataType:'json',data:data}).done(function(resp) {
|
||||
done(isSuccess(resp));
|
||||
}).fail(function() {
|
||||
done(false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function updateWorkModeWarning(value) {
|
||||
$('#work-mode-warning').toggle(parseInt(value, 10) === 1);
|
||||
}
|
||||
|
||||
function applyRecordData(data) {
|
||||
$('#rec-enable').prop('checked', data.enable == 1);
|
||||
$('#rec-keep').val(data.record_time || 0);
|
||||
$('#rec-valid').val(data.app_valid_time || 0);
|
||||
$('#rec-history-size').val(data.history_data_size || '');
|
||||
$('#rec-history-path').val(data.history_data_path || '');
|
||||
$('#rec-base-path').val(data.base_data_path || data.terminal_data_path || '');
|
||||
|
||||
if (data.status && data.status.data_size !== undefined) {
|
||||
var size = data.status.data_size || 0;
|
||||
var unit = data.status.data_size_unit || 'KB';
|
||||
var display = size + unit;
|
||||
if (unit === 'KB' && size >= 1024) {
|
||||
var mb = size / 1024;
|
||||
display = (mb % 1 === 0 ? mb.toFixed(0) : mb.toFixed(1)) + 'MB';
|
||||
}
|
||||
$('#data-size-display').text(display);
|
||||
$('#data-size-group').show();
|
||||
} else {
|
||||
$('#data-size-group').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAll() {
|
||||
var tasks = [];
|
||||
|
||||
settingsLoaded = false;
|
||||
$('#settings-refresh,#settings-save').prop('disabled', true);
|
||||
$('#settings-tip').text(text.loading);
|
||||
tasks.push(loadTask(api.workModeGet, function(data) {
|
||||
var value = parseInt(data.work_mode, 10) === 1 ? '1' : '0';
|
||||
$('#work-mode').val(value);
|
||||
updateWorkModeWarning(value);
|
||||
}));
|
||||
tasks.push(loadTask(api.systemGet, function(data) {
|
||||
var fwx = data.fwx;
|
||||
if (!fwx) return false;
|
||||
$('#lan-ifname').val(fwx.lan_ifname || 'br-lan');
|
||||
currentThemeMode = fwx.theme_mode == 1 ? 1 : 0;
|
||||
}));
|
||||
tasks.push(loadTask(api.tcpRstGet, function(data) {
|
||||
$('#tcp-rst').prop('checked', data.tcp_rst != 0);
|
||||
}));
|
||||
<% if has_app_filter then %>
|
||||
tasks.push(loadTask(api.appFilterGet, function(data) {
|
||||
$('#app-filter-enable').prop('checked', data.enable == 1);
|
||||
}));
|
||||
<% end %>
|
||||
<% if has_access_control then %>
|
||||
tasks.push(loadTask(api.accessControlGet, function(data) {
|
||||
$('#access-control-enable').prop('checked', data.enable == 1);
|
||||
}));
|
||||
<% end %>
|
||||
tasks.push(loadTask(api.advancedGet, function(data) {
|
||||
$('#disable-hnat').prop('checked', data.disable_hnat == 1);
|
||||
}));
|
||||
tasks.push(loadTask(api.recordGet, applyRecordData));
|
||||
|
||||
runTasks(tasks, function(success) {
|
||||
settingsLoaded = success;
|
||||
$('#settings-refresh').prop('disabled', false);
|
||||
$('#settings-save').prop('disabled', !success);
|
||||
$('#settings-tip').text(success ? '' : text.loadFailed);
|
||||
});
|
||||
}
|
||||
|
||||
function validateSettings() {
|
||||
var lanIfname = $.trim($('#lan-ifname').val() || '');
|
||||
var historySize = $.trim($('#rec-history-size').val() || '');
|
||||
var historyPath = $.trim($('#rec-history-path').val() || '');
|
||||
var basePath = $.trim($('#rec-base-path').val() || '');
|
||||
|
||||
$('#lan-ifname-error').hide();
|
||||
$('#lan-ifname').css('border-color', '');
|
||||
if (!/^[a-zA-Z0-9-]{2,16}$/.test(lanIfname)) {
|
||||
$('#lan-ifname-error').text(text.invalidLan).show();
|
||||
$('#lan-ifname').css('border-color', '#f87171');
|
||||
return null;
|
||||
}
|
||||
if (historySize) {
|
||||
var size = Number(historySize);
|
||||
if (!/^\d+$/.test(historySize) || isNaN(size) || size < 1 || size > 1024) {
|
||||
showResult(text.invalidSize, false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!historyPath || historyPath === '/') {
|
||||
showResult(text.emptyHistoryPath, false);
|
||||
return null;
|
||||
}
|
||||
if (historyPath.length > 64) {
|
||||
showResult(text.longHistoryPath, false);
|
||||
return null;
|
||||
}
|
||||
if (!basePath || basePath === '/') {
|
||||
showResult(text.emptyBasePath, false);
|
||||
return null;
|
||||
}
|
||||
if (basePath.length > 64) {
|
||||
showResult(text.longBasePath, false);
|
||||
return null;
|
||||
}
|
||||
return {lanIfname:lanIfname,historySize:historySize,historyPath:historyPath,basePath:basePath};
|
||||
}
|
||||
|
||||
function saveAll() {
|
||||
var values;
|
||||
var tasks = [];
|
||||
|
||||
if (!settingsLoaded) {
|
||||
showResult(text.loadFailed, false);
|
||||
return;
|
||||
}
|
||||
values = validateSettings();
|
||||
if (!values) return;
|
||||
|
||||
tasks.push(postTask(api.workModeSet, {work_mode:$('#work-mode').val() || '0'}));
|
||||
tasks.push(postTask(api.systemSet, {lan_ifname:values.lanIfname,theme_mode:currentThemeMode}));
|
||||
tasks.push(postTask(api.tcpRstSet, {tcp_rst:$('#tcp-rst').is(':checked') ? 1 : 0}));
|
||||
<% if has_app_filter then %>
|
||||
tasks.push(postTask(api.appFilterSet, {enable:$('#app-filter-enable').is(':checked') ? 1 : 0}));
|
||||
<% end %>
|
||||
<% if has_access_control then %>
|
||||
tasks.push(postTask(api.accessControlSet, {enable:$('#access-control-enable').is(':checked') ? 1 : 0}));
|
||||
<% end %>
|
||||
tasks.push(postTask(api.advancedSet, {disable_hnat:$('#disable-hnat').is(':checked') ? 1 : 0}));
|
||||
tasks.push(postTask(api.recordSet, {
|
||||
enable:$('#rec-enable').is(':checked') ? 1 : 0,
|
||||
record_time:parseInt($('#rec-keep').val(), 10) || 0,
|
||||
app_valid_time:parseInt($('#rec-valid').val(), 10) || 0,
|
||||
history_data_size:values.historySize,
|
||||
history_data_path:values.historyPath,
|
||||
base_data_path:values.basePath
|
||||
}));
|
||||
|
||||
$('#settings-refresh,#settings-save').prop('disabled', true);
|
||||
runTasks(tasks, function(success) {
|
||||
$('#settings-refresh,#settings-save').prop('disabled', false);
|
||||
updateWorkModeWarning($('#work-mode').val());
|
||||
showResult(success ? text.saved : text.saveFailed, success);
|
||||
});
|
||||
}
|
||||
|
||||
function cleanHistory() {
|
||||
if (!window.confirm(text.cleanConfirm)) return;
|
||||
$('#clean-history').prop('disabled', true);
|
||||
$.ajax({url:api.recordAction,method:'POST',dataType:'json',data:{action:'clean_all_data'}}).done(function(resp) {
|
||||
if (isSuccess(resp)) {
|
||||
showResult(text.cleanSuccess, true);
|
||||
loadTask(api.recordGet, applyRecordData)(function() {});
|
||||
} else {
|
||||
showResult(text.cleanFailed, false);
|
||||
}
|
||||
}).fail(function() {
|
||||
showResult(text.cleanFailed, false);
|
||||
}).always(function() {
|
||||
$('#clean-history').prop('disabled', false);
|
||||
});
|
||||
}
|
||||
|
||||
$('.settings-panel-header').on('click', function() {
|
||||
var header = $(this);
|
||||
var panel = $('#' + header.attr('aria-controls'));
|
||||
var expanded = header.attr('aria-expanded') === 'true';
|
||||
header.attr('aria-expanded', expanded ? 'false' : 'true').toggleClass('collapsed', expanded);
|
||||
panel.stop(true, true).slideToggle(120);
|
||||
});
|
||||
$('#work-mode').on('change', function() { updateWorkModeWarning(this.value); });
|
||||
$('#settings-refresh').on('click', refreshAll);
|
||||
$('#settings-save').on('click', saveAll);
|
||||
$('#clean-history').on('click', cleanHistory);
|
||||
$(refreshAll);
|
||||
})();
|
||||
//]]></script>
|
||||
|
||||
<%+footer%>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,437 @@
|
||||
<%+header%>
|
||||
<link rel="stylesheet" href="<%=resource%>/oaf/css/common.css">
|
||||
<script src="<%=resource%>/oaf/jquery-3.7.1.min.js"></script>
|
||||
<script src="<%=resource%>/oaf/oaf_icon.js"></script>
|
||||
|
||||
<div style="padding: 8px;">
|
||||
<div class="cbi-section">
|
||||
<ul class="tab-list">
|
||||
<li class="tab-item active" data-tab="online-records"><%:Online Records%></li>
|
||||
<li class="tab-item" data-tab="history-records"><%:History Records%></li>
|
||||
</ul>
|
||||
|
||||
<div id="tab-online-records" class="tab-body active">
|
||||
<table class="table cbi-section-table" id="online-record-table" style="table-layout: fixed; width: 100%;">
|
||||
<colgroup>
|
||||
<col class="record-app-col">
|
||||
<col class="record-mac-col">
|
||||
<col class="record-hostname-col">
|
||||
<col class="record-nickname-col">
|
||||
<col class="record-time-col">
|
||||
<col class="record-time-col">
|
||||
<col class="record-duration-col">
|
||||
<col class="record-status-col">
|
||||
<col class="record-status-col">
|
||||
</colgroup>
|
||||
<tr class="tr table-titles">
|
||||
<th class="th"><%:App%></th>
|
||||
<th class="th">MAC</th>
|
||||
<th class="th"><%:Hostname%></th>
|
||||
<th class="th"><%:Nickname%></th>
|
||||
<th class="th"><%:First Visit%></th>
|
||||
<th class="th"><%:Last Visit%></th>
|
||||
<th class="th"><%:Duration%></th>
|
||||
<th class="th"><%:Online%></th>
|
||||
<th class="th"><%:Filter Status%></th>
|
||||
</tr>
|
||||
<tr class="tr">
|
||||
<td class="td" colspan="9"><em><br /><%:Loading...%></em></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="online-pagination" style="margin-top: 12px;"></div>
|
||||
</div>
|
||||
|
||||
<div id="tab-history-records" class="tab-body">
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:10px 0 12px 0;">
|
||||
<input id="q-history-mac" class="cbi-input-text" type="text" placeholder="<%:Terminal MAC%>" style="width:200px;">
|
||||
<input id="q-history-start" class="cbi-input-text" type="datetime-local" style="width:210px;">
|
||||
<input id="q-history-end" class="cbi-input-text" type="datetime-local" style="width:210px;">
|
||||
<button type="button" class="cbi-button cbi-button-action" id="btn-history-search"><%:Search%></button>
|
||||
<button type="button" class="cbi-button" id="btn-history-reset"><%:Reset%></button>
|
||||
</div>
|
||||
|
||||
<table class="table cbi-section-table" id="history-record-table" style="table-layout: fixed; width: 100%;">
|
||||
<colgroup>
|
||||
<col class="record-app-col">
|
||||
<col class="record-mac-col">
|
||||
<col class="record-hostname-col">
|
||||
<col class="record-nickname-col">
|
||||
<col class="record-time-col">
|
||||
<col class="record-time-col">
|
||||
<col class="record-duration-col">
|
||||
<col class="record-status-col">
|
||||
<col class="record-status-col">
|
||||
</colgroup>
|
||||
<tr class="tr table-titles">
|
||||
<th class="th"><%:App%></th>
|
||||
<th class="th">MAC</th>
|
||||
<th class="th"><%:Hostname%></th>
|
||||
<th class="th"><%:Nickname%></th>
|
||||
<th class="th"><%:First Visit%></th>
|
||||
<th class="th"><%:Last Visit%></th>
|
||||
<th class="th"><%:Duration%></th>
|
||||
<th class="th"><%:Online%></th>
|
||||
<th class="th"><%:Filter Status%></th>
|
||||
</tr>
|
||||
<tr class="tr">
|
||||
<td class="td" colspan="9"><em><br /><%:Loading...%></em></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div id="history-pagination" style="margin-top: 12px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tab-list {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid var(--border-color-medium, #d1d5db);
|
||||
}
|
||||
.tab-item {
|
||||
padding: 3px 16px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-color-medium, #d1d5db);
|
||||
border-bottom: none;
|
||||
border-radius: 5px 5px 0 0;
|
||||
margin-right: 2px;
|
||||
background-color: var(--background-color-low, #f5f5f5);
|
||||
color: var(--text-color-medium, #6b7280);
|
||||
}
|
||||
.tab-item.active {
|
||||
font-weight: bold;
|
||||
background-color: var(--background-color-high, #ffffff);
|
||||
color: var(--text-color-high, #333);
|
||||
}
|
||||
.tab-item:hover {
|
||||
background-color: var(--background-color-medium, #eeeeee);
|
||||
}
|
||||
.tab-body {
|
||||
margin-top: 6px;
|
||||
display: none;
|
||||
}
|
||||
.tab-body.active {
|
||||
display: block;
|
||||
}
|
||||
#online-record-table .th, #history-record-table .th {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
#online-record-table .td, #history-record-table .td {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
#online-record-table,
|
||||
#history-record-table {
|
||||
--record-app-col-width: 138px;
|
||||
--record-app-cell-padding: 14px;
|
||||
--record-app-icon-size: 22px;
|
||||
--record-app-icon-gap: 7px;
|
||||
}
|
||||
#online-record-table .record-app-col,
|
||||
#history-record-table .record-app-col {
|
||||
width: var(--record-app-col-width);
|
||||
}
|
||||
#online-record-table .record-mac-col,
|
||||
#history-record-table .record-mac-col {
|
||||
width: 15%;
|
||||
}
|
||||
#online-record-table .record-hostname-col,
|
||||
#history-record-table .record-hostname-col,
|
||||
#online-record-table .record-nickname-col,
|
||||
#history-record-table .record-nickname-col {
|
||||
width: 11%;
|
||||
}
|
||||
#online-record-table .record-time-col,
|
||||
#history-record-table .record-time-col {
|
||||
width: 15%;
|
||||
}
|
||||
#online-record-table .record-duration-col,
|
||||
#history-record-table .record-duration-col {
|
||||
width: 8%;
|
||||
}
|
||||
#online-record-table .record-status-col,
|
||||
#history-record-table .record-status-col {
|
||||
width: 6%;
|
||||
}
|
||||
#online-record-table .td:nth-child(5),
|
||||
#online-record-table .td:nth-child(6),
|
||||
#history-record-table .td:nth-child(5),
|
||||
#history-record-table .td:nth-child(6) {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.record-app-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--record-app-icon-gap);
|
||||
min-width: 0;
|
||||
width: calc(var(--record-app-col-width) - var(--record-app-cell-padding));
|
||||
}
|
||||
.record-app-cell .record-app-name {
|
||||
min-width: 0;
|
||||
max-width: calc(var(--record-app-col-width) - var(--record-app-cell-padding) - var(--record-app-icon-size) - var(--record-app-icon-gap));
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
(function() {
|
||||
var text = {
|
||||
filtered: '<%:Filtered%>',
|
||||
unfiltered: '<%:Unfiltered%>',
|
||||
online: '<%:Online%>',
|
||||
offline: '<%:Offline%>',
|
||||
noData: '<%:No Data%>',
|
||||
previous: '<%:Previous%>',
|
||||
next: '<%:Next%>',
|
||||
total: '<%:Total%>',
|
||||
records: '<%:Records%>'
|
||||
};
|
||||
var online_state = { page: 1, page_size: 15, total_num: 0, total_page: 1 };
|
||||
var history_state = { page: 1, page_size: 15, total_num: 0, total_page: 1 };
|
||||
var history_loaded = false;
|
||||
|
||||
function pad2(n) {
|
||||
n = parseInt(n || 0, 10);
|
||||
return n < 10 ? ("0" + n) : ("" + n);
|
||||
}
|
||||
|
||||
function format_time(ts) {
|
||||
var t = parseInt(ts || 0, 10);
|
||||
if (!t) return "--";
|
||||
var d = new Date(t * 1000);
|
||||
if (isNaN(d.getTime())) return "--";
|
||||
return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()) + " "
|
||||
+ pad2(d.getHours()) + ":" + pad2(d.getMinutes());
|
||||
}
|
||||
|
||||
function format_duration(sec) {
|
||||
var s = parseInt(sec || 0, 10);
|
||||
if (s < 0) s = 0;
|
||||
var h = Math.floor(s / 3600);
|
||||
var m = Math.floor((s % 3600) / 60);
|
||||
var r = s % 60;
|
||||
if (h > 0) return h + "h" + (m > 0 ? " " + m + "m" : "");
|
||||
if (m > 0) return m + "m" + (r > 0 ? " " + r + "s" : "");
|
||||
return r + "s";
|
||||
}
|
||||
|
||||
function get_ts_from_input(selector) {
|
||||
var v = $(selector).val();
|
||||
if (!v) return 0;
|
||||
var ms = new Date(v).getTime();
|
||||
if (!ms || ms < 0) return 0;
|
||||
return Math.floor(ms / 1000);
|
||||
}
|
||||
|
||||
function render_filter_status(act) {
|
||||
return parseInt(act || 0, 10) === 1
|
||||
? '<span style="color:#F44336;">' + text.filtered + '</span>'
|
||||
: '<span style="color:#22c55e;">' + text.unfiltered + '</span>';
|
||||
}
|
||||
|
||||
function render_online_status(online) {
|
||||
return parseInt(online || 0, 10) === 1
|
||||
? '<span style="color:#22c55e;">' + text.online + '</span>'
|
||||
: '<span style="color:#F44336;">' + text.offline + '</span>';
|
||||
}
|
||||
|
||||
function app_display_name(app_id, app_name) {
|
||||
var id = app_id === undefined || app_id === null ? "" : String(app_id);
|
||||
var name = $.trim(app_name || "");
|
||||
if (!name || name === "--" || name === "Unknown" || name === "Unknown App") {
|
||||
return id && id !== "0" ? ("App" + id) : "--";
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function render_app_cell($cell, app_id, app_name, app_icon) {
|
||||
var id = app_id === undefined || app_id === null ? "" : String(app_id);
|
||||
var name = app_display_name(id, app_name);
|
||||
var $wrap = $('<div>').addClass('record-app-cell').attr('title', name);
|
||||
var $icon = window.OAFIcon
|
||||
? window.OAFIcon.createAppIcon(id, name, '<%=resource%>', { size: '22px', radius: '5px', icon: app_icon })
|
||||
: $('<span>').text('');
|
||||
|
||||
$wrap.append($icon).append($('<span>').addClass('record-app-name').text(name));
|
||||
$cell.empty().append($wrap);
|
||||
}
|
||||
|
||||
function render_table(table_id, list) {
|
||||
var $tb = $("#" + table_id);
|
||||
$tb.find("tr:gt(0)").remove();
|
||||
if (!Array.isArray(list) || list.length === 0) {
|
||||
$tb.append('<tr class="tr"><td class="td" colspan="9"><em><br />' + text.noData + '</em></td></tr>');
|
||||
return;
|
||||
}
|
||||
list.forEach(function(item) {
|
||||
var app_id = item.id || item.appid || "";
|
||||
var app_name = app_display_name(app_id, item.name || item.appname || "");
|
||||
var mac = item.mac || "--";
|
||||
var hostname = item.hostname || "--";
|
||||
var nickname = item.nickname || "--";
|
||||
var ft = item.ft || item.first_time || 0;
|
||||
var lt = item.lt || item.latest_time || 0;
|
||||
var tt = item.tt || item.total_time || 0;
|
||||
var act = item.act || item.latest_action || 0;
|
||||
var online = item.online || 0;
|
||||
|
||||
var $row = $('<tr>').addClass('tr');
|
||||
var $appCell = $('<td>').addClass('td record-app-td');
|
||||
|
||||
render_app_cell($appCell, app_id, app_name, item.icon);
|
||||
$row
|
||||
.append($appCell)
|
||||
.append($('<td>').addClass('td').attr('title', mac).text(mac))
|
||||
.append($('<td>').addClass('td').attr('title', hostname).text(hostname))
|
||||
.append($('<td>').addClass('td').attr('title', nickname).text(nickname))
|
||||
.append($('<td>').addClass('td').text(format_time(ft)))
|
||||
.append($('<td>').addClass('td').text(format_time(lt)))
|
||||
.append($('<td>').addClass('td').text(format_duration(tt)))
|
||||
.append($('<td>').addClass('td').html(render_online_status(online)))
|
||||
.append($('<td>').addClass('td').html(render_filter_status(act)));
|
||||
$tb.append($row);
|
||||
});
|
||||
}
|
||||
|
||||
function render_pagination(container_id, state, go_fn) {
|
||||
var $p = $("#" + container_id);
|
||||
if (state.total_page <= 1) {
|
||||
$p.html("");
|
||||
return;
|
||||
}
|
||||
|
||||
var $wrap = $('<div>').css({
|
||||
display: 'flex',
|
||||
gap: '6px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap'
|
||||
});
|
||||
var $prev = $('<button type="button">').addClass('cbi-button').text(text.previous).prop('disabled', state.page <= 1);
|
||||
var $next = $('<button type="button">').addClass('cbi-button').text(text.next).prop('disabled', state.page >= state.total_page);
|
||||
var $pageText = $('<span>').text(state.page + ' / ' + state.total_page);
|
||||
var $jumpInput = $('<input type="number">')
|
||||
.addClass('cbi-input-text')
|
||||
.attr({ min: 1, max: state.total_page })
|
||||
.css({ width: '72px', height: '28px', boxSizing: 'border-box' })
|
||||
.val(state.page);
|
||||
var $jumpBtn = $('<button type="button">').addClass('cbi-button').text('Go');
|
||||
var $total = $('<span>').css('color', 'var(--text-color-medium, #999)')
|
||||
.text(text.total + ' ' + state.total_num + ' ' + text.records);
|
||||
|
||||
function jump_to_input_page() {
|
||||
var target = parseInt($jumpInput.val(), 10);
|
||||
if (isNaN(target)) {
|
||||
target = state.page;
|
||||
}
|
||||
target = Math.max(1, Math.min(state.total_page, target));
|
||||
$jumpInput.val(target);
|
||||
if (target !== state.page) {
|
||||
go_fn(target);
|
||||
}
|
||||
}
|
||||
|
||||
$prev.on("click", function() {
|
||||
if (state.page > 1) go_fn(state.page - 1);
|
||||
});
|
||||
$next.on("click", function() {
|
||||
if (state.page < state.total_page) go_fn(state.page + 1);
|
||||
});
|
||||
$jumpBtn.on("click", jump_to_input_page);
|
||||
$jumpInput.on("keydown", function(e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
jump_to_input_page();
|
||||
}
|
||||
});
|
||||
|
||||
$wrap.append($prev, $pageText, $next, $jumpInput, $jumpBtn, $total);
|
||||
$p.empty().append($wrap);
|
||||
}
|
||||
|
||||
function load_online_records() {
|
||||
new XHR().get("<%=url('admin/services/oaf/api/app_record/get_active_app_records')%>", {
|
||||
page: online_state.page,
|
||||
page_size: online_state.page_size
|
||||
}, function(x, data) {
|
||||
var resp = data || {};
|
||||
online_state.total_num = parseInt(resp.total_num || 0, 10);
|
||||
online_state.total_page = parseInt(resp.total_page || 1, 10);
|
||||
online_state.page = parseInt(resp.page || online_state.page, 10);
|
||||
online_state.page_size = parseInt(resp.page_size || online_state.page_size, 10);
|
||||
render_table("online-record-table", resp.list || []);
|
||||
render_pagination("online-pagination", online_state, function(p) {
|
||||
online_state.page = p;
|
||||
load_online_records();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function load_history_records() {
|
||||
new XHR().get("<%=url('admin/services/oaf/api/app_record/get_app_history_records')%>", {
|
||||
mac: $("#q-history-mac").val() || "",
|
||||
start_time: get_ts_from_input("#q-history-start"),
|
||||
end_time: get_ts_from_input("#q-history-end"),
|
||||
page: history_state.page,
|
||||
page_size: history_state.page_size
|
||||
}, function(x, data) {
|
||||
var resp = data || {};
|
||||
history_state.total_num = parseInt(resp.total_num || 0, 10);
|
||||
history_state.total_page = parseInt(resp.total_page || 1, 10);
|
||||
history_state.page = parseInt(resp.page || history_state.page, 10);
|
||||
history_state.page_size = parseInt(resp.page_size || history_state.page_size, 10);
|
||||
render_table("history-record-table", resp.list || []);
|
||||
render_pagination("history-pagination", history_state, function(p) {
|
||||
history_state.page = p;
|
||||
load_history_records();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function switch_tab(tab_id) {
|
||||
$(".tab-item").removeClass("active");
|
||||
$('.tab-item[data-tab="' + tab_id + '"]').addClass("active");
|
||||
$(".tab-body").removeClass("active");
|
||||
$("#tab-" + tab_id).addClass("active");
|
||||
|
||||
if (tab_id === "history-records" && !history_loaded) {
|
||||
history_loaded = true;
|
||||
load_history_records();
|
||||
}
|
||||
}
|
||||
|
||||
$(".tab-item").on("click", function() {
|
||||
var tab_id = $(this).attr("data-tab");
|
||||
if (!tab_id) return;
|
||||
switch_tab(tab_id);
|
||||
});
|
||||
|
||||
$("#btn-history-search").on("click", function() {
|
||||
history_state.page = 1;
|
||||
load_history_records();
|
||||
});
|
||||
|
||||
$("#btn-history-reset").on("click", function() {
|
||||
$("#q-history-mac").val("");
|
||||
$("#q-history-start").val("");
|
||||
$("#q-history-end").val("");
|
||||
history_state.page = 1;
|
||||
load_history_records();
|
||||
});
|
||||
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('tab') === 'history') {
|
||||
switch_tab('history-records');
|
||||
}
|
||||
load_online_records();
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
<%+footer%>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,295 @@
|
||||
<%+header%>
|
||||
<link rel="stylesheet" href="<%=resource%>/oaf/css/common.css">
|
||||
<script type="text/javascript" src="<%=resource%>/oaf/jquery-3.7.1.min.js"></script>
|
||||
<script type="text/javascript" src="<%=resource%>/oaf/common.js"></script>
|
||||
<script type="text/javascript" src="<%=resource%>/oaf/oaf_icon.js"></script>
|
||||
|
||||
<div class="feature-container">
|
||||
<div class="feature-page-heading">
|
||||
<div class="feature-page-title"><%:App Feature Library%></div>
|
||||
<div class="feature-page-desc"><%:The feature library is used to describe app features, and determines the effectiveness of app filtering and internet usage records%></div>
|
||||
</div>
|
||||
|
||||
<ul class="tab-list cbi-tabmenu">
|
||||
<li class="tab-item cbi-tab active" data-tab="tab-basic"><%:Basic Info%></li>
|
||||
<li class="tab-item cbi-tab" data-tab="tab-online"><%:Online Update%></li>
|
||||
<li class="tab-item cbi-tab" data-tab="tab-custom"><%:Custom Applications%></li>
|
||||
</ul>
|
||||
|
||||
<div class="cbi-tabcontainer">
|
||||
<div id="tab-basic" class="tab-body active cbi-tab">
|
||||
<div id="feature-info" class="feature-info-card">
|
||||
<div class="feature-info-grid">
|
||||
<div class="feature-info-item">
|
||||
<span class="feature-info-label"><%:Current version%></span>
|
||||
<span class="feature-info-value" id="info-version">--</span>
|
||||
</div>
|
||||
<div class="feature-info-item">
|
||||
<span class="feature-info-label"><%:Feature Library Type%></span>
|
||||
<span class="feature-info-value" id="info-free">--</span>
|
||||
</div>
|
||||
<div class="feature-info-item">
|
||||
<span class="feature-info-label"><%:Feature format%></span>
|
||||
<span class="feature-info-value" id="info-format">--</span>
|
||||
</div>
|
||||
<div class="feature-info-item">
|
||||
<span class="feature-info-label"><%:App number%></span>
|
||||
<span class="feature-info-value" id="info-appnum">--</span>
|
||||
</div>
|
||||
<div class="feature-info-item">
|
||||
<span class="feature-info-label"><%:Official Website%></span>
|
||||
<span class="feature-info-value"><a href="http://www.openappfilter.com" target="_blank">www.openappfilter.com</a></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-app-panel">
|
||||
<div class="custom-title"><%:App List%></div>
|
||||
<div id="app-list" class="app-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-online" class="tab-body cbi-tab">
|
||||
<div class="online-panel">
|
||||
<div class="custom-title"><%:Online Feature Library Update%></div>
|
||||
<div class="feature-hint"><%:Visit%> <a href="http://www.openappfilter.com" target="_blank">www.openappfilter.com</a> <%:to obtain the advanced feature library update Key.%></div>
|
||||
<div class="online-control-stack">
|
||||
<div class="online-control-row">
|
||||
<label for="online-token"><%:Subscription Key%></label>
|
||||
<div class="online-token-input">
|
||||
<input type="password" id="online-token" autocomplete="off">
|
||||
<button type="button" class="online-token-eye" id="online-token-toggle" title="<%:Show%>" aria-label="<%:Show%>"></button>
|
||||
</div>
|
||||
<div class="online-actions">
|
||||
<button type="button" class="cbi-button cbi-button-apply" id="online-save-token"><%:Save%></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-hint"><%: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.%></div>
|
||||
<div class="online-control-row">
|
||||
<label for="online-lang"><%:Language%></label>
|
||||
<select id="online-lang" class="online-lang-select">
|
||||
<option value="en"><%:English%></option>
|
||||
<option value="cn"><%:Chinese%></option>
|
||||
</select>
|
||||
<div class="online-actions">
|
||||
<button type="button" class="cbi-button online-fetch-button" id="online-fetch-list"><%:Update List%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="online-device-info">
|
||||
<span><%:Current Version Number%>: <strong id="online-current-version">--</strong></span>
|
||||
<span><%:Feature Library Type%>: <strong id="online-current-free">--</strong></span>
|
||||
<span><%:Feature format%>: <strong id="online-version">v4.0</strong></span>
|
||||
</div>
|
||||
<div id="online-status" class="feature-status" style="display:none;"></div>
|
||||
<div id="online-announcement" class="online-announcement" style="display:none;">
|
||||
<span class="announcement-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 10v4a1 1 0 0 0 1 1h3l5 4V5L8 9H5a1 1 0 0 0-1 1Z"/><path d="M16 9.5a4 4 0 0 1 0 5"/><path d="M19 7a8 8 0 0 1 0 10"/></svg>
|
||||
</span>
|
||||
<span id="online-announcement-text"></span>
|
||||
</div>
|
||||
<div id="online-list-empty" class="custom-empty"><%:Click Update List to load available feature libraries%></div>
|
||||
<div id="online-list-wrap" class="online-list-wrap" style="display:none;">
|
||||
<table class="common-table online-table">
|
||||
<thead><tr>
|
||||
<th><%:Feature Library%></th>
|
||||
<th><%:Type%></th>
|
||||
<th><%:Application Count%></th>
|
||||
<th><%:Language%></th>
|
||||
<th><%:Release Date%></th>
|
||||
<th><%:Actions%></th>
|
||||
</tr></thead>
|
||||
<tbody id="online-list-body"></tbody>
|
||||
</table>
|
||||
<div id="online-card-list" class="online-card-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-custom" class="tab-body cbi-tab">
|
||||
<div class="custom-panel">
|
||||
<div class="custom-toolbar">
|
||||
<div class="custom-title"><%:Custom Applications%></div>
|
||||
<button type="button" class="cbi-button cbi-button-add" id="custom-add"><%:Add Application%></button>
|
||||
</div>
|
||||
<div id="custom-status" class="feature-hint"><%:Loading...%></div>
|
||||
<div id="custom-empty" class="custom-empty" style="display:none;"><%:No custom applications%></div>
|
||||
<div class="custom-table-wrap" id="custom-table-wrap" style="display:none;">
|
||||
<table class="common-table custom-table">
|
||||
<thead><tr>
|
||||
<th><%:Application Name%></th>
|
||||
<th><%:Category%></th>
|
||||
<th><%:Feature Count%></th>
|
||||
<th><%:Actions%></th>
|
||||
</tr></thead>
|
||||
<tbody id="custom-table-body"></tbody>
|
||||
</table>
|
||||
<div id="custom-card-list" class="custom-card-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="custom-editor" class="custom-editor-mask" style="display:none;">
|
||||
<div class="custom-editor-dialog">
|
||||
<div class="custom-editor-header">
|
||||
<h3 id="custom-editor-title"><%:Add Application%></h3>
|
||||
<button type="button" class="editor-close" id="custom-editor-close" title="<%:Close%>">×</button>
|
||||
</div>
|
||||
<div class="custom-editor-body">
|
||||
<div class="editor-basic-grid">
|
||||
<div class="editor-field">
|
||||
<label for="custom-name"><%:Application Name%> *</label>
|
||||
<input type="text" id="custom-name" maxlength="63">
|
||||
</div>
|
||||
<div class="editor-field">
|
||||
<label for="custom-class"><%:Category%> *</label>
|
||||
<select id="custom-class"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-editor-toolbar">
|
||||
<span><%:Application Features%></span>
|
||||
<button type="button" class="cbi-button cbi-button-add" id="add-feature"><%:Add Feature%></button>
|
||||
</div>
|
||||
<div id="feature-editors"></div>
|
||||
<div id="custom-editor-error" class="editor-error" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="custom-editor-footer">
|
||||
<button type="button" class="cbi-button" id="custom-cancel"><%:Cancel%></button>
|
||||
<button type="button" class="cbi-button cbi-button-apply" id="custom-save"><%:Save%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.feature-container{padding:10px}.feature-page-heading{margin:2px 0 14px}.feature-page-title{font-size:22px;font-weight:700;color:var(--text-color-high,#333);line-height:1.35}.feature-page-desc{margin-top:4px;color:var(--text-color-medium,#6b7280);font-size:13px;line-height:1.5}.feature-container .tab-list{display:flex;list-style:none;padding:0;margin:10px 0 15px;border-bottom:1px solid var(--border-color-medium,#d1d5db);overflow-x:auto}.feature-container .tab-item{padding:3px 16px;cursor:pointer;border:1px solid var(--border-color-medium,#d1d5db)!important;border-bottom:none!important;margin-right:6px;border-radius:5px 5px 0 0;background:var(--background-color-low,#f5f5f5)!important;color:var(--text-color-medium,#6b7280)!important;white-space:nowrap}.feature-container .tab-item.active{background:var(--background-color-high,#fff)!important;color:var(--text-color-high,#333)!important;font-weight:bold}.feature-container .tab-item:hover{background:var(--background-color-medium,#eee)!important}.tab-body{display:none}.tab-body.active{display:block}.feature-info-card,.custom-panel{padding:16px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:8px;background:var(--background-color-high,#fff);margin-bottom:12px}.feature-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.feature-info-item{padding:10px 12px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:6px;background:var(--background-color-low,#f5f5f5)}.feature-info-label{display:block;font-size:13px;color:var(--text-color-medium,#6b7280);margin-bottom:4px}.feature-info-value{font-weight:600;font-size:15px;color:var(--text-color-high,#333)}.feature-card{background:var(--background-color-high,#fff);border:1px solid var(--border-color-low,#e5e7eb);border-radius:8px;padding:16px;margin-top:12px}.feature-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.feature-label{min-width:120px;font-weight:600}.feature-inputs{display:flex;align-items:center;gap:10px;flex:1}.feature-file{width:100%;max-width:360px}.feature-btn{min-width:110px}.feature-hint{margin-top:10px;color:var(--text-color-medium,#6b7280);font-size:13px;line-height:1.5}.feature-status{margin-top:8px;font-size:14px;font-weight:600}.feature-status.success{color:#34d399}.feature-status.error,.editor-error{color:#f87171}.feature-modal{display:none;position:fixed;inset:0;background-color:rgba(0,0,0,.5);z-index:1000;justify-content:center;align-items:center}.feature-modal-content{background:var(--background-color-high,#fff);padding:16px 20px;border-radius:6px;color:var(--text-color-high,#333);min-width:200px;text-align:center;box-shadow:0 4px 12px rgba(0,0,0,.4);border:1px solid var(--border-color-low,#e5e7eb)}.app-list{margin-top:10px}.class-card{margin-bottom:16px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:8px;background:var(--background-color-high,#fff)}.class-header{padding:12px 16px;font-weight:600;font-size:15px;border-bottom:1px solid var(--border-color-low,#e5e7eb);color:var(--text-color-high,#333)}.app-items{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;padding:12px 16px}.app-item{display:flex;align-items:center;gap:8px;padding:8px 12px;border:1px solid var(--border-color-medium,#d1d5db);border-radius:6px;background:var(--background-color-low,#f5f5f5);font-size:13px;cursor:default}.app-item img,.app-icon-letter{width:20px;height:20px;border-radius:4px;flex-shrink:0}.app-item img{object-fit:contain}.app-icon-letter{display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#fff}.app-item .app-name{color:var(--text-color-medium,#6b7280);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.custom-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}.custom-title{font-size:17px;font-weight:600;color:var(--text-color-high,#333)}.custom-empty{text-align:center;padding:34px 12px;color:var(--text-color-medium,#6b7280);border:1px dashed var(--border-color-medium,#d1d5db);border-radius:6px}.custom-table-wrap{overflow-x:auto}.custom-table th:last-child,.custom-table td:last-child{text-align:right;width:110px}.custom-actions{display:flex;justify-content:flex-end;gap:6px}.icon-action{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:1px solid var(--border-color-medium,#d1d5db);border-radius:5px;background:transparent;color:var(--text-color-medium,#6b7280);cursor:pointer}.icon-action:hover{background:var(--background-color-low,#f5f5f5);color:var(--text-color-high,#333)}.icon-action.delete:hover{color:#ef4444;border-color:#ef4444}.icon-action svg{width:17px;height:17px}.custom-card-list{display:none}.custom-editor-mask{position:fixed;inset:0;z-index:1100;background:rgba(0,0,0,.55);align-items:center;justify-content:center;padding:16px}.custom-editor-dialog{display:flex;flex-direction:column;width:min(820px,100%);max-height:calc(100vh - 32px);background:var(--background-coloLine truncated
|
||||
.online-panel{padding:16px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:8px;background:var(--background-color-high,#fff)}.online-token-row{display:grid;grid-template-columns:140px minmax(240px,1fr) auto;align-items:center;gap:12px;margin-top:16px}.online-token-row>label{font-weight:600}.online-token-input,.online-actions{display:flex;align-items:center;gap:8px}.online-token-input{position:relative}.online-token-input input{min-width:0;flex:1;padding-right:38px}.online-token-eye{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:0;background:transparent;color:var(--text-color-medium,#6b7280);cursor:pointer}.online-token-eye:hover{color:var(--text-color-high,#333)}.online-token-eye svg{width:18px;height:18px}.online-device-info{display:flex;flex-wrap:wrap;gap:10px 24px;margin:14px 0;color:var(--text-color-medium,#6b7280);font-size:13px}.online-device-info strong{color:var(--text-color-high,#333);font-weight:500}.online-announcement{display:flex;align-items:flex-start;gap:8px;margin:10px 0 12px;padding:9px 12px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:6px;background:var(--background-color-low,#f5f5f5);color:var(--text-color-high,#333);font-size:13px;line-height:1.5}.announcement-icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-top:1px;color:#f59e0b;flex-shrink:0}.announcement-icon svg{width:18px;height:18px}.online-list-wrap{overflow-x:auto;margin-top:14px}.online-table th:last-child,.online-table td:last-child{text-align:left;width:106px}.online-table tr.current-version td,.online-card.current-version{background:var(--background-color-low,#f5f5f5)}.current-version-label{color:#22c55e;font-weight:600}.online-use-version:disabled{opacity:.55;cursor:not-allowed}.online-card-list{display:none}.online-card{padding:12px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:6px;background:var(--background-color-low,#f5f5f5);margin-bottom:10px}.online-card-title{font-weight:600;overflow-wrap:anywhere}.online-card-meta{margin-top:6px;color:var(--text-color-medium,#6b7280);font-size:13px}.online-card-action{margin-top:10px;text-align:left}
|
||||
.online-control-stack{display:flex;flex-direction:column;gap:12px;margin-top:16px}.online-control-row{display:grid;grid-template-columns:140px minmax(240px,1fr) auto;align-items:center;gap:12px}.online-control-row>label{font-weight:600}.online-lang-select{width:180px;max-width:100%}.online-confirm-mask{position:fixed;inset:0;z-index:1200;display:flex;align-items:center;justify-content:center;padding:16px;background:rgba(0,0,0,.45)}.online-confirm-dialog{width:min(420px,calc(100vw - 32px));min-height:150px;box-sizing:border-box;padding:22px 24px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:8px;background:var(--background-color-high,#fff);color:var(--text-color-high,#333);box-shadow:0 12px 30px rgba(0,0,0,.28)}.online-confirm-message{min-height:56px;line-height:1.6;text-align:center;font-size:14px;color:var(--text-color-high,#333)}.online-confirm-actions{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:18px}.online-confirm-actions .cbi-button{min-width:68px;height:30px;padding:0 14px;font-size:13px;line-height:28px;border-radius:4px}.online-confirm-cancel{background:var(--background-color-low,#f5f5f5)!important;color:var(--text-color-high,#333)!important;border:1px solid var(--border-color-medium,#d1d5db)!important}.online-confirm-ok{background:#2885e8!important;color:#fff!important;border:1px solid #2885e8!important}
|
||||
.common-modal-content{width:auto!important;min-width:150px!important;height:auto!important;min-height:86px!important;padding:18px 24px!important;box-sizing:border-box;font-size:14px;line-height:1.5}
|
||||
.online-progress-mask{position:fixed;inset:0;z-index:1210;display:none;align-items:center;justify-content:center;padding:16px;background:rgba(0,0,0,.45)}.online-progress-mask.show{display:flex}.online-progress-modal{width:320px;max-width:90vw;min-height:120px;border-radius:10px;padding:18px 16px;background:var(--background-color-high,#fff);color:var(--text-color-high,#333);display:flex;align-items:center;justify-content:center;flex-direction:column;text-align:center;box-sizing:border-box;gap:12px;box-shadow:0 12px 30px rgba(0,0,0,.28);border:1px solid var(--border-color-low,#e5e7eb)}.online-progress-spinner{width:30px;height:30px;border:3px solid rgba(25,118,210,.2);border-top-color:var(--primary-color-high,#1976d2);border-radius:50%;animation:online-progress-spin .8s linear infinite}.online-progress-text{font-size:13px;line-height:1.5;color:var(--text-color-high,#374151)}[data-darkmode="true"] .online-progress-modal{background:#2d2d2d;color:#f5f5f5}[data-darkmode="true"] .online-progress-spinner{border-color:rgba(59,130,246,.2);border-top-color:#3b82f6}[data-darkmode="true"] .online-progress-text{color:rgba(255,255,255,.88)}@keyframes online-progress-spin{to{transform:rotate(360deg)}}
|
||||
.online-premium-text,.online-premium-cell,.online-card.online-premium-row .online-card-title,.online-card.online-premium-row .online-card-meta{color:#2885e8}[data-darkmode="true"] .online-premium-text,[data-darkmode="true"] .online-premium-cell,[data-darkmode="true"] .online-card.online-premium-row .online-card-title,[data-darkmode="true"] .online-card.online-premium-row .online-card-meta{color:#58a6ff}
|
||||
.online-fetch-button{background:#2885e8!important;border-color:#2885e8!important;color:#fff!important}.online-fetch-button:hover{background:#1d74d8!important;border-color:#1d74d8!important;color:#fff!important}.online-fetch-button:disabled{opacity:.6;color:#fff!important}
|
||||
.feature-app-panel{padding:16px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:8px;background:var(--background-color-high,#fff);margin-top:12px}.class-header{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;box-sizing:border-box;background:transparent;border:0;cursor:pointer;text-align:left}.class-title{font-weight:600;color:var(--text-color-high,#333);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.class-count{display:inline-flex;align-items:center;justify-content:center;min-width:32px;height:22px;padding:0 8px;border-radius:999px;background:var(--background-color-low,#f5f5f5);color:var(--text-color-medium,#6b7280);font-size:12px}.class-chevron{width:8px;height:8px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;color:var(--text-color-medium,#6b7280);flex-shrink:0}.class-card.collapsed .class-chevron{transform:rotate(-45deg)}.class-card.collapsed .app-items{display:none}
|
||||
.feature-app-panel .class-card{margin-bottom:5px;padding:10px;border:1px solid var(--border-color-low,#e5e7eb);border-radius:6px;background:var(--background-color-low,#f5f5f5)}.feature-app-panel .class-header{padding:8px;border:0!important;border-radius:4px;background:var(--background-color-medium,#eee);margin:0;color:var(--text-color-high,#333)}.feature-app-panel .class-title{font-size:14px}.feature-app-panel .app-items{display:flex;flex-wrap:wrap;gap:15px;padding:10px 8px 4px}.feature-app-panel .app-item{width:calc(20% - 12px);min-width:120px;max-width:180px;padding:5px 0;border:0;background:transparent;box-sizing:border-box}.feature-app-panel .app-name{color:var(--text-color-medium,#6b7280)}
|
||||
@media(min-width:800px){.app-items{grid-template-columns:repeat(6,1fr)}}@media(min-width:1200px){.app-items{grid-template-columns:repeat(8,1fr)}}
|
||||
@media(max-width:640px){.feature-container{padding:4px}.feature-info-card,.custom-panel,.online-panel{padding:10px}.feature-inputs{align-items:stretch;flex-direction:column}.feature-btn{width:100%}.custom-table,.online-table{display:none}.custom-card-list,.online-card-list{display:block}.custom-toolbar{align-items:stretch;flex-direction:column}.custom-toolbar .cbi-button{width:100%}.editor-basic-grid,.feature-field-grid{grid-template-columns:1fr}.custom-editor-mask{padding:8px}.custom-editor-dialog{max-height:calc(100vh - 16px)}.custom-editor-footer .cbi-button{flex:1}.online-token-row{grid-template-columns:1fr}.online-actions{align-items:stretch;flex-direction:column}.online-actions .cbi-button{width:100%}.online-token-input{width:100%}}
|
||||
@media(max-width:640px){.online-control-row{grid-template-columns:1fr}.online-lang-select{width:100%}}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function($){
|
||||
var apiClassList='<%=url("admin/services/oaf/feature/class_list")%>';
|
||||
var apiInfo='<%=url("admin/services/oaf/feature/info")%>';
|
||||
var apiOnlineConfig='<%=url("admin/services/oaf/feature/online_config")%>';
|
||||
var apiOnlineSave='<%=url("admin/services/oaf/feature/online_save")%>';
|
||||
var apiOnlineList='<%=url("admin/services/oaf/feature/online_list")%>';
|
||||
var apiOnlineStart='<%=url("admin/services/oaf/feature/online_start")%>';
|
||||
var apiOnlineStatus='<%=url("admin/services/oaf/feature/online_status")%>';
|
||||
var apiCustomList='<%=url("admin/services/oaf/feature/custom_list")%>';
|
||||
var apiCustomClasses='<%=url("admin/services/oaf/feature/custom_class_list")%>';
|
||||
var apiCustomSave='<%=url("admin/services/oaf/feature/custom_save")%>';
|
||||
var txt={loadFailed:'<%:Load failed%>',saveFailed:'<%:Save failed%>',saved:'<%:Saved successfully%>',loading:'<%:Loading...%>',noData:'<%:No data%>',noApps:'<%:No apps%>',unknown:'<%:Unknown%>',add:'<%:Add Application%>',edit:'<%:Edit Application%>',feature:'<%:Feature%>',position:'<%:Position%>',value:'<%:Value%>',deleteConfirm:'<%:Are you sure you want to delete this application?%>',invalidName:'<%:Invalid application name%>',invalidClass:'<%:Please select a category%>',invalidPort:'<%:Destination port must be a single port or port range between 1 and 65535%>',invalidDomain:'<%:Invalid domain%>',invalidUri:'<%:Invalid URI%>',invalidPayload:'<%:Invalid payload match format%>',invalidPosition:'<%:Payload position must be a signed integer%>',emptyFeature:'<%:At least one match field must be configured for each feature%>',maxApps:'<%:A maximum of 64 custom applications is allowed%>',maxFeatures:'<%:A maximum of 16 features is allowed%>',maxPayloads:'<%:A maximum of 15 payload matches is allowed%>'};
|
||||
var onlineTxt={useVersion:'<%:Switch%>',confirm:'<%:Use this feature library version now?%>',confirmAction:'<%:Confirm%>',cancel:'<%:Cancel%>',success:'<%:Feature library updated successfully%>',failed:'<%:Feature library update failed%>',timeout:'<%:Feature library update timeout%>',invalidToken:'<%:Invalid subscription key%>',vipRequired:'<%:A valid subscription is required%>',rateLimited:'<%:Too many requests. Please try again later.%>',deviceLimited:'<%:The subscription device limit has been reached%>',serviceUnavailable:'<%:Subscription service is unavailable%>',formatError:'<%:Feature library format error. Please use the feature library for this version.%>',fileTooLarge:'<%:The downloaded file is too large%>',checksumError:'<%:Feature library checksum verification failed%>',updating:'<%:Updating, please wait...%>',updatingList:'<%:Updating list, please wait...%>',tokenSaved:'<%:Saved successfully%>',updated:'<%:Updated successfully%>',switchSuccess:'<%:Switched successfully%>',show:'<%:Show%>',hide:'<%:Hide%>',downloading:'<%:Downloading%>',extracting:'<%:Extracting%>',validating:'<%:Validating%>',applying:'<%:Applying%>',lite:'<%:Lite%>',plus:'<%:Plus%>',applicationCount:'<%:Application Count%>',chinese:'<%:Chinese%>',english:'<%:English%>',currentVersion:'<%:Current Version%>',updateListHint:'<%:Click Update List to get the latest feature libraries%>',freeVersion:'<%:Free%>',premiumVersion:'<%:Premium%>',keyRequired:'<%:Please enter a valid Key. You can apply for a Key on the official website.%>',keyRequiredVersion:'<%:Enter the Key to use this feature library version%>',invalidKeyPermission:'<%:Invalid Key. Please get the correct Key from the official website and enable the advanced feature library permission.%>'};
|
||||
var customApps=[];
|
||||
var customClasses=[];
|
||||
var customLoaded=false;
|
||||
var editingIndex=-1;
|
||||
var originalClassId=0;
|
||||
var iconStatusCache={};
|
||||
var onlineLoaded=false;
|
||||
var onlinePolling=false;
|
||||
var onlinePollStartedAt=0;
|
||||
var onlinePollTimeout=180000;
|
||||
var currentVersionNumber=$.trim($('#online-current-version').text()||$('#info-version').text()||'');
|
||||
var currentFeatureType=0;
|
||||
var currentFeatureFree=0;
|
||||
var currentOnlineFiles=[];
|
||||
|
||||
function clone(value){return JSON.parse(JSON.stringify(value));}
|
||||
function utf8Length(value){return unescape(encodeURIComponent(value || '')).length;}
|
||||
function showMessage(message,ok){if(window.showCommonModal){showCommonModal(message,2500,ok?'success':'error');}}
|
||||
function showOnlineToast(message,ok){if(window.showCommonModal)showCommonModal(message,2000,ok?'success':'error');else{setOnlineStatus(message,ok);setTimeout(function(){setOnlineStatus('',true);},2000);}}
|
||||
function showOnlineProgress(message){var mask=$('.online-progress-mask');if(!mask.length){mask=$('<div>').addClass('online-progress-mask');var modal=$('<div>').addClass('online-progress-modal');modal.append($('<div>').addClass('online-progress-spinner'),$('<div>').addClass('online-progress-text'));mask.append(modal).appendTo(document.body);}mask.find('.online-progress-text').text(message||txt.loading);mask.addClass('show');}
|
||||
function updateOnlineProgress(message){var mask=$('.online-progress-mask');if(mask.length)mask.find('.online-progress-text').text(message||txt.loading);else showOnlineProgress(message);}
|
||||
function closeOnlineProgress(){$('.online-progress-mask').removeClass('show').remove();}
|
||||
function browserRawLanguage(){var langs=navigator.languages&&navigator.languages.length?navigator.languages:[navigator.language||navigator.userLanguage||''];return String(langs[0]||'');}
|
||||
function browserDefaultOnlineLang(){var lang=browserRawLanguage().toLowerCase();if(lang.indexOf('zh')===0)return 'cn';if(lang.indexOf('en')===0)return 'en';return 'en';}
|
||||
function showOnlineSwitchConfirm(onConfirm){$('.online-confirm-mask').remove();var mask=$('<div>').addClass('online-confirm-mask');var dialog=$('<div>').addClass('online-confirm-dialog');var message=$('<div>').addClass('online-confirm-message').text(onlineTxt.confirm);var actions=$('<div>').addClass('online-confirm-actions');var cancel=$('<button>').attr('type','button').addClass('cbi-button online-confirm-cancel').text(onlineTxt.cancel);var ok=$('<button>').attr('type','button').addClass('cbi-button cbi-button-apply online-confirm-ok').text(onlineTxt.confirmAction);cancel.on('click',function(){mask.remove();});ok.on('click',function(){mask.remove();if(onConfirm)onConfirm();});actions.append(cancel,ok);dialog.append(message,actions);mask.append(dialog).appendTo(document.body);}
|
||||
function setEditorError(message){$('#custom-editor-error').text(message||'').toggle(!!message);}
|
||||
function className(classId){for(var i=0;i<customClasses.length;i++){if(Number(customClasses[i].class_id)===Number(classId))return customClasses[i].class_name;}return '--';}
|
||||
function appLetterColor(name){return window.OAFIcon?window.OAFIcon.hashColor(name):'#3b82f6';}
|
||||
function editIcon(){return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L8 18l-4 1 1-4Z"/></svg>';}
|
||||
function deleteIcon(){return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v5M14 11v5"/></svg>';}
|
||||
function eyeIcon(visible){return visible?'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3l18 18"/><path d="M10.6 10.6a2 2 0 0 0 2.8 2.8"/><path d="M9.9 4.2A10.7 10.7 0 0 1 12 4c5 0 9 5 10 8a14.5 14.5 0 0 1-3.1 4.8"/><path d="M6.6 6.6A14.5 14.5 0 0 0 2 12c1 3 5 8 10 8 1.5 0 2.9-.4 4.1-1.1"/></svg>':'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>';}
|
||||
|
||||
function onlineError(data){var code=Number(data&&data.status_code)||400;var message=data&&data.message?String(data.message):'';if(code===40000||code===40001||code===40010)return onlineTxt.invalidKeyPermission;if(code===40031)return onlineTxt.deviceLimited;if(code===40029)return onlineTxt.rateLimited;if(code===401)return onlineTxt.formatError;if(code===402)return onlineTxt.fileTooLarge;if(code===403)return onlineTxt.checksumError;return message?message:onlineTxt.failed;}
|
||||
function setOnlineStatus(message,ok){var box=$('#online-status');box.text(message||'').toggle(!!message).removeClass('success error');if(message&&ok===true)box.addClass('success');else if(message&&ok===false)box.addClass('error');}
|
||||
function setOnlineBusy(busy){$('#online-save-token,#online-fetch-list,.online-use-version,#online-lang').prop('disabled',!!busy);}
|
||||
function formatOnlineSize(bytes){var value=Number(bytes)||0;var units=['B','KB','MB','GB'];var index=0;while(value>=1024&&index<units.length-1){value=value/1024;index++;}if(index===0)return Math.floor(value)+units[index];return (Math.floor(value*10)/10).toFixed(1).replace(/\.0$/,'')+units[index];}
|
||||
function onlineProgressMessage(data,stageText){var now=Number(data&&data.download_now)||0;var total=Number(data&&data.download_total)||0;if(data&&data.stage==='downloading'&&(now>0||total>0)){var text=now>0?formatOnlineSize(now):'0B';if(total>0){var percent=Math.min(100,Math.max(0,Math.floor(now*100/total)));text+=' / '+formatOnlineSize(total)+' ('+percent+'%)';}return onlineTxt.updating+' ('+stageText+' '+text+')';}return onlineTxt.updating+' ('+stageText+')';}
|
||||
function featureVersionDisplay(version,type){var text=String(version||'').trim();return text?'v'+text.replace(/^[vV]/,'')+'('+onlineTypeLabel(type)+')':'--';}
|
||||
function refreshFeatureInfo(done){$.get(apiInfo,function(resp){var data=resp&&resp.code===2000?resp.data:null;if(!data)return;currentVersionNumber=data.version||'';currentFeatureType=data.type;currentFeatureFree=data.free;$('#info-version').text(featureVersionDisplay(data.version,data.type));$('#info-free').text(onlineFreeLabel(data.free));$('#online-current-version').text(featureVersionDisplay(data.version,data.type));$('#online-current-free').text(onlineFreeLabel(data.free));$('#info-format').text(data.format||'--');$('#info-appnum').text(data.app_count===undefined?'--':data.app_count);}).always(function(){if(done)done();});}
|
||||
function loadOnlineConfig(){setOnlineStatus(txt.loading);$.get(apiOnlineConfig,function(resp){if(!resp||resp.code!==2000||!resp.data){setOnlineStatus(onlineError(resp&&resp.data),false);return;}$('#online-token').val(resp.data.token||'');$('#online-version').text(resp.data.version||'v4.0');$('#online-current-version').text(featureVersionDisplay(currentVersionNumber,currentFeatureType));$('#online-current-free').text(onlineFreeLabel(currentFeatureFree));onlineLoaded=true;setOnlineStatus('',true);loadOnlineList(false);}).fail(function(){setOnlineStatus(txt.loadFailed,false);});}
|
||||
function saveOnlineToken(){var token=$.trim($('#online-token').val()||'');$('#online-save-token').prop('disabled',true);$.post(apiOnlineSave,{token:token},function(resp){if(resp&&resp.code===2000){setOnlineStatus('',true);showOnlineToast(onlineTxt.tokenSaved,true);}else{setOnlineStatus(onlineError(resp&&resp.data),false);}},'json').fail(function(){setOnlineStatus(txt.saveFailed,false);}).always(function(){$('#online-save-token').prop('disabled',false);});}
|
||||
function normalizeFeatureVersion(version){return String(version||'').trim().replace(/^[vV]/,'').toLowerCase();}
|
||||
function onlineHasKey(){return !!$.trim($('#online-token').val()||'');}
|
||||
function updateOnlineUseButtons(){var hasKey=onlineHasKey();$('.online-use-version').each(function(){var btn=$(this);var current=String(btn.attr('data-current')||'0')==='1';var premium=String(btn.attr('data-free')||'0')!=='1';var blocked=premium&&!hasKey;btn.prop('disabled',current||blocked).toggleClass('disabled',current||blocked).attr('title',blocked?onlineTxt.keyRequiredVersion:'');});}
|
||||
function onlineUseButton(id,disabled,lang,md5,free){var blocked=Number(free)!==1&&!onlineHasKey();return $('<button>').attr({type:'button','data-id':id,'data-lang':lang||'cn','data-md5':md5||'','data-free':Number(free)===1?1:0,'data-current':disabled?1:0,title:blocked?onlineTxt.keyRequiredVersion:''}).prop('disabled',!!disabled||blocked).toggleClass('disabled',!!disabled||blocked).addClass('cbi-button cbi-button-apply online-use-version').text(onlineTxt.useVersion);}
|
||||
function onlineTypeLabel(type){return Number(type)===1?onlineTxt.plus:onlineTxt.lite;}
|
||||
function onlineFreeLabel(value){return Number(value)===1?onlineTxt.freeVersion:onlineTxt.premiumVersion;}
|
||||
function onlineLangLabel(lang){return String(lang||'cn').toLowerCase()==='en'?onlineTxt.english:onlineTxt.chinese;}
|
||||
function isCurrentOnlineVersion(file){var current=normalizeFeatureVersion(currentVersionNumber);var target=normalizeFeatureVersion(file&&file.version);return !!current&&!!target&¤t===target&&Number(currentFeatureType)===Number(file&&file.type)&&Number(currentFeatureFree)===Number(file&&file.free);}
|
||||
function onlineVersionCell(file){var wrap=$('<span>').text(featureVersionDisplay(file&&file.version,file&&file.type));if(isCurrentOnlineVersion(file))wrap.append(document.createTextNode(' - '),$('<span>').addClass('current-version-label').text(onlineTxt.currentVersion));return wrap;}
|
||||
function renderOnlineList(data,refresh){currentOnlineFiles=(data&&data.files?data.files:[]).slice().sort(function(a,b){return (Number(a&&a.free)===1?1:0)-(Number(b&&b.free)===1?1:0);});var body=$('#online-list-body').empty();var cards=$('#online-card-list').empty();var announcement=$.trim(data&&data.announcement?data.announcement:'');if(!currentOnlineFiles.length){$('#online-announcement').hide();$('#online-list-wrap').hide();$('#online-list-empty').text(refresh?txt.noData:onlineTxt.updateListHint).show();return;}if(announcement){$('#online-announcement-text').text(announcement);$('#online-announcement').css('display','flex');}else{$('#online-announcement').hide();}$('#online-list-empty').hide();$('#online-list-wrap').show();currentOnlineFiles.forEach(function(file){var count=Math.max(0,Number(file.count)||0);var current=isCurrentOnlineVersion(file);var isFree=Number(file&&file.free)===1;var freeText=onlineFreeLabel(file.free);var premiumClass=isFree?'':'online-premium-cell';var freeCell=$('<span>').text(freeText).toggleClass('online-premium-text',!isFree);var row=$('<tr>').toggleClass('current-version',current).toggleClass('online-premium-row',!isFree);row.append($('<td>').append(onlineVersionCell(file).addClass(premiumClass)),$('<td>').append(freeCell),$('<td>').append($('<span>').addClass(premiumClass).text(count)),$('<td>').append($('<span>').addClass(premiumClass).text(onlineLangLabel(file.lang))),$('<td>').append($('<span>').addClass(premiumClass).text(file.date||'')),$('<td>').append(onlineUseButton(file.id,current,file.lang,file.md5,file.free)));body.append(row);var card=$('<div>').addClass('online-card').toggleClass('current-version',current).toggleClass('online-premium-row',!isFree);card.append($('<div>').addClass('online-card-title').append(onlineVersionCell(file)),$('<div>').addClass('online-card-meta').append($('<span>').text(freeText).toggleClass('online-premium-text',!isFree),document.createTextNode(' / '+onlineTxt.applicationCount+': '+count+' / '+onlineLangLabel(file.lang))),$('<div>').addClass('online-card-meta').text(file.date||''),$('<div>').addClass('online-card-action').append(onlineUseButton(file.id,current,file.lang,file.md5,file.free)));cards.append(card);});}
|
||||
function loadOnlineList(refresh){if(refresh)showOnlineProgress(onlineTxt.updatingList);else setOnlineStatus(txt.loading);$('#online-fetch-list').prop('disabled',true);$.get(apiOnlineList,{lang:$('#online-lang').val()||'cn',device_lang:browserRawLanguage(),refresh:refresh?1:0},function(resp){if(resp&&resp.code===2000&&resp.data){renderOnlineList(resp.data,refresh);setOnlineStatus('',true);if(refresh){closeOnlineProgress();showOnlineToast(onlineTxt.updated,true);}}else{if(refresh)closeOnlineProgress();setOnlineStatus(onlineError(resp&&resp.data),false);}}).fail(function(){if(refresh)closeOnlineProgress();setOnlineStatus(txt.loadFailed,false);}).always(function(){$('#online-fetch-list').prop('disabled',false);});}
|
||||
function fetchOnlineList(){loadOnlineList(true);}
|
||||
function handleOnlineStatus(data){var state=data&&data.state?data.state:'idle';var stages={downloading:onlineTxt.downloading,extracting:onlineTxt.extracting,validating:onlineTxt.validating,applying:onlineTxt.applying};var stageText=stages[data&&data.stage]||data&&data.stage||'';if(state==='running'){if(!onlinePollStartedAt)onlinePollStartedAt=Date.now();setOnlineBusy(true);updateOnlineProgress(onlineProgressMessage(data,stageText));return true;}setOnlineBusy(false);if(!onlinePollStartedAt)return false;closeOnlineProgress();if(state==='success'){setOnlineStatus('',true);showOnlineToast(onlineTxt.switchSuccess,true);setTimeout(function(){refreshFeatureInfo(function(){loadOnlineList(false);});},2000);}else if(state==='failed'){setOnlineStatus(onlineError(data),false);}return false;}
|
||||
function pollOnlineStatus(){if(onlinePolling)return;onlinePolling=true;var request=function(){if(onlinePollStartedAt&&Date.now()-onlinePollStartedAt>onlinePollTimeout){onlinePolling=false;onlinePollStartedAt=0;setOnlineBusy(false);closeOnlineProgress();setOnlineStatus(onlineTxt.timeout,false);return;}$.ajax({url:apiOnlineStatus,dataType:'json',timeout:5000}).done(function(resp){var running=resp&&resp.code===2000&&handleOnlineStatus(resp.data||{});if(running)setTimeout(request,1000);else{onlinePolling=false;onlinePollStartedAt=0;}}).fail(function(){onlinePolling=false;onlinePollStartedAt=0;setOnlineBusy(false);closeOnlineProgress();setOnlineStatus(txt.loadFailed,false);});};request();}
|
||||
function startOnlineUpdate(id,lang,md5){var run=function(){setOnlineBusy(true);onlinePollStartedAt=Date.now();setOnlineStatus('',true);showOnlineProgress(onlineTxt.updating);$.ajax({url:apiOnlineStart,type:'POST',data:{id:id,lang:lang||'cn',md5:md5||''},dataType:'json',timeout:15000}).done(function(resp){if(resp&&resp.code===2000){onlinePolling=false;pollOnlineStatus();}else{onlinePollStartedAt=0;setOnlineBusy(false);closeOnlineProgress();setOnlineStatus(onlineError(resp&&resp.data),false);}}).fail(function(){onlinePollStartedAt=0;setOnlineBusy(false);closeOnlineProgress();setOnlineStatus(onlineTxt.failed,false);});};showOnlineSwitchConfirm(run);}
|
||||
|
||||
function loadClassList(){$.get(apiClassList,function(data){renderClassList(data&&data.class_list?data.class_list:[]);}).fail(function(){$('#app-list').empty().append($('<div>').addClass('feature-hint').text(txt.loadFailed));});}
|
||||
function renderClassList(list){var container=$('#app-list').empty();if(!list.length){container.append($('<div>').addClass('feature-hint').text(txt.noData));return;}list.forEach(function(category,index){var collapsed=index!==0;var card=$('<div>').addClass('class-card').toggleClass('collapsed',collapsed);var appList=category.app_list||[];var header=$('<button>').attr({type:'button','aria-expanded':collapsed?'false':'true','data-index':index}).addClass('class-header');header.append($('<span>').addClass('class-title').text((category.name||txt.unknown)+'('+appList.length+')'),$('<span>').addClass('class-chevron').attr('aria-hidden','true'));var apps=$('<div>').addClass('app-items');if(!appList.length){apps.append($('<div>').addClass('feature-hint').text(txt.noApps));}else{appList.forEach(function(raw){var parts=raw.split(',');var appId=parts[0]||'';var appName=parts[1]||txt.unknown;var hasIcon=parts[2]!=='0';var item=$('<div>').addClass('app-item');var icon=$('<span>').addClass('app-icon-letter').css('background',appLetterColor(appName)).text(appName.charAt(0).toUpperCase());var src='<%=resource%>/oaf/app_icons/'+encodeURIComponent(appId)+'.png';if(hasIcon&&appId&&iconStatusCache[appId]!=='failed'){var loader=new Image();loader.onload=function(){iconStatusCache[appId]='loaded';icon.replaceWith($('<img>').attr({src:src,alt:appName}));};loader.onerror=function(){iconStatusCache[appId]='failed';};loader.src=src;}item.append(icon).append($('<span>').addClass('app-name').text(appName));apps.append(item);});}card.append(header,apps);container.append(card);});}
|
||||
|
||||
function loadCustomData(){customLoaded=false;$('#custom-status').text(txt.loading).show();$('#custom-empty,#custom-table-wrap').hide();$.when($.get(apiCustomClasses),$.get(apiCustomList)).done(function(classResult,appResult){var classResp=classResult[0]||{};var appResp=appResult[0]||{};if(classResp.code!==2000||appResp.code!==2000){renderCustomLoadError();return;}customClasses=classResp.data&&classResp.data.class_list?classResp.data.class_list:[];customApps=appResp.data&&appResp.data.app_list?appResp.data.app_list:[];customLoaded=true;renderCustomList();}).fail(renderCustomLoadError);}
|
||||
function renderCustomLoadError(){$('#custom-status').text(txt.loadFailed).show();$('#custom-empty,#custom-table-wrap').hide();}
|
||||
function actionButtons(index){var wrap=$('<div>').addClass('custom-actions');wrap.append($('<button>').attr({type:'button',title:'<%:Edit%>','data-index':index}).addClass('icon-action custom-edit').html(editIcon()));wrap.append($('<button>').attr({type:'button',title:'<%:Delete%>','data-index':index}).addClass('icon-action delete custom-delete').html(deleteIcon()));return wrap;}
|
||||
function renderCustomList(){var body=$('#custom-table-body').empty();var cards=$('#custom-card-list').empty();$('#custom-status').hide();$('#custom-add').prop('disabled',customApps.length>=64);if(!customApps.length){$('#custom-empty').show();$('#custom-table-wrap').hide();return;}$('#custom-empty').hide();$('#custom-table-wrap').show();customApps.forEach(function(app,index){var row=$('<tr>');row.append($('<td>').text(app.name||''),$('<td>').text(className(app.class_id)),$('<td>').text((app.features||[]).length),$('<td>').append(actionButtons(index)));body.append(row);var card=$('<div>').addClass('custom-card');var head=$('<div>').addClass('custom-card-head').append($('<span>').text(app.name||''),actionButtons(index));card.append(head,$('<div>').addClass('custom-card-meta').text(className(app.class_id)+' · '+(app.features||[]).length+' <%:Features%>'));cards.append(card);});}
|
||||
|
||||
function emptyFeature(){return {protocol:'tcp',dest_port:'',domain:'',uri:'',payload:[]};}
|
||||
function populateClassSelect(selected){var select=$('#custom-class').empty();customClasses.forEach(function(item){select.append($('<option>').val(item.class_id).text(item.class_name));});if(selected)select.val(String(selected));}
|
||||
function editorField(label,input,help){var field=$('<div>').addClass('editor-field').append($('<label>').text(label),input);if(help)field.append($('<span>').addClass('field-help').text(help));return field;}
|
||||
function validDestPort(value){var match=/^(\d{1,5})(?:-(\d{1,5}))?$/.exec(value);if(!match)return false;var start=Number(match[1]);var end=match[2]!==undefined?Number(match[2]):start;return start>=1&&start<=65535&&end>=start&&end<=65535;}
|
||||
function payloadToText(payload){return (payload||[]).map(function(item){var position=Number(item.position);var positionText=position>=0&&position<10?'0'+position:String(position);return positionText+':'+String(item.value||'').toLowerCase();}).join(',');}
|
||||
function parsePayloadText(text){if(!text)return[];var items=text.split(',');if(items.length>15)throw txt.maxPayloads;return items.map(function(item){var match=/^(-?\d+):([0-9a-f]{2})$/i.exec($.trim(item));if(!match)throw txt.invalidPayload;var position=Number(match[1]);if(position<-2147483648||position>2147483647)throw txt.invalidPosition;return {position:position,value:match[2].toLowerCase()};});}
|
||||
function addFeatureEditor(feature){var container=$('#feature-editors');if(container.children('.feature-editor').length>=16){setEditorError(txt.maxFeatures);return;}feature=feature||emptyFeature();var editor=$('<div>').addClass('feature-editor');var header=$('<div>').addClass('feature-editor-header').append($('<span>').addClass('feature-title').text(txt.feature),$('<button>').attr({type:'button',title:'<%:Remove%>'}).addClass('feature-remove').html('×'));var body=$('<div>').addClass('feature-editor-body');var generalHelp=$('<div>').addClass('field-help feature-general-help').text('<%:Leave blank for no restriction%>');var grid=$('<div>').addClass('feature-field-grid');var protocol=$('<select>').addClass('feature-protocol').append($('<option>').val('tcp').text('TCP'),$('<option>').val('udp').text('UDP')).val(feature.protocol||'tcp');grid.append(editorField('<%:Protocol%> *',protocol,'<%:Select TCP or UDP.%>'),editorField('<%:Destination Port%>',$('<input>').attr({type:'text',maxlength:'11'}).addClass('feature-port').val(feature.dest_port||''),'<%:Supports a single destination port or port range, for example 80 or 80-443.%>'),editorField('<%:Domain%>',$('<input>').attr({type:'text',maxlength:'31'}).addClass('feature-domain').val(feature.domain||'')),editorField('URI',$('<input>').attr({type:'text',maxlength:'63'}).addClass('feature-uri').val(feature.uri||''),'<%:The HTTP request URI. HTTPS does not support URI matching.%>'));
|
||||
var payloadInput=$('<input>').attr({type:'text',maxlength:'255'}).addClass('feature-payload').val(payloadToText(feature.payload));var payloadField=editorField('<%:Payload Match%>',payloadInput,'<%: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.%>');payloadField.css('margin-top','12px');body.append(generalHelp,grid,payloadField);editor.append(header,body);container.append(editor);}
|
||||
function openEditor(index){if(!customLoaded||!customClasses.length){showMessage(txt.loadFailed,false);return;}editingIndex=typeof index==='number'?index:-1;if(editingIndex<0&&customApps.length>=64){showMessage(txt.maxApps,false);return;}var app=editingIndex>=0?clone(customApps[editingIndex]):{appid:0,class_id:customClasses[0].class_id,name:'',features:[emptyFeature()]};originalClassId=Number(app.class_id)||0;$('#custom-editor-title').text(editingIndex>=0?txt.edit:txt.add);$('#custom-name').val(app.name||'');populateClassSelect(app.class_id);$('#feature-editors').empty();(app.features&&app.features.length?app.features:[emptyFeature()]).forEach(addFeatureEditor);setEditorError('');$('#custom-editor').css('display','flex');}
|
||||
function closeEditor(){$('#custom-editor').hide();setEditorError('');}
|
||||
|
||||
function collectEditor(){var name=$.trim($('#custom-name').val()||'');var classId=Number($('#custom-class').val());if(!name||utf8Length(name)>63||/[~\:\[\];,]/.test(name))throw txt.invalidName;if(!classId)throw txt.invalidClass;var features=[];$('#feature-editors .feature-editor').each(function(){var editor=$(this);var port=$.trim(editor.find('.feature-port').val()||'');var domain=$.trim(editor.find('.feature-domain').val()||'');var uri=$.trim(editor.find('.feature-uri').val()||'');var payload=parsePayloadText($.trim(editor.find('.feature-payload').val()||''));if(port&&!validDestPort(port))throw txt.invalidPort;if(utf8Length(domain)>31||/[\s;,\[\]]/.test(domain))throw txt.invalidDomain;if(utf8Length(uri)>63||/[\s;,\[\]]/.test(uri))throw txt.invalidUri;if(!port&&!domain&&!uri&&!payload.length)throw txt.emptyFeature;features.push({protocol:editor.find('.feature-protocol').val(),dest_port:port,domain:domain,uri:uri,payload:payload});});if(!features.length)throw txt.emptyFeature;var old=editingIndex>=0?customApps[editingIndex]:null;return {appid:old&&Number(old.class_id)===classId?old.appid:0,class_id:classId,name:name,features:features};}
|
||||
function saveAppList(list,onSuccess,onFailure){$('#custom-save').prop('disabled',true);$.ajax({url:apiCustomSave,type:'POST',dataType:'json',data:{data:JSON.stringify({app_list:list})}}).done(function(resp){if(resp&&resp.code===2000&&resp.data&&resp.data.app_list){customApps=resp.data.app_list;renderCustomList();if(onSuccess)onSuccess();showMessage(txt.saved,true);}else{var error=resp&&resp.data&&resp.data.error?resp.data.error:txt.saveFailed;if(onFailure)onFailure(error);else showMessage(error,false);}}).fail(function(){if(onFailure)onFailure(txt.saveFailed);else showMessage(txt.saveFailed,false);}).always(function(){$('#custom-save').prop('disabled',false);});}
|
||||
function saveEditor(){setEditorError('');if(editingIndex<0&&customApps.length>=64){setEditorError(txt.maxApps);return;}var app;try{app=collectEditor();}catch(error){setEditorError(String(error));return;}var next=clone(customApps);if(editingIndex>=0)next[editingIndex]=app;else next.push(app);saveAppList(next,function(){closeEditor();},setEditorError);}
|
||||
function deleteApplication(index){var remove=function(){var next=clone(customApps);next.splice(index,1);saveAppList(next);};if(window.showConfirmModal)showConfirmModal(txt.deleteConfirm,remove,null,'<%:Delete%>','<%:Cancel%>');else if(window.confirm(txt.deleteConfirm))remove();}
|
||||
|
||||
$(document).on('click','.tab-item',function(){var tabId=$(this).data('tab');$('.tab-item').removeClass('active');$(this).addClass('active');$('.tab-body').removeClass('active');$('#'+tabId).addClass('active');if(tabId==='tab-basic'){refreshFeatureInfo();loadClassList();}else if(tabId==='tab-online'){refreshFeatureInfo(function(){loadOnlineConfig();});pollOnlineStatus();}else if(tabId==='tab-custom'){loadCustomData();}});
|
||||
$(document).on('click','.class-header',function(){var card=$(this).closest('.class-card');var collapsed=!card.hasClass('collapsed');card.toggleClass('collapsed',collapsed);$(this).attr('aria-expanded',collapsed?'false':'true');});
|
||||
$('#online-token-toggle').html(eyeIcon(false));
|
||||
$(document).on('click','#online-token-toggle',function(){var input=$('#online-token');var show=input.attr('type')==='password';input.attr('type',show?'text':'password');$(this).html(eyeIcon(show)).attr({title:show?onlineTxt.hide:onlineTxt.show,'aria-label':show?onlineTxt.hide:onlineTxt.show});});
|
||||
$(document).on('input','#online-token',updateOnlineUseButtons);
|
||||
$(document).on('click','#online-save-token',saveOnlineToken);
|
||||
$(document).on('click','#online-fetch-list',fetchOnlineList);
|
||||
$(document).on('click','.online-use-version',function(){if($(this).prop('disabled'))return;if(String($(this).attr('data-free')||'0')!=='1'&&!$.trim($('#online-token').val()||'')){showOnlineToast(onlineTxt.keyRequired,false);return;}startOnlineUpdate(String($(this).attr('data-id')||''),String($(this).attr('data-lang')||'cn'),String($(this).attr('data-md5')||''));});
|
||||
$(document).on('click','#custom-add',function(){openEditor();});
|
||||
$(document).on('click','.custom-edit',function(){openEditor(Number($(this).data('index')));});
|
||||
$(document).on('click','.custom-delete',function(){deleteApplication(Number($(this).data('index')));});
|
||||
$(document).on('click','#custom-editor-close,#custom-cancel',closeEditor);
|
||||
$(document).on('click','#custom-save',saveEditor);
|
||||
$(document).on('click','#add-feature',function(){addFeatureEditor(emptyFeature());});
|
||||
$(document).on('click','.feature-remove',function(){if($('#feature-editors .feature-editor').length<=1){setEditorError(txt.emptyFeature);return;}$(this).closest('.feature-editor').remove();});
|
||||
$('#custom-editor').on('click',function(event){if(event.target===this)closeEditor();});
|
||||
|
||||
$(function(){var defaultLang=browserDefaultOnlineLang();$('#online-lang').val(defaultLang);refreshFeatureInfo();pollOnlineStatus();loadClassList();});
|
||||
})(jQuery);
|
||||
</script>
|
||||
<%+footer%>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,495 @@
|
||||
<link rel="stylesheet" href="<%=resource%>/oaf/css/common.css">
|
||||
<style>
|
||||
|
||||
.user-list {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.user-list th, .user-list td {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Add user button */
|
||||
.add-user-btn {
|
||||
margin-top: 10px;
|
||||
padding: 5px 15px;
|
||||
background-color: #113861;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mode-selection {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* Modal styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
width: 60%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.user-selection {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.whitelist-section {
|
||||
margin-top: 30px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.whitelist-section.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.whitelist-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
let userData = {
|
||||
users: [],
|
||||
mode: 1
|
||||
};
|
||||
|
||||
|
||||
let allUsers = [
|
||||
|
||||
];
|
||||
function initData() {
|
||||
userData.list = [
|
||||
|
||||
];
|
||||
getAppFilterUserData();
|
||||
getAllUsersData();
|
||||
getWhitelistData();
|
||||
}
|
||||
|
||||
|
||||
function getAllUsersData() {
|
||||
new XHR().get('<%=url('admin/services/oaf/api/get_all_users')%>', {flag: 2, page: 0},
|
||||
function (x, data) {
|
||||
if (Array.isArray(data.data.list))
|
||||
allUsers = data.data.list;
|
||||
renderAutoUserData();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function getAppFilterUserData() {
|
||||
new XHR().get('<%=url('admin/services/oaf/api/mac_filter/get_mac_filter_user')%>', null,
|
||||
function (x, data) {
|
||||
|
||||
userData = data.data;
|
||||
if (!Array.isArray(userData.list))
|
||||
userData.list=[]
|
||||
renderUserData();
|
||||
render_mode_description(userData.mode);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function renderUserData() {
|
||||
const table = document.getElementById('user_table');
|
||||
const rows = table.querySelectorAll('.tr:not(.table-titles)');
|
||||
rows.forEach(row => row.remove());
|
||||
|
||||
const modeRadios = document.querySelectorAll('input[name="mode"]');
|
||||
|
||||
console.log("userData.mode = " + userData.mode);
|
||||
modeRadios.forEach(radio => {
|
||||
console.log("radio.value = " + radio.value);
|
||||
radio.checked = parseInt(radio.value) === userData.mode;
|
||||
});
|
||||
if (userData.mode === 1) {
|
||||
document.getElementById('user_table').style.display = 'table';
|
||||
document.getElementById('auto_user_table').style.display = 'none';
|
||||
} else {
|
||||
document.getElementById('user_table').style.display = 'none';
|
||||
document.getElementById('auto_user_table').style.display = 'table';
|
||||
}
|
||||
|
||||
const addButton = document.querySelector('.cbi-button-add');
|
||||
const actionsHeader = document.querySelector('.table-titles .th:last-child');
|
||||
if (addButton) {
|
||||
addButton.style.display = userData.mode === 1 ? 'inline-block' : 'none';
|
||||
}
|
||||
if (actionsHeader) {
|
||||
actionsHeader.style.display = userData.mode === 1 ? '' : 'none';
|
||||
}
|
||||
|
||||
const displayList = userData.mode === 1 ? userData.list : allUsers;
|
||||
|
||||
displayList.forEach((user, index) => {
|
||||
const hostname = user.hostname || '--';
|
||||
const nickname = user.nickname || '--';
|
||||
const row = document.createElement('div');
|
||||
row.className = 'tr';
|
||||
row.innerHTML = `
|
||||
<div class="td">${user.mac}</div>
|
||||
<div class="td">${hostname}</div>
|
||||
<div class="td">${nickname}</div>
|
||||
${userData.mode === 1 ? `<div class="td"><button type="button" class="btn-delete" onclick="removeUser(${index})"><%:Delete%></button></div>` : ''}
|
||||
`;
|
||||
table.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function renderAutoUserData() {
|
||||
const table = document.getElementById('auto_user_table');
|
||||
const rows = table.querySelectorAll('.tr:not(.table-titles)');
|
||||
rows.forEach(row => row.remove());
|
||||
|
||||
const displayList = allUsers.filter(user =>
|
||||
!whitelistData.list.some(wUser => wUser.mac === user.mac)
|
||||
);
|
||||
|
||||
displayList.forEach((user, index) => {
|
||||
const hostname = user.hostname || '--';
|
||||
const nickname = user.nickname || '--';
|
||||
const row = document.createElement('div');
|
||||
row.className = 'tr';
|
||||
row.innerHTML = `
|
||||
<div class="td">${user.mac}</div>
|
||||
<div class="td">${hostname}</div>
|
||||
<div class="td">${nickname}</div>
|
||||
<div class="td">
|
||||
<button type="button" class="add-user-btn" style="padding: 3px 8px;" onclick="addToWhitelist('${user.mac}')"><%:Add to whitelist%></button>
|
||||
</div>
|
||||
`;
|
||||
table.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function showSuccessMessage(message = '<%:Setting success%>') {
|
||||
const modal = document.getElementById('modal');
|
||||
const messageElement = modal.querySelector('p');
|
||||
messageElement.textContent = message;
|
||||
modal.style.display = 'flex';
|
||||
setTimeout(() => {
|
||||
modal.style.display = 'none';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function removeUser(index) {
|
||||
const userToRemove = userData.list[index];
|
||||
new XHR().post('<%=url('admin/services/oaf/api/mac_filter/del_mac_filter_user')%>',
|
||||
{ mac: userToRemove.mac },
|
||||
function(x, data) {
|
||||
getAppFilterUserData();
|
||||
showSuccessMessage('<%:Delete success%>');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function showUserModal() {
|
||||
const modal = document.getElementById('userModal');
|
||||
modal.style.display = 'flex';
|
||||
|
||||
const userList = document.getElementById('userList');
|
||||
userList.innerHTML = '';
|
||||
|
||||
allUsers.forEach(user => {
|
||||
const userElement = document.createElement('div');
|
||||
userElement.style.padding = '8px';
|
||||
userElement.style.borderBottom = '1px solid #eee';
|
||||
|
||||
const displayName = user.nickname || user.hostname || '';
|
||||
|
||||
userElement.innerHTML = `
|
||||
<label style="display: flex; align-items: center;">
|
||||
<input type="checkbox" value="${user.mac}" style="margin-right: 8px;">
|
||||
<span>${user.mac}</span>
|
||||
<span style="color: #666; margin-left: 8px;">${displayName ? `(${displayName})` : ''}</span>
|
||||
</label>
|
||||
`;
|
||||
userList.appendChild(userElement);
|
||||
});
|
||||
}
|
||||
|
||||
function closeUserModal() {
|
||||
const modal = document.getElementById('userModal');
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
|
||||
function addSelectedUsers() {
|
||||
const userList = document.getElementById('userList');
|
||||
const checkboxes = userList.querySelectorAll('input[type="checkbox"]:checked');
|
||||
|
||||
const selectedMacs = Array.from(checkboxes).map(checkbox => checkbox.value);
|
||||
|
||||
var data = {
|
||||
mac_list: selectedMacs
|
||||
}
|
||||
var data_str = JSON.stringify(data);
|
||||
|
||||
console.log(selectedMacs);
|
||||
new XHR().post('<%=url('admin/services/oaf/api/mac_filter/add_mac_filter_user')%>',
|
||||
{ data: data_str },
|
||||
function(x, data) {
|
||||
getAppFilterUserData();
|
||||
closeUserModal();
|
||||
showSuccessMessage('<%:Add success%>');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function render_mode_description(newMode) {
|
||||
const modeDescription = document.getElementById('modeDescription');
|
||||
if (newMode === 0) {
|
||||
modeDescription.textContent = '<%:In auto mode, all new devices will be managed, including those with random MAC addresses%>';
|
||||
} else {
|
||||
modeDescription.textContent = '<%:In manual mode, only specified devices will be managed%>';
|
||||
}
|
||||
|
||||
const whitelistSection = document.getElementById('whitelist_section');
|
||||
if (newMode === 0) {
|
||||
whitelistSection.classList.add('show');
|
||||
} else {
|
||||
whitelistSection.classList.remove('show');
|
||||
}
|
||||
}
|
||||
|
||||
function switchMode(newMode) {
|
||||
userData.mode = newMode;
|
||||
renderUserData();
|
||||
render_mode_description(newMode);
|
||||
|
||||
new XHR().post('<%=url('admin/services/oaf/api/mac_filter/set_mac_filter_user')%>',
|
||||
{ mode: newMode },
|
||||
function(x, data) {
|
||||
console.log('Mode updated successfully');
|
||||
showSuccessMessage('<%:Mode switched successfully%>');
|
||||
});
|
||||
}
|
||||
|
||||
let whitelistData = {
|
||||
list: []
|
||||
};
|
||||
|
||||
function getWhitelistData() {
|
||||
new XHR().get('<%=url('admin/services/oaf/api/mac_filter/get_mac_filter_whitelist')%>', null,
|
||||
function (x, data) {
|
||||
whitelistData = data.data;
|
||||
if (!Array.isArray(whitelistData.list))
|
||||
whitelistData.list = [];
|
||||
renderWhitelistData();
|
||||
renderAutoUserData();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function renderWhitelistData() {
|
||||
const table = document.getElementById('whitelist_table');
|
||||
const rows = table.querySelectorAll('.tr:not(.table-titles)');
|
||||
rows.forEach(row => row.remove());
|
||||
|
||||
whitelistData.list.forEach((user, index) => {
|
||||
const hostname = user.hostname || '--';
|
||||
const nickname = user.nickname || '--';
|
||||
const row = document.createElement('div');
|
||||
row.className = 'tr';
|
||||
row.innerHTML = `
|
||||
<div class="td">${user.mac}</div>
|
||||
<div class="td">${hostname}</div>
|
||||
<div class="td">${nickname}</div>
|
||||
<div class="td"><button type="button" class="btn-delete" onclick="removeWhitelistUser(${index})"><%:Delete%></button></div>
|
||||
`;
|
||||
table.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function showWhitelistModal() {
|
||||
const modal = document.getElementById('whitelistModal');
|
||||
modal.style.display = 'flex';
|
||||
|
||||
const userList = document.getElementById('whitelistUserList');
|
||||
userList.innerHTML = '';
|
||||
|
||||
allUsers.forEach(user => {
|
||||
const userElement = document.createElement('div');
|
||||
userElement.style.padding = '8px';
|
||||
userElement.style.borderBottom = '1px solid #eee';
|
||||
|
||||
const displayName = user.nickname || user.hostname || '';
|
||||
|
||||
userElement.innerHTML = `
|
||||
<label style="display: flex; align-items: center;">
|
||||
<input type="checkbox" value="${user.mac}" style="margin-right: 8px;">
|
||||
<span>${user.mac}</span>
|
||||
<span style="color: #666; margin-left: 8px;">${displayName ? `(${displayName})` : ''}</span>
|
||||
</label>
|
||||
`;
|
||||
userList.appendChild(userElement);
|
||||
});
|
||||
}
|
||||
|
||||
function closeWhitelistModal() {
|
||||
const modal = document.getElementById('whitelistModal');
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
|
||||
function addSelectedWhitelistUsers() {
|
||||
const userList = document.getElementById('whitelistUserList');
|
||||
const checkboxes = userList.querySelectorAll('input[type="checkbox"]:checked');
|
||||
|
||||
const selectedMacs = Array.from(checkboxes).map(checkbox => checkbox.value);
|
||||
|
||||
var data = {
|
||||
mac_list: selectedMacs
|
||||
};
|
||||
|
||||
new XHR().post('<%=url('admin/services/oaf/api/mac_filter/add_mac_filter_whitelist')%>',
|
||||
{ data: JSON.stringify(data) },
|
||||
function(x, res) {
|
||||
closeWhitelistModal();
|
||||
getWhitelistData();
|
||||
showSuccessMessage('<%:Add success%>');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function removeWhitelistUser(index) {
|
||||
const userToRemove = whitelistData.list[index];
|
||||
new XHR().post('<%=url('admin/services/oaf/api/mac_filter/del_mac_filter_whitelist')%>',
|
||||
{ mac: userToRemove.mac },
|
||||
function(x, data) {
|
||||
getWhitelistData();
|
||||
showSuccessMessage('<%:Delete success%>');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function addToWhitelist(mac) {
|
||||
var data = {
|
||||
mac_list: [mac]
|
||||
};
|
||||
|
||||
new XHR().post('<%=url('admin/services/oaf/api/mac_filter/add_mac_filter_whitelist')%>',
|
||||
{ data: JSON.stringify(data) },
|
||||
function(x, res) {
|
||||
getWhitelistData();
|
||||
renderAutoUserData();
|
||||
showSuccessMessage('<%:Add success%>');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
window.onload = initData;
|
||||
//]]></script>
|
||||
|
||||
<div class="cbi-section cbi-tblsection">
|
||||
<div style="max-width: 1000px; margin-left: 5px;padding: 5px;">
|
||||
<div class="mode-selection">
|
||||
<input type="radio" name="mode" value="0" onclick="switchMode(0)"> <%:Auto mode%>
|
||||
<input type="radio" name="mode" value="1" checked onclick="switchMode(1)"> <%:Manual mode%>
|
||||
</div>
|
||||
|
||||
<div id="modeDescription" style="margin-bottom: 15px;">
|
||||
<%:In manual mode, only specified devices will be managed%>
|
||||
</div>
|
||||
|
||||
<div class="table" id="user_table" style="display: none;">
|
||||
<div class="tr table-titles">
|
||||
<div class="th"><%:MAC%></div>
|
||||
<div class="th"><%:Hostname%></div>
|
||||
<div class="th"><%:Remark%></div>
|
||||
<div class="th"><%:Actions%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table" id="auto_user_table" style="display: none;">
|
||||
<div class="tr table-titles">
|
||||
<div class="th"><%:MAC%></div>
|
||||
<div class="th"><%:Hostname%></div>
|
||||
<div class="th"><%:Device nickname%></div>
|
||||
<div class="th"><%:Operation%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-section-create">
|
||||
<input type="button" class="cbi-button cbi-button-add" value="<%:Add user%>" onclick="showUserModal()" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="userModal" style="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;">
|
||||
<div class="modal-content" style="background-color: #2d2d2d; color: #ffffff;">
|
||||
<h3 style="margin: 0 0 15px 0;"><%:Select device%></h3>
|
||||
<div id="userList" style="flex: 1; overflow-y: auto;">
|
||||
</div>
|
||||
<div style="margin-top: 15px; text-align: right;">
|
||||
<button type="button" onclick="addSelectedUsers()" style="margin-right: 10px; padding: 5px 15px;"><%:OK%></button>
|
||||
<button type="button" onclick="closeUserModal()" style="padding: 5px 15px;"><%:Cancel%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="modal" style="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;">
|
||||
<div style="background-color: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px; text-align: center; width: 130px; height: 70px; color: white; display: flex; justify-content: center; align-items: center;">
|
||||
<p style="margin: 0;color: white;"><%:Setting success%></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="whitelist-section" id="whitelist_section">
|
||||
<div class="whitelist-title"><%:Whitelist%></div>
|
||||
<div class="table" id="whitelist_table">
|
||||
<div class="tr table-titles">
|
||||
<div class="th"><%:MAC%></div>
|
||||
<div class="th"><%:Hostname%></div>
|
||||
<div class="th"><%:Device nickname%></div>
|
||||
<div class="th"><%:Operation%></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="whitelistModal">
|
||||
<div class="modal-content">
|
||||
<h3><%:Add to whitelist%></h3>
|
||||
<div class="user-selection" id="whitelistUserList"></div>
|
||||
<div style="text-align: right; margin-top: 15px;">
|
||||
<button type="button" class="add-user-btn" onclick="addSelectedWhitelistUsers()"><%:OK%></button>
|
||||
<button type="button" onclick="closeWhitelistModal()" style="margin-left: 10px;"><%:Cancel%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,672 @@
|
||||
<%
|
||||
local dsp = require "luci.dispatcher"
|
||||
local nfs = require "nixio.fs"
|
||||
local has_app_filter = nfs.access("/etc/config/appfilter")
|
||||
local has_access_control = nfs.access("/etc/config/macfilter")
|
||||
local has_record = nfs.access("/etc/config/fwx_record")
|
||||
%>
|
||||
<link rel="stylesheet" href="<%=resource%>/oaf/css/common.css">
|
||||
<script src="<%=resource%>/oaf/jquery-3.7.1.min.js"></script>
|
||||
<script src="<%=resource%>/oaf/common.js"></script>
|
||||
|
||||
<style>
|
||||
.oaf-whitelist-tip {
|
||||
margin: 8px 0 14px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--border-color-low, #e5e7eb);
|
||||
border-radius: 6px;
|
||||
background: var(--background-color-low, #f5f5f5);
|
||||
color: var(--text-color-medium, #6b7280);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.oaf-whitelist-tabs {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
margin: 10px 0 15px;
|
||||
border-bottom: 1px solid var(--border-color-medium, #d1d5db);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.oaf-whitelist-tab {
|
||||
padding: 3px 16px;
|
||||
border: 1px solid var(--border-color-medium, #d1d5db) !important;
|
||||
border-bottom: none !important;
|
||||
border-radius: 5px 5px 0 0;
|
||||
margin-right: 6px;
|
||||
background: var(--background-color-low, #f5f5f5) !important;
|
||||
color: var(--text-color-medium, #6b7280) !important;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.oaf-whitelist-tab:hover {
|
||||
background: var(--background-color-medium, #eee) !important;
|
||||
color: var(--text-color-high, #333) !important;
|
||||
}
|
||||
|
||||
.oaf-whitelist-tab.active {
|
||||
background: var(--background-color-high, #fff) !important;
|
||||
color: var(--text-color-high, #333) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.oaf-whitelist-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.oaf-whitelist-panel.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.oaf-whitelist-panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.oaf-whitelist-panel-header h3 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oaf-whitelist-table-wrap {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.oaf-whitelist-table {
|
||||
width: 100%;
|
||||
min-width: 680px;
|
||||
}
|
||||
|
||||
.oaf-whitelist-add-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
min-width: max-content;
|
||||
white-space: nowrap;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 9px 18px;
|
||||
background-color: #2885e8;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.oaf-whitelist-add-btn:hover {
|
||||
background-color: #1e6bb8;
|
||||
}
|
||||
|
||||
.oaf-whitelist-delete-btn {
|
||||
padding: 5px 12px;
|
||||
background-color: #e5484d;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.oaf-whitelist-delete-btn:hover {
|
||||
background-color: #c9373c;
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal,
|
||||
.oaf-whitelist-message {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal {
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-content {
|
||||
width: calc(100vw - 32px);
|
||||
max-width: 700px;
|
||||
max-height: calc(100vh - 64px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color-low, #d1d5db);
|
||||
background: var(--background-color-high, #fff);
|
||||
color: var(--text-color-high, #1f2937);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-header {
|
||||
padding: 20px 24px 14px;
|
||||
border-bottom: 1px solid var(--border-color-low, #d1d5db);
|
||||
color: var(--text-color-high, #1f2937);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.oaf-whitelist-selection-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 24px 10px;
|
||||
}
|
||||
|
||||
.oaf-whitelist-selection-stat {
|
||||
color: var(--text-color-medium, #6b7280);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-list {
|
||||
min-height: 180px;
|
||||
margin: 0 24px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border-color-low, #d1d5db);
|
||||
border-radius: 4px;
|
||||
background: var(--background-color-low, #f5f5f5);
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--border-color-low, #d1d5db);
|
||||
background: var(--background-color-high, #fff);
|
||||
color: var(--text-color-high, #1f2937);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-item:hover {
|
||||
background: var(--background-color-medium, #eee);
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-item .oaf-check-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-item .oaf-check-item > input[type="checkbox"] {
|
||||
margin: 0 12px 0 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-item .oaf-check-item > label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.oaf-whitelist-user-mac {
|
||||
color: var(--text-color-high, #1f2937);
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.oaf-whitelist-empty-state {
|
||||
padding: 40px;
|
||||
color: var(--text-color-medium, #6b7280);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid var(--border-color-low, #d1d5db);
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-btn {
|
||||
min-width: 84px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-btn-primary {
|
||||
border: none;
|
||||
background-color: #2885e8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-btn-secondary {
|
||||
border: 1px solid var(--border-color-medium, #d1d5db);
|
||||
background: transparent;
|
||||
color: var(--text-color-high, #1f2937);
|
||||
}
|
||||
|
||||
[data-darkmode="true"] .oaf-whitelist-user-item:hover {
|
||||
background: var(--background-color-medium, rgba(255, 255, 255, 0.08));
|
||||
}
|
||||
|
||||
.oaf-whitelist-message {
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.oaf-whitelist-message-content {
|
||||
padding: 20px 30px;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.oaf-whitelist-tabs {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.oaf-whitelist-tab {
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.oaf-whitelist-panel-header,
|
||||
.oaf-whitelist-selection-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-content {
|
||||
width: calc(100vw - 20px);
|
||||
max-height: calc(100vh - 24px);
|
||||
}
|
||||
|
||||
.oaf-whitelist-modal-buttons .oaf-whitelist-modal-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var whitelistText = {
|
||||
noEntries: '<%:No whitelist entries yet%>',
|
||||
loading: '<%:Loading...%>',
|
||||
deleteLabel: '<%:Delete%>',
|
||||
noDevices: '<%:No available devices to add%>',
|
||||
selectDevice: '<%:Please select at least one device%>',
|
||||
addSuccess: '<%:Add success%>',
|
||||
addFailed: '<%:Add failed%>',
|
||||
confirmRemove: '<%:Are you sure you want to remove this device from whitelist?%>',
|
||||
deleteSuccess: '<%:Delete success%>',
|
||||
deleteFailed: '<%:Delete failed%>',
|
||||
selected: '<%:Selected%>'
|
||||
};
|
||||
|
||||
var whitelistTypes = {};
|
||||
<% if has_app_filter then %>
|
||||
whitelistTypes.app_filter = {
|
||||
panelId: 'appFilterWhitelistPanel',
|
||||
tableBodyId: 'appFilterWhitelistTableBody',
|
||||
getUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "app_filter", "get_appfilter_whitelist")%>',
|
||||
addUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "app_filter", "add_appfilter_whitelist")%>',
|
||||
delUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "app_filter", "del_appfilter_whitelist")%>',
|
||||
usersUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "app_filter", "get_all_users")%>',
|
||||
list: [],
|
||||
users: [],
|
||||
usersLoaded: false
|
||||
};
|
||||
<% end %>
|
||||
<% if has_access_control then %>
|
||||
whitelistTypes.access_control = {
|
||||
panelId: 'accessControlWhitelistPanel',
|
||||
tableBodyId: 'accessControlWhitelistTableBody',
|
||||
getUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "mac_filter", "get_mac_filter_whitelist")%>',
|
||||
addUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "mac_filter", "add_mac_filter_whitelist")%>',
|
||||
delUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "mac_filter", "del_mac_filter_whitelist")%>',
|
||||
usersUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "get_all_users")%>',
|
||||
list: [],
|
||||
users: [],
|
||||
usersLoaded: false
|
||||
};
|
||||
<% end %>
|
||||
<% if has_record then %>
|
||||
whitelistTypes.record = {
|
||||
panelId: 'recordWhitelistPanel',
|
||||
tableBodyId: 'recordWhitelistTableBody',
|
||||
getUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "record_whitelist", "get_record_whitelist")%>',
|
||||
addUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "record_whitelist", "add_record_whitelist")%>',
|
||||
delUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "record_whitelist", "del_record_whitelist")%>',
|
||||
usersUrl: '<%=dsp.build_url("admin", "services", "oaf", "api", "record_whitelist", "get_all_users")%>',
|
||||
list: [],
|
||||
users: [],
|
||||
usersLoaded: false
|
||||
};
|
||||
<% end %>
|
||||
|
||||
var activeWhitelistType = null;
|
||||
var modalWhitelistType = null;
|
||||
|
||||
function escapeWhitelistHtml(value) {
|
||||
return $('<div>').text(value == null ? '' : String(value)).html();
|
||||
}
|
||||
|
||||
function normalizeWhitelistMac(mac) {
|
||||
return (mac || '').replace(/^\s+|\s+$/g, '').toUpperCase();
|
||||
}
|
||||
|
||||
function switchWhitelistTab(type) {
|
||||
if (!whitelistTypes[type]) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeWhitelistType = type;
|
||||
$('.oaf-whitelist-tab').removeClass('active').attr('aria-selected', 'false');
|
||||
$('.oaf-whitelist-tab[data-type="' + type + '"]').addClass('active').attr('aria-selected', 'true');
|
||||
$('.oaf-whitelist-panel').removeClass('active');
|
||||
$('#' + whitelistTypes[type].panelId).addClass('active');
|
||||
}
|
||||
|
||||
function loadWhitelist(type) {
|
||||
var config = whitelistTypes[type];
|
||||
|
||||
new XHR().get(config.getUrl, null, function(x, data) {
|
||||
config.list = data && data.data && Array.isArray(data.data.list) ? data.data.list : [];
|
||||
renderWhitelistTable(type);
|
||||
}, function() {
|
||||
config.list = [];
|
||||
renderWhitelistTable(type);
|
||||
});
|
||||
}
|
||||
|
||||
function loadWhitelistUsers(type, callback) {
|
||||
var config = whitelistTypes[type];
|
||||
|
||||
new XHR().get(config.usersUrl, {flag: 2, page: 0}, function(x, data) {
|
||||
config.users = data && data.data && Array.isArray(data.data.list) ? data.data.list : [];
|
||||
config.usersLoaded = true;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}, function() {
|
||||
config.users = [];
|
||||
config.usersLoaded = true;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderWhitelistTable(type) {
|
||||
var config = whitelistTypes[type];
|
||||
var tableBody = document.getElementById(config.tableBodyId);
|
||||
|
||||
tableBody.innerHTML = '';
|
||||
if (config.list.length === 0) {
|
||||
tableBody.innerHTML = '<tr><td colspan="5" style="text-align:center;padding:30px;color:#999;">' + whitelistText.noEntries + '</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
config.list.forEach(function(user, index) {
|
||||
var row = document.createElement('tr');
|
||||
var mac = normalizeWhitelistMac(user.mac || '');
|
||||
row.innerHTML = '<td>' + (index + 1) + '</td>' +
|
||||
'<td style="font-family:monospace;font-weight:bold;">' + escapeWhitelistHtml(mac) + '</td>' +
|
||||
'<td>' + escapeWhitelistHtml(user.hostname || '--') + '</td>' +
|
||||
'<td>' + escapeWhitelistHtml(user.nickname || '--') + '</td>' +
|
||||
'<td><button type="button" class="oaf-whitelist-delete-btn" onclick="removeWhitelistUser(\'' + type + '\',\'' + mac + '\')">' + whitelistText.deleteLabel + '</button></td>';
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function showWhitelistModal(type) {
|
||||
modalWhitelistType = type;
|
||||
$('#oafWhitelistModal').css('display', 'flex');
|
||||
|
||||
if (!whitelistTypes[type].usersLoaded) {
|
||||
$('#oafWhitelistUserList').html('<div class="oaf-whitelist-empty-state">' + whitelistText.loading + '</div>');
|
||||
updateWhitelistSelectionStats();
|
||||
loadWhitelistUsers(type, function() {
|
||||
if (modalWhitelistType === type) {
|
||||
renderAvailableWhitelistUsers(type);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
renderAvailableWhitelistUsers(type);
|
||||
}
|
||||
|
||||
function renderAvailableWhitelistUsers(type) {
|
||||
var config = whitelistTypes[type];
|
||||
var whitelistMacs = config.list.map(function(user) { return normalizeWhitelistMac(user.mac || ''); });
|
||||
var availableUsers = config.users.filter(function(user) { return whitelistMacs.indexOf(normalizeWhitelistMac(user.mac || '')) === -1; });
|
||||
var userList = $('#oafWhitelistUserList').empty();
|
||||
|
||||
if (availableUsers.length === 0) {
|
||||
userList.html('<div class="oaf-whitelist-empty-state">' + whitelistText.noDevices + '</div>');
|
||||
updateWhitelistSelectionStats();
|
||||
return;
|
||||
}
|
||||
|
||||
availableUsers.forEach(function(user) {
|
||||
var mac = normalizeWhitelistMac(user.mac || '');
|
||||
var extraInfo = user.nickname || ((user.hostname && user.hostname !== '*') ? user.hostname : user.ip) || '';
|
||||
var displayText = extraInfo ? mac + ' (' + extraInfo + ')' : mac;
|
||||
var item = $('<div class="oaf-whitelist-user-item"></div>');
|
||||
var checkId = 'oaf-whitelist-user-' + modalWhitelistType + '-' + mac.replace(/[^a-zA-Z0-9_-]/g, '-');
|
||||
var checkWrap = $('<div class="oaf-check-item"></div>');
|
||||
var checkbox = $('<input type="checkbox">')
|
||||
.attr('id', checkId)
|
||||
.val(mac)
|
||||
.on('change', updateWhitelistSelectionStats);
|
||||
var label = $('<label class="oaf-whitelist-user-mac"></label>')
|
||||
.attr('for', checkId)
|
||||
.text(displayText);
|
||||
|
||||
checkWrap.append(checkbox).append(label);
|
||||
item.append(checkWrap);
|
||||
item.on('click', function(event) {
|
||||
if (!$(event.target).closest('.oaf-check-item').length) {
|
||||
checkbox.prop('checked', !checkbox.prop('checked')).trigger('change');
|
||||
}
|
||||
});
|
||||
userList.append(item);
|
||||
});
|
||||
|
||||
updateWhitelistSelectionStats();
|
||||
}
|
||||
|
||||
function updateWhitelistSelectionStats() {
|
||||
var allCount = $('#oafWhitelistUserList input[type="checkbox"]').length;
|
||||
var selectedCount = $('#oafWhitelistUserList input[type="checkbox"]:checked').length;
|
||||
$('#oafWhitelistSelectionStats').text(whitelistText.selected + ': ' + selectedCount + ' / ' + allCount);
|
||||
}
|
||||
|
||||
function toggleAllWhitelistUsers() {
|
||||
var checkboxes = $('#oafWhitelistUserList input[type="checkbox"]');
|
||||
var selectedCount = checkboxes.filter(':checked').length;
|
||||
checkboxes.prop('checked', checkboxes.length > 0 && selectedCount !== checkboxes.length);
|
||||
updateWhitelistSelectionStats();
|
||||
}
|
||||
|
||||
function closeWhitelistModal() {
|
||||
$('#oafWhitelistModal').hide();
|
||||
modalWhitelistType = null;
|
||||
}
|
||||
|
||||
function addWhitelistUsers() {
|
||||
var config = whitelistTypes[modalWhitelistType];
|
||||
var selectedMacs = $('#oafWhitelistUserList input[type="checkbox"]:checked').map(function() {
|
||||
return this.value;
|
||||
}).get();
|
||||
|
||||
if (selectedMacs.length === 0) {
|
||||
showWhitelistMessage(whitelistText.selectDevice);
|
||||
return;
|
||||
}
|
||||
|
||||
new XHR().post(config.addUrl, {data: JSON.stringify({mac_list: selectedMacs})}, function(x, data) {
|
||||
if (data && data.code == 2000) {
|
||||
var type = modalWhitelistType;
|
||||
closeWhitelistModal();
|
||||
loadWhitelist(type);
|
||||
showWhitelistMessage(whitelistText.addSuccess);
|
||||
} else {
|
||||
showWhitelistMessage(whitelistText.addFailed);
|
||||
}
|
||||
}, function() {
|
||||
showWhitelistMessage(whitelistText.addFailed);
|
||||
});
|
||||
}
|
||||
|
||||
function removeWhitelistUser(type, mac) {
|
||||
var config = whitelistTypes[type];
|
||||
|
||||
if (!confirm(whitelistText.confirmRemove)) {
|
||||
return;
|
||||
}
|
||||
|
||||
new XHR().post(config.delUrl, {mac: mac}, function(x, data) {
|
||||
if (data && data.code == 2000) {
|
||||
loadWhitelist(type);
|
||||
showWhitelistMessage(whitelistText.deleteSuccess);
|
||||
} else {
|
||||
showWhitelistMessage(whitelistText.deleteFailed);
|
||||
}
|
||||
}, function() {
|
||||
showWhitelistMessage(whitelistText.deleteFailed);
|
||||
});
|
||||
}
|
||||
|
||||
function showWhitelistMessage(message) {
|
||||
$('#oafWhitelistMessageText').text(message);
|
||||
$('#oafWhitelistMessage').css('display', 'flex');
|
||||
setTimeout(function() {
|
||||
$('#oafWhitelistMessage').hide();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
var types = Object.keys(whitelistTypes);
|
||||
|
||||
if (types.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
switchWhitelistTab(types[0]);
|
||||
types.forEach(function(type) {
|
||||
loadWhitelist(type);
|
||||
loadWhitelistUsers(type);
|
||||
});
|
||||
|
||||
$('#oafWhitelistModal').on('click', function(event) {
|
||||
if (event.target === this) {
|
||||
closeWhitelistModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
//]]></script>
|
||||
|
||||
<div class="cbi-section cbi-tblsection">
|
||||
<div class="oaf-whitelist-tip">
|
||||
<%: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.%>
|
||||
</div>
|
||||
|
||||
<div class="oaf-whitelist-tabs" role="tablist">
|
||||
<% if has_app_filter then %>
|
||||
<button type="button" class="oaf-whitelist-tab" data-type="app_filter" role="tab" aria-selected="false" onclick="switchWhitelistTab('app_filter')"><%:App Filter Whitelist%></button>
|
||||
<% end %>
|
||||
<% if has_access_control then %>
|
||||
<button type="button" class="oaf-whitelist-tab" data-type="access_control" role="tab" aria-selected="false" onclick="switchWhitelistTab('access_control')"><%:Access Control Whitelist%></button>
|
||||
<% end %>
|
||||
<% if has_record then %>
|
||||
<button type="button" class="oaf-whitelist-tab" data-type="record" role="tab" aria-selected="false" onclick="switchWhitelistTab('record')"><%:Record Whitelist%></button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if has_app_filter then %>
|
||||
<div id="appFilterWhitelistPanel" class="oaf-whitelist-panel" role="tabpanel">
|
||||
<div class="oaf-whitelist-panel-header">
|
||||
<h3 style="margin:0;"><%:App Filter Whitelist%></h3>
|
||||
<button type="button" class="oaf-whitelist-add-btn" onclick="showWhitelistModal('app_filter')">+ <%:Add to Whitelist%></button>
|
||||
</div>
|
||||
<div class="oaf-whitelist-table-wrap">
|
||||
<table class="common-table oaf-whitelist-table">
|
||||
<thead><tr><th><%:No.%></th><th><%:MAC Address%></th><th><%:Hostname%></th><th><%:Nickname%></th><th><%:Operation%></th></tr></thead>
|
||||
<tbody id="appFilterWhitelistTableBody"><tr><td colspan="5" style="text-align:center;padding:30px;color:#999;"><%:Loading...%></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if has_access_control then %>
|
||||
<div id="accessControlWhitelistPanel" class="oaf-whitelist-panel" role="tabpanel">
|
||||
<div class="oaf-whitelist-panel-header">
|
||||
<h3 style="margin:0;"><%:Access Control Whitelist%></h3>
|
||||
<button type="button" class="oaf-whitelist-add-btn" onclick="showWhitelistModal('access_control')">+ <%:Add to Whitelist%></button>
|
||||
</div>
|
||||
<div class="oaf-whitelist-table-wrap">
|
||||
<table class="common-table oaf-whitelist-table">
|
||||
<thead><tr><th><%:No.%></th><th><%:MAC Address%></th><th><%:Hostname%></th><th><%:Nickname%></th><th><%:Operation%></th></tr></thead>
|
||||
<tbody id="accessControlWhitelistTableBody"><tr><td colspan="5" style="text-align:center;padding:30px;color:#999;"><%:Loading...%></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if has_record then %>
|
||||
<div id="recordWhitelistPanel" class="oaf-whitelist-panel" role="tabpanel">
|
||||
<div class="oaf-whitelist-panel-header">
|
||||
<h3 style="margin:0;"><%:Record Whitelist%></h3>
|
||||
<button type="button" class="oaf-whitelist-add-btn" onclick="showWhitelistModal('record')">+ <%:Add to Whitelist%></button>
|
||||
</div>
|
||||
<div class="oaf-whitelist-table-wrap">
|
||||
<table class="common-table oaf-whitelist-table">
|
||||
<thead><tr><th><%:No.%></th><th><%:MAC Address%></th><th><%:Hostname%></th><th><%:Nickname%></th><th><%:Operation%></th></tr></thead>
|
||||
<tbody id="recordWhitelistTableBody"><tr><td colspan="5" style="text-align:center;padding:30px;color:#999;"><%:Loading...%></td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="oafWhitelistModal" class="oaf-whitelist-modal">
|
||||
<div class="oaf-whitelist-modal-content">
|
||||
<div class="oaf-whitelist-modal-header"><%:Add to Whitelist%></div>
|
||||
<div class="oaf-whitelist-selection-toolbar">
|
||||
<button type="button" class="oaf-whitelist-modal-btn oaf-whitelist-modal-btn-secondary" onclick="toggleAllWhitelistUsers()"><%:Select All%></button>
|
||||
<span id="oafWhitelistSelectionStats" class="oaf-whitelist-selection-stat"><%:Selected%>: 0 / 0</span>
|
||||
</div>
|
||||
<div id="oafWhitelistUserList" class="oaf-whitelist-user-list"></div>
|
||||
<div class="oaf-whitelist-modal-buttons">
|
||||
<button type="button" class="oaf-whitelist-modal-btn oaf-whitelist-modal-btn-primary" onclick="addWhitelistUsers()"><%:OK%></button>
|
||||
<button type="button" class="oaf-whitelist-modal-btn oaf-whitelist-modal-btn-secondary" onclick="closeWhitelistModal()"><%:Cancel%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="oafWhitelistMessage" class="oaf-whitelist-message">
|
||||
<div class="oaf-whitelist-message-content">
|
||||
<p id="oafWhitelistMessageText" style="margin:0;"></p>
|
||||
</div>
|
||||
</div>
|
||||
Reference in new issue
Block a user