mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-02 03:13:56 +08:00
⛅ Sync 2026-09-28 20:44:51
This commit is contained in:
1 parent
08e60b55f3
commit
38660a8971
8 files changed
+170
-208
No files matched your search
@@ -6,8 +6,8 @@
|
|||||||
include $(TOPDIR)/rules.mk
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
PKG_NAME:=luci-app-honk
|
PKG_NAME:=luci-app-honk
|
||||||
PKG_VERSION:=1.1.3
|
PKG_VERSION:=1.1.4
|
||||||
PKG_RELEASE:=5
|
PKG_RELEASE:=6
|
||||||
|
|
||||||
LUCI_TITLE:=LuCI support for honk
|
LUCI_TITLE:=LuCI support for honk
|
||||||
LUCI_PKGARCH:=all
|
LUCI_PKGARCH:=all
|
||||||
|
|||||||
@@ -324,3 +324,6 @@ msgstr "面板未启用"
|
|||||||
|
|
||||||
msgid "Dashboard type is currently set to \"None\". Web dashboard and API listener are disabled."
|
msgid "Dashboard type is currently set to \"None\". Web dashboard and API listener are disabled."
|
||||||
msgstr "当前面板类型为“无”,Web 管理面板和 Native API 监听已禁用。"
|
msgstr "当前面板类型为“无”,Web 管理面板和 Native API 监听已禁用。"
|
||||||
|
|
||||||
|
msgid "Failed to switch dashboard:"
|
||||||
|
msgstr "切换面板失败:"
|
||||||
@@ -9,11 +9,6 @@
|
|||||||
"file": [
|
"file": [
|
||||||
"/etc/honk/config.dae",
|
"/etc/honk/config.dae",
|
||||||
"/etc/honk/config.d/*",
|
"/etc/honk/config.d/*",
|
||||||
"/etc/honk/config.d/api.dae",
|
|
||||||
"/etc/honk/config.d/dns.dae",
|
|
||||||
"/etc/honk/config.d/node.dae",
|
|
||||||
"/etc/honk/config.d/route.dae",
|
|
||||||
"/etc/honk/dashboard/*",
|
|
||||||
"/etc/honk/doona/*",
|
"/etc/honk/doona/*",
|
||||||
"/etc/honk/zashboard/*"
|
"/etc/honk/zashboard/*"
|
||||||
]
|
]
|
||||||
@@ -26,11 +21,6 @@
|
|||||||
"file": [
|
"file": [
|
||||||
"/etc/honk/config.dae",
|
"/etc/honk/config.dae",
|
||||||
"/etc/honk/config.d/*",
|
"/etc/honk/config.d/*",
|
||||||
"/etc/honk/config.d/api.dae",
|
|
||||||
"/etc/honk/config.d/dns.dae",
|
|
||||||
"/etc/honk/config.d/node.dae",
|
|
||||||
"/etc/honk/config.d/route.dae",
|
|
||||||
"/etc/honk/dashboard/*",
|
|
||||||
"/etc/honk/doona/*",
|
"/etc/honk/doona/*",
|
||||||
"/etc/honk/zashboard/*"
|
"/etc/honk/zashboard/*"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,6 +5,45 @@
|
|||||||
import { readfile, writefile, popen, stat } from 'fs';
|
import { readfile, writefile, popen, stat } from 'fs';
|
||||||
import { cursor } from 'uci';
|
import { cursor } from 'uci';
|
||||||
|
|
||||||
|
const DASHBOARD_DIRS = {
|
||||||
|
zashboard: "/etc/honk/zashboard",
|
||||||
|
doona: "/etc/honk/doona"
|
||||||
|
};
|
||||||
|
|
||||||
|
function get_dashboard_dir(type) {
|
||||||
|
return DASHBOARD_DIRS[type] || DASHBOARD_DIRS.doona;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_honk_pid() {
|
||||||
|
let p = popen("pidof honk-core 2>/dev/null");
|
||||||
|
let pids = p ? p.read("all") : "";
|
||||||
|
if (p) p.close();
|
||||||
|
let m = match(pids, /([0-9]+)/);
|
||||||
|
return m ? m[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_host_port(addr) {
|
||||||
|
let res = { host: "", port: "" };
|
||||||
|
if (!addr) return res;
|
||||||
|
let m_v6 = match(addr, /^\[([^\]]+)\]:([0-9]+)$/);
|
||||||
|
let m_v4 = match(addr, /^([^:]+):([0-9]+)$/);
|
||||||
|
let m_p = match(addr, /^:([0-9]+)$/);
|
||||||
|
if (m_v6) {
|
||||||
|
res.host = m_v6[1];
|
||||||
|
res.port = m_v6[2];
|
||||||
|
} else if (m_v4) {
|
||||||
|
res.host = m_v4[1];
|
||||||
|
res.port = m_v4[2];
|
||||||
|
} else if (m_p) {
|
||||||
|
res.host = "0.0.0.0";
|
||||||
|
res.port = m_p[1];
|
||||||
|
} else if (match(addr, /^[0-9]+$/)) {
|
||||||
|
res.host = "0.0.0.0";
|
||||||
|
res.port = addr;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
function strip_dae_comments(content) {
|
function strip_dae_comments(content) {
|
||||||
if (!content) return "";
|
if (!content) return "";
|
||||||
let lines = split(content, /[\r\n]+/);
|
let lines = split(content, /[\r\n]+/);
|
||||||
@@ -40,48 +79,6 @@ function strip_dae_comments(content) {
|
|||||||
return join("\n", clean_lines);
|
return join("\n", clean_lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_clash_api(clean_content) {
|
|
||||||
let api_m = match(clean_content, /clash_api\s*\{([^}]+)\}/);
|
|
||||||
if (!api_m) return null;
|
|
||||||
let block = api_m[1];
|
|
||||||
|
|
||||||
let ec_m = match(block, /external_controller\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
|
|
||||||
let ui_m = match(block, /external_ui\s*:\s*['"]?([^'" \t\r\n]+)['"]?/);
|
|
||||||
let sec_m = match(block, /secret\s*:\s*['"]?([^'" \t\r\n]*)['"]?/);
|
|
||||||
let dm_m = match(block, /default_mode\s*:\s*['"]?([^'" \t\r\n]*)['"]?/);
|
|
||||||
|
|
||||||
let res = {
|
|
||||||
external_controller: ec_m ? ec_m[1] : "",
|
|
||||||
external_ui: ui_m ? ui_m[1] : "",
|
|
||||||
secret: sec_m ? sec_m[1] : "",
|
|
||||||
default_mode: dm_m ? dm_m[1] : "Rule",
|
|
||||||
host: "",
|
|
||||||
port: ""
|
|
||||||
};
|
|
||||||
|
|
||||||
let ec = res.external_controller;
|
|
||||||
if (ec) {
|
|
||||||
let m_v6 = match(ec, /^\[([^\]]+)\]:([0-9]+)$/);
|
|
||||||
let m_v4 = match(ec, /^([^:]+):([0-9]+)$/);
|
|
||||||
let m_p = match(ec, /^:([0-9]+)$/);
|
|
||||||
if (m_v6) {
|
|
||||||
res.host = m_v6[1];
|
|
||||||
res.port = m_v6[2];
|
|
||||||
} else if (m_v4) {
|
|
||||||
res.host = m_v4[1];
|
|
||||||
res.port = m_v4[2];
|
|
||||||
} else if (m_p) {
|
|
||||||
res.host = "0.0.0.0";
|
|
||||||
res.port = m_p[1];
|
|
||||||
} else if (match(ec, /^[0-9]+$/)) {
|
|
||||||
res.host = "0.0.0.0";
|
|
||||||
res.port = ec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse_native_api(clean_content) {
|
function parse_native_api(clean_content) {
|
||||||
let api_m = match(clean_content, /native_api\s*\{([^}]+)\}/);
|
let api_m = match(clean_content, /native_api\s*\{([^}]+)\}/);
|
||||||
if (!api_m) return null;
|
if (!api_m) return null;
|
||||||
@@ -107,32 +104,20 @@ function parse_native_api(clean_content) {
|
|||||||
port: ""
|
port: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
let listen = res.listen;
|
let hp = parse_host_port(res.listen);
|
||||||
if (listen) {
|
res.host = hp.host;
|
||||||
let m_v6 = match(listen, /^\[([^\]]+)\]:([0-9]+)$/);
|
res.port = hp.port;
|
||||||
let m_v4 = match(listen, /^([^:]+):([0-9]+)$/);
|
|
||||||
let m_p = match(listen, /^:([0-9]+)$/);
|
|
||||||
if (m_v6) {
|
|
||||||
res.host = m_v6[1];
|
|
||||||
res.port = m_v6[2];
|
|
||||||
} else if (m_v4) {
|
|
||||||
res.host = m_v4[1];
|
|
||||||
res.port = m_v4[2];
|
|
||||||
} else if (m_p) {
|
|
||||||
res.host = "0.0.0.0";
|
|
||||||
res.port = m_p[1];
|
|
||||||
} else if (match(listen, /^[0-9]+$/)) {
|
|
||||||
res.host = "0.0.0.0";
|
|
||||||
res.port = listen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_config_file_path() {
|
function get_config_file_path() {
|
||||||
let u = cursor();
|
let u = cursor();
|
||||||
let p = u ? u.get("honk", "config", "config_file") : null;
|
let p = null;
|
||||||
|
if (u) {
|
||||||
|
u.load("honk");
|
||||||
|
p = u.get("honk", "config", "config_file");
|
||||||
|
}
|
||||||
return p || "/etc/honk/config.dae";
|
return p || "/etc/honk/config.dae";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,21 +129,18 @@ function get_api_config() {
|
|||||||
let api_file = get_api_file_path();
|
let api_file = get_api_file_path();
|
||||||
let content = readfile(api_file) || "";
|
let content = readfile(api_file) || "";
|
||||||
let clean = strip_dae_comments(content);
|
let clean = strip_dae_comments(content);
|
||||||
let parsed_clash = parse_clash_api(clean);
|
|
||||||
let parsed_native = parse_native_api(clean);
|
let parsed_native = parse_native_api(clean);
|
||||||
|
|
||||||
if (!parsed_clash && !parsed_native) {
|
if (!parsed_native) {
|
||||||
let config_file = get_config_file_path();
|
let config_file = get_config_file_path();
|
||||||
let legacy_content = readfile(config_file) || "";
|
let legacy_content = readfile(config_file) || "";
|
||||||
let legacy_clean = strip_dae_comments(legacy_content);
|
let legacy_clean = strip_dae_comments(legacy_content);
|
||||||
let leg_clash = parse_clash_api(legacy_clean);
|
|
||||||
let leg_native = parse_native_api(legacy_clean);
|
let leg_native = parse_native_api(legacy_clean);
|
||||||
if (leg_clash || leg_native) {
|
if (leg_native) {
|
||||||
return {
|
return {
|
||||||
file: config_file,
|
file: config_file,
|
||||||
content: legacy_content,
|
content: legacy_content,
|
||||||
clean: legacy_clean,
|
clean: legacy_clean,
|
||||||
parsed_clash: leg_clash,
|
|
||||||
parsed_native: leg_native,
|
parsed_native: leg_native,
|
||||||
is_legacy: true
|
is_legacy: true
|
||||||
};
|
};
|
||||||
@@ -169,7 +151,6 @@ function get_api_config() {
|
|||||||
file: api_file,
|
file: api_file,
|
||||||
content: content,
|
content: content,
|
||||||
clean: clean,
|
clean: clean,
|
||||||
parsed_clash: parsed_clash,
|
|
||||||
parsed_native: parsed_native,
|
parsed_native: parsed_native,
|
||||||
is_legacy: false
|
is_legacy: false
|
||||||
};
|
};
|
||||||
@@ -225,9 +206,9 @@ function get_dashboard_info(req) {
|
|||||||
let u = cursor();
|
let u = cursor();
|
||||||
let requested_type = (req && req.args) ? req.args.type : null;
|
let requested_type = (req && req.args) ? req.args.type : null;
|
||||||
if (!requested_type && u) {
|
if (!requested_type && u) {
|
||||||
|
u.load("honk");
|
||||||
requested_type = u.get("honk", "config", "dashboard");
|
requested_type = u.get("honk", "config", "dashboard");
|
||||||
if (!requested_type) {
|
if (!requested_type) {
|
||||||
u.load("honk");
|
|
||||||
u.foreach("honk", "honk", function(s) {
|
u.foreach("honk", "honk", function(s) {
|
||||||
if (s.dashboard) requested_type = s.dashboard;
|
if (s.dashboard) requested_type = s.dashboard;
|
||||||
});
|
});
|
||||||
@@ -236,16 +217,8 @@ function get_dashboard_info(req) {
|
|||||||
if (!requested_type) requested_type = "none";
|
if (!requested_type) requested_type = "none";
|
||||||
|
|
||||||
let api_cfg = get_api_config();
|
let api_cfg = get_api_config();
|
||||||
if (!api_cfg.is_legacy) {
|
|
||||||
clean_legacy_api_from_config(get_config_file_path());
|
|
||||||
}
|
|
||||||
let parsed_clash = api_cfg.parsed_clash;
|
|
||||||
let parsed_native = api_cfg.parsed_native;
|
let parsed_native = api_cfg.parsed_native;
|
||||||
|
let running = !!get_honk_pid();
|
||||||
let p = popen("pidof honk-core 2>/dev/null");
|
|
||||||
let pids = p ? p.read("all") : "";
|
|
||||||
if (p) p.close();
|
|
||||||
let running = !!match(pids, /[0-9]+/);
|
|
||||||
|
|
||||||
let res = {
|
let res = {
|
||||||
dashboard_type: requested_type,
|
dashboard_type: requested_type,
|
||||||
@@ -260,17 +233,16 @@ function get_dashboard_info(req) {
|
|||||||
external_ui: "",
|
external_ui: "",
|
||||||
geosite_download_url: "",
|
geosite_download_url: "",
|
||||||
geoip_download_url: "",
|
geoip_download_url: "",
|
||||||
default_mode: "Rule",
|
default_mode: "Rule"
|
||||||
has_clash_api: !!parsed_clash,
|
|
||||||
has_native_api: !!(parsed_native && parsed_native.enabled)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requested_type == "none") {
|
if (requested_type == "none") {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target_default_ui = (requested_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona";
|
let target_default_ui = get_dashboard_dir(requested_type);
|
||||||
let other_default_ui = (requested_type == "zashboard") ? "/etc/honk/doona" : "/etc/honk/zashboard";
|
let other_type = (requested_type == "zashboard") ? "doona" : "zashboard";
|
||||||
|
let other_default_ui = get_dashboard_dir(other_type);
|
||||||
|
|
||||||
if (parsed_native && parsed_native.enabled) {
|
if (parsed_native && parsed_native.enabled) {
|
||||||
res.external_controller = parsed_native.listen;
|
res.external_controller = parsed_native.listen;
|
||||||
@@ -286,14 +258,6 @@ function get_dashboard_info(req) {
|
|||||||
} else {
|
} else {
|
||||||
res.configured = false;
|
res.configured = false;
|
||||||
}
|
}
|
||||||
} else if (parsed_clash) {
|
|
||||||
res.configured = false;
|
|
||||||
res.external_controller = parsed_clash.external_controller;
|
|
||||||
res.host = parsed_clash.host;
|
|
||||||
res.port = parsed_clash.port || "9090";
|
|
||||||
res.secret = parsed_clash.secret;
|
|
||||||
res.external_ui = parsed_clash.external_ui;
|
|
||||||
res.default_mode = parsed_clash.default_mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.external_ui) {
|
if (res.external_ui) {
|
||||||
@@ -306,25 +270,35 @@ function get_dashboard_info(req) {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function download_dashboard(req) {
|
function download_dashboard(req) {
|
||||||
let requested_type = req.args ? req.args.type : null;
|
let requested_type = (req && req.args) ? req.args.type : null;
|
||||||
let url = req.args ? req.args.url : null;
|
if (!requested_type) {
|
||||||
if (!url) {
|
let u = cursor();
|
||||||
if (requested_type == "doona") {
|
if (u) {
|
||||||
url = "https://github.com/Zakkaus/doona/releases/download/v0.1.0-beta.8/doona-0.1.0-beta.8.tar.gz";
|
u.load("honk");
|
||||||
} else {
|
requested_type = u.get("honk", "config", "dashboard");
|
||||||
url = "https://github.com/Zephyruso/zashboard/releases/latest/download/dist-no-fonts.zip";
|
if (!requested_type) {
|
||||||
|
u.foreach("honk", "honk", function(s) {
|
||||||
|
if (s.dashboard) requested_type = s.dashboard;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!requested_type || requested_type == "none") {
|
||||||
|
requested_type = "doona";
|
||||||
|
}
|
||||||
|
|
||||||
if (!match(url, /^https?:\/\//) || match(url, /[ \t\r\n'"`]/)) {
|
let url = (req && req.args && req.args.url) ? req.args.url : "";
|
||||||
|
if (url != "" && (!match(url, /^https?:\/\//) || match(url, /[ \t\r\n'"`]/))) {
|
||||||
return { success: false, message: "Invalid URL" };
|
return { success: false, message: "Invalid URL" };
|
||||||
}
|
}
|
||||||
|
|
||||||
let api_cfg = get_api_config();
|
let api_cfg = get_api_config();
|
||||||
let parsed_native = api_cfg.parsed_native;
|
let parsed_native = api_cfg.parsed_native;
|
||||||
let other_default_ui = (requested_type == "zashboard") ? "/etc/honk/doona" : "/etc/honk/zashboard";
|
let other_type = (requested_type == "zashboard") ? "doona" : "zashboard";
|
||||||
let target_dir = (requested_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona";
|
let other_default_ui = get_dashboard_dir(other_type);
|
||||||
|
let target_dir = get_dashboard_dir(requested_type) || "/etc/honk/doona";
|
||||||
if (parsed_native && parsed_native.ui && parsed_native.ui != other_default_ui) {
|
if (parsed_native && parsed_native.ui && parsed_native.ui != other_default_ui) {
|
||||||
target_dir = parsed_native.ui;
|
target_dir = parsed_native.ui;
|
||||||
}
|
}
|
||||||
@@ -354,6 +328,18 @@ function switch_dashboard_api(target_type) {
|
|||||||
|
|
||||||
system("mkdir -p /etc/honk/config.d");
|
system("mkdir -p /etc/honk/config.d");
|
||||||
|
|
||||||
|
// 2. Persist UCI dashboard option to keep UCI and api.dae in sync
|
||||||
|
let u = cursor();
|
||||||
|
if (u) {
|
||||||
|
u.load("honk");
|
||||||
|
let sid = "config";
|
||||||
|
if (!u.get("honk", sid)) {
|
||||||
|
u.foreach("honk", "honk", function(s) { sid = s[".name"]; });
|
||||||
|
}
|
||||||
|
u.set("honk", sid, "dashboard", target_type || "none");
|
||||||
|
u.commit("honk");
|
||||||
|
}
|
||||||
|
|
||||||
if (target_type == "none" || !target_type) {
|
if (target_type == "none" || !target_type) {
|
||||||
let cur = readfile(api_file);
|
let cur = readfile(api_file);
|
||||||
if (cur && trim(cur) != "") {
|
if (cur && trim(cur) != "") {
|
||||||
@@ -366,14 +352,12 @@ function switch_dashboard_api(target_type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let api_cfg = get_api_config();
|
let api_cfg = get_api_config();
|
||||||
let parsed_clash = api_cfg.parsed_clash;
|
|
||||||
let parsed_native = api_cfg.parsed_native;
|
let parsed_native = api_cfg.parsed_native;
|
||||||
|
let target_ui = get_dashboard_dir(target_type);
|
||||||
let target_ui = (target_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona";
|
|
||||||
|
|
||||||
// Check if already correctly configured in api.dae
|
// Check if already correctly configured in api.dae
|
||||||
if (!api_cfg.is_legacy) {
|
if (!api_cfg.is_legacy) {
|
||||||
if (parsed_native && parsed_native.enabled && !parsed_clash &&
|
if (parsed_native && parsed_native.enabled &&
|
||||||
parsed_native.listen &&
|
parsed_native.listen &&
|
||||||
parsed_native.config_write &&
|
parsed_native.config_write &&
|
||||||
parsed_native.geosite_download_url && parsed_native.geoip_download_url &&
|
parsed_native.geosite_download_url && parsed_native.geoip_download_url &&
|
||||||
@@ -429,11 +413,7 @@ return {
|
|||||||
"luci.honk": {
|
"luci.honk": {
|
||||||
status: {
|
status: {
|
||||||
call: function(req) {
|
call: function(req) {
|
||||||
let p = popen("pidof honk-core 2>/dev/null");
|
let pid = get_honk_pid();
|
||||||
let pids = p ? p.read("all") : "";
|
|
||||||
if (p) p.close();
|
|
||||||
let m = match(pids, /([0-9]+)/);
|
|
||||||
let pid = m ? m[1] : null;
|
|
||||||
let running = !!pid;
|
let running = !!pid;
|
||||||
let memory = null;
|
let memory = null;
|
||||||
if (running) {
|
if (running) {
|
||||||
@@ -484,20 +464,11 @@ return {
|
|||||||
call: get_dashboard_info
|
call: get_dashboard_info
|
||||||
},
|
},
|
||||||
|
|
||||||
get_zashboard_info: {
|
|
||||||
call: get_dashboard_info
|
|
||||||
},
|
|
||||||
|
|
||||||
download_dashboard: {
|
download_dashboard: {
|
||||||
args: { url: "string", type: "string" },
|
args: { url: "string", type: "string" },
|
||||||
call: download_dashboard
|
call: download_dashboard
|
||||||
},
|
},
|
||||||
|
|
||||||
download_zashboard: {
|
|
||||||
args: { url: "string", type: "string" },
|
|
||||||
call: download_dashboard
|
|
||||||
},
|
|
||||||
|
|
||||||
download_status: {
|
download_status: {
|
||||||
call: function(req) {
|
call: function(req) {
|
||||||
let status_raw = readfile("/tmp/honk_dashboard_download.status") || "IDLE";
|
let status_raw = readfile("/tmp/honk_dashboard_download.status") || "IDLE";
|
||||||
@@ -508,18 +479,6 @@ return {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
enable_clash_api: {
|
|
||||||
call: function(req) {
|
|
||||||
return switch_dashboard_api("zashboard");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
enable_native_api: {
|
|
||||||
call: function(req) {
|
|
||||||
return switch_dashboard_api("doona");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
switch_dashboard_api: {
|
switch_dashboard_api: {
|
||||||
args: { type: "string" },
|
args: { type: "string" },
|
||||||
call: function(req) {
|
call: function(req) {
|
||||||
|
|||||||
@@ -76,9 +76,6 @@ var callHonkDownloadDashboard = rpc.declare({
|
|||||||
expect: { }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
var callHonkZashboardInfo = callHonkDashboardInfo;
|
|
||||||
var callHonkDownloadZashboard = callHonkDownloadDashboard;
|
|
||||||
|
|
||||||
var callHonkDownloadStatus = rpc.declare({
|
var callHonkDownloadStatus = rpc.declare({
|
||||||
object: 'luci.honk',
|
object: 'luci.honk',
|
||||||
method: 'download_status',
|
method: 'download_status',
|
||||||
@@ -92,6 +89,13 @@ var callHonkSwitchDashboardApi = rpc.declare({
|
|||||||
expect: { }
|
expect: { }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var callUciGet = rpc.declare({
|
||||||
|
object: 'uci',
|
||||||
|
method: 'get',
|
||||||
|
params: [ 'config', 'section', 'option' ],
|
||||||
|
expect: { value: '' }
|
||||||
|
});
|
||||||
|
|
||||||
function readFile(path) {
|
function readFile(path) {
|
||||||
if (fs.read_direct) {
|
if (fs.read_direct) {
|
||||||
return fs.read_direct(path).catch(function() {
|
return fs.read_direct(path).catch(function() {
|
||||||
@@ -173,16 +177,23 @@ function ensureEditorStyles() {
|
|||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _cmPromise = null;
|
||||||
|
|
||||||
function ensureCodeMirror() {
|
function ensureCodeMirror() {
|
||||||
|
if (_cmPromise) {
|
||||||
|
return _cmPromise;
|
||||||
|
}
|
||||||
|
|
||||||
loadStyle(L.resource('honk/lib/codemirror.css'));
|
loadStyle(L.resource('honk/lib/codemirror.css'));
|
||||||
loadStyle(L.resource('honk/addon/fold/foldgutter.css'));
|
loadStyle(L.resource('honk/addon/fold/foldgutter.css'));
|
||||||
ensureEditorStyles();
|
ensureEditorStyles();
|
||||||
|
|
||||||
if (window.CodeMirror && window.CodeMirror.modes && window.CodeMirror.modes.dae) {
|
if (window.CodeMirror && window.CodeMirror.modes && window.CodeMirror.modes.dae) {
|
||||||
return Promise.resolve(window.CodeMirror);
|
_cmPromise = Promise.resolve(window.CodeMirror);
|
||||||
|
return _cmPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadScript(L.resource('honk/lib/codemirror.js'))
|
_cmPromise = loadScript(L.resource('honk/lib/codemirror.js'))
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
loadScript(L.resource('honk/addon/edit/matchbrackets.js')),
|
loadScript(L.resource('honk/addon/edit/matchbrackets.js')),
|
||||||
@@ -195,7 +206,12 @@ function ensureCodeMirror() {
|
|||||||
})
|
})
|
||||||
.then(function() {
|
.then(function() {
|
||||||
return window.CodeMirror;
|
return window.CodeMirror;
|
||||||
|
}).catch(function(err) {
|
||||||
|
_cmPromise = null;
|
||||||
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return _cmPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatEditor(ed) {
|
function formatEditor(ed) {
|
||||||
@@ -442,60 +458,57 @@ function createConfigFileView(filePath, mapTitle, mapDesc, fieldTitle, successMs
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Advanced-only tabs: these paths are hidden when advanced=0.
|
// Tab visibility control:
|
||||||
// This runs on every honk page render and reads live UCI values,
|
// - dns, node, route are hidden when advanced=0
|
||||||
// bypassing LuCI's sessionStorage menu cache entirely.
|
// - api is hidden when dashboard=none
|
||||||
var ADVANCED_TAB_PATHS = ['/honk/dns', '/honk/node', '/honk/route'];
|
function applyTabVisibility() {
|
||||||
|
return Promise.all([
|
||||||
|
L.resolveDefault(callUciGet('honk', 'config', 'advanced'), '0'),
|
||||||
|
L.resolveDefault(callUciGet('honk', 'config', 'dashboard'), 'none')
|
||||||
|
]).then(function(res) {
|
||||||
|
var isAdvanced = (res[0] === '1');
|
||||||
|
var dashType = res[1] || 'none';
|
||||||
|
|
||||||
function applyAdvancedTabVisibility() {
|
var hiddenTabs = [];
|
||||||
// Use ubus directly to read the committed UCI value (not the in-memory
|
|
||||||
// JS UCI module, which may have pending unsaved changes).
|
|
||||||
return L.resolveDefault(
|
|
||||||
rpc.declare({
|
|
||||||
object: 'uci',
|
|
||||||
method: 'get',
|
|
||||||
params: ['config', 'section', 'option'],
|
|
||||||
expect: { value: '' }
|
|
||||||
})('honk', 'config', 'advanced'),
|
|
||||||
''
|
|
||||||
).then(function(val) {
|
|
||||||
var isAdvanced = (val === '1');
|
|
||||||
applyTabCss(isAdvanced);
|
|
||||||
if (!isAdvanced) {
|
if (!isAdvanced) {
|
||||||
var isAdvPage = (window.L && L.env && Array.isArray(L.env.dispatchpath) && ['dns', 'node', 'route'].indexOf(L.env.dispatchpath[3]) !== -1) ||
|
hiddenTabs.push('dns', 'node', 'route');
|
||||||
ADVANCED_TAB_PATHS.some(function(p) { return window.location.pathname.replace(/\/+$/, '').endsWith(p); });
|
}
|
||||||
if (isAdvPage) {
|
if (dashType === 'none') {
|
||||||
window.location.href = L.url('admin/services/honk/global');
|
hiddenTabs.push('api');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyTabCss(hiddenTabs);
|
||||||
|
|
||||||
|
var currentTab = (window.L && L.env && Array.isArray(L.env.dispatchpath)) ? L.env.dispatchpath[3] : '';
|
||||||
|
if (!currentTab) {
|
||||||
|
var m = window.location.pathname.match(/\/honk\/([a-z0-9_-]+)/);
|
||||||
|
if (m) currentTab = m[1];
|
||||||
|
}
|
||||||
|
if (currentTab && hiddenTabs.indexOf(currentTab) !== -1) {
|
||||||
|
window.location.href = L.url('admin/services/honk/global');
|
||||||
}
|
}
|
||||||
}).catch(function() {
|
}).catch(function() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyTabCss(isAdvanced) {
|
var applyAdvancedTabVisibility = applyTabVisibility;
|
||||||
// Inject a style element that hides advanced-only tab items and links.
|
|
||||||
// This is idempotent and works seamlessly across themes (Bootstrap, Aurora, etc.).
|
function applyTabCss(hiddenTabs) {
|
||||||
var styleId = 'honk-adv-tab-style';
|
var styleId = 'honk-tab-visibility-style';
|
||||||
var existing = document.getElementById(styleId);
|
var existing = document.getElementById(styleId);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
existing = document.createElement('style');
|
existing = document.createElement('style');
|
||||||
existing.id = styleId;
|
existing.id = styleId;
|
||||||
document.head.appendChild(existing);
|
document.head.appendChild(existing);
|
||||||
}
|
}
|
||||||
if (isAdvanced) {
|
if (!hiddenTabs || hiddenTabs.length === 0) {
|
||||||
existing.textContent = '';
|
existing.textContent = '';
|
||||||
} else {
|
} else {
|
||||||
existing.textContent = [
|
existing.textContent = hiddenTabs.map(function(tab) {
|
||||||
'#tabmenu .tabmenu-item-dns,',
|
return '#tabmenu .tabmenu-item-' + tab + ',\n' +
|
||||||
'#tabmenu .tabmenu-item-node,',
|
'.tabmenu-item-' + tab + ',\n' +
|
||||||
'#tabmenu .tabmenu-item-route,',
|
'#tabmenu a[href$="/honk/' + tab + '"]';
|
||||||
'.tabmenu-item-dns,',
|
}).join(',\n') + ' { display: none !important; }';
|
||||||
'.tabmenu-item-node,',
|
|
||||||
'.tabmenu-item-route,',
|
|
||||||
'#tabmenu a[href$="/honk/dns"],',
|
|
||||||
'#tabmenu a[href$="/honk/node"],',
|
|
||||||
'#tabmenu a[href$="/honk/route"] { display: none !important; }'
|
|
||||||
].join('\n');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,26 +570,22 @@ function renderStatusHeader() {
|
|||||||
return callHonkStatus().then(updateStatus);
|
return callHonkStatus().then(updateStatus);
|
||||||
}, 3);
|
}, 3);
|
||||||
|
|
||||||
// Apply tab visibility based on live UCI advanced value.
|
applyTabVisibility();
|
||||||
// This runs asynchronously after render; the CSS injection is fast enough
|
|
||||||
// that tabs flicker is imperceptible (tabs hide before user can click them).
|
|
||||||
applyAdvancedTabVisibility();
|
|
||||||
|
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return baseclass.extend({
|
return baseclass.extend({
|
||||||
applyAdvancedTabVisibility: applyAdvancedTabVisibility,
|
applyTabVisibility: applyTabVisibility,
|
||||||
|
applyAdvancedTabVisibility: applyTabVisibility,
|
||||||
callHonkStatus: callHonkStatus,
|
callHonkStatus: callHonkStatus,
|
||||||
callHonkReload: callHonkReload,
|
callHonkReload: callHonkReload,
|
||||||
callHonkRestart: callHonkRestart,
|
callHonkRestart: callHonkRestart,
|
||||||
callHonkGetLog: callHonkGetLog,
|
callHonkGetLog: callHonkGetLog,
|
||||||
callHonkClearLog: callHonkClearLog,
|
callHonkClearLog: callHonkClearLog,
|
||||||
callHonkDashboardInfo: callHonkDashboardInfo,
|
callHonkDashboardInfo: callHonkDashboardInfo,
|
||||||
callHonkZashboardInfo: callHonkDashboardInfo,
|
|
||||||
callHonkDownloadDashboard: callHonkDownloadDashboard,
|
callHonkDownloadDashboard: callHonkDownloadDashboard,
|
||||||
callHonkDownloadZashboard: callHonkDownloadDashboard,
|
|
||||||
callHonkDownloadStatus: callHonkDownloadStatus,
|
callHonkDownloadStatus: callHonkDownloadStatus,
|
||||||
callHonkSwitchDashboardApi: callHonkSwitchDashboardApi,
|
callHonkSwitchDashboardApi: callHonkSwitchDashboardApi,
|
||||||
readFile: readFile,
|
readFile: readFile,
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
if (honk && honk.applyAdvancedTabVisibility) {
|
var applyTabs = (honk && (honk.applyTabVisibility || honk.applyAdvancedTabVisibility));
|
||||||
honk.applyAdvancedTabVisibility();
|
if (applyTabs) {
|
||||||
|
applyTabs();
|
||||||
}
|
}
|
||||||
var sec = (uci.sections('honk', 'honk')[0] || {});
|
var sec = (uci.sections('honk', 'honk')[0] || {});
|
||||||
var sid = sec['.name'] || 'config';
|
var sid = sec['.name'] || 'config';
|
||||||
@@ -85,13 +86,12 @@ return view.extend({
|
|||||||
'style': 'margin-right: 10px;',
|
'style': 'margin-right: 10px;',
|
||||||
'change': function(ev) {
|
'change': function(ev) {
|
||||||
var newType = ev.target.value;
|
var newType = ev.target.value;
|
||||||
uci.set('honk', sid, 'dashboard', newType);
|
sel.disabled = true;
|
||||||
uci.save().then(function() {
|
honk.callHonkSwitchDashboardApi(newType).then(function() {
|
||||||
return uci.apply();
|
|
||||||
}).then(function() {
|
|
||||||
return honk.callHonkSwitchDashboardApi(newType);
|
|
||||||
}).then(function() {
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
}).catch(function(err) {
|
||||||
|
sel.disabled = false;
|
||||||
|
honk.showNotification(null, E('p', _('Failed to switch dashboard:') + ' ' + (err.message || err)), 'error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ return view.extend({
|
|||||||
var newDash = uci.get('honk', sid, 'dashboard') || 'none';
|
var newDash = uci.get('honk', sid, 'dashboard') || 'none';
|
||||||
if (newDash !== oldDash) {
|
if (newDash !== oldDash) {
|
||||||
return honk.callHonkSwitchDashboardApi(newDash);
|
return honk.callHonkSwitchDashboardApi(newDash);
|
||||||
|
} else {
|
||||||
|
return honk.callHonkReload();
|
||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return ui.changes.apply(mode == '0');
|
return ui.changes.apply(mode == '0');
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ return view.extend({
|
|||||||
handleReset: null,
|
handleReset: null,
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
if (honk && honk.applyAdvancedTabVisibility) {
|
var applyTabs = (honk && (honk.applyTabVisibility || honk.applyAdvancedTabVisibility));
|
||||||
honk.applyAdvancedTabVisibility();
|
if (applyTabs) {
|
||||||
|
applyTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
var scrolled = false;
|
var scrolled = false;
|
||||||
@@ -35,12 +36,10 @@ return view.extend({
|
|||||||
logTextarea.value = '';
|
logTextarea.value = '';
|
||||||
logTextarea.scrollTop = 0;
|
logTextarea.scrollTop = 0;
|
||||||
scrolled = false;
|
scrolled = false;
|
||||||
var notify = (honk && honk.showNotification) ? honk.showNotification : ui.addNotification;
|
honk.showNotification(null, E('p', _('Logs cleared successfully.')), 'info');
|
||||||
notify(null, E('p', _('Logs cleared successfully.')), 'info');
|
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
btnClear.disabled = false;
|
btnClear.disabled = false;
|
||||||
var notify = (honk && honk.showNotification) ? honk.showNotification : ui.addNotification;
|
honk.showNotification(null, E('p', _('Failed to clear logs:') + ' ' + (err.message || err)), 'error');
|
||||||
notify(null, E('p', _('Failed to clear logs:') + ' ' + (err.message || err)), 'error');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, _('Clear logs'));
|
}, _('Clear logs'));
|
||||||
|
|||||||
Reference in new issue
Block a user