diff --git a/luci-app-honk/Makefile b/luci-app-honk/Makefile index b9675c072..508751979 100644 --- a/luci-app-honk/Makefile +++ b/luci-app-honk/Makefile @@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-honk -PKG_VERSION:=1.1.3 -PKG_RELEASE:=5 +PKG_VERSION:=1.1.4 +PKG_RELEASE:=6 LUCI_TITLE:=LuCI support for honk LUCI_PKGARCH:=all diff --git a/luci-app-honk/po/zh_Hans/honk.po b/luci-app-honk/po/zh_Hans/honk.po index 95094efe9..48e5477e2 100644 --- a/luci-app-honk/po/zh_Hans/honk.po +++ b/luci-app-honk/po/zh_Hans/honk.po @@ -324,3 +324,6 @@ msgstr "面板未启用" msgid "Dashboard type is currently set to \"None\". Web dashboard and API listener are disabled." msgstr "当前面板类型为“无”,Web 管理面板和 Native API 监听已禁用。" + +msgid "Failed to switch dashboard:" +msgstr "切换面板失败:" diff --git a/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json b/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json index 75126cc89..1123b56a7 100644 --- a/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json +++ b/luci-app-honk/root/usr/share/rpcd/acl.d/luci-app-honk.json @@ -9,11 +9,6 @@ "file": [ "/etc/honk/config.dae", "/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/zashboard/*" ] @@ -26,11 +21,6 @@ "file": [ "/etc/honk/config.dae", "/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/zashboard/*" ] diff --git a/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc b/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc index 1c1af5178..70c6a7a15 100644 --- a/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc +++ b/luci-app-honk/root/usr/share/rpcd/ucode/honk.uc @@ -5,6 +5,45 @@ import { readfile, writefile, popen, stat } from 'fs'; 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) { if (!content) return ""; let lines = split(content, /[\r\n]+/); @@ -40,48 +79,6 @@ function strip_dae_comments(content) { 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) { let api_m = match(clean_content, /native_api\s*\{([^}]+)\}/); if (!api_m) return null; @@ -107,32 +104,20 @@ function parse_native_api(clean_content) { port: "" }; - let listen = res.listen; - if (listen) { - let m_v6 = match(listen, /^\[([^\]]+)\]:([0-9]+)$/); - 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; - } - } + let hp = parse_host_port(res.listen); + res.host = hp.host; + res.port = hp.port; return res; } function get_config_file_path() { 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"; } @@ -144,21 +129,18 @@ function get_api_config() { let api_file = get_api_file_path(); let content = readfile(api_file) || ""; let clean = strip_dae_comments(content); - let parsed_clash = parse_clash_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 legacy_content = readfile(config_file) || ""; let legacy_clean = strip_dae_comments(legacy_content); - let leg_clash = parse_clash_api(legacy_clean); let leg_native = parse_native_api(legacy_clean); - if (leg_clash || leg_native) { + if (leg_native) { return { file: config_file, content: legacy_content, clean: legacy_clean, - parsed_clash: leg_clash, parsed_native: leg_native, is_legacy: true }; @@ -169,7 +151,6 @@ function get_api_config() { file: api_file, content: content, clean: clean, - parsed_clash: parsed_clash, parsed_native: parsed_native, is_legacy: false }; @@ -225,9 +206,9 @@ function get_dashboard_info(req) { let u = cursor(); let requested_type = (req && req.args) ? req.args.type : null; if (!requested_type && u) { + u.load("honk"); requested_type = u.get("honk", "config", "dashboard"); if (!requested_type) { - u.load("honk"); u.foreach("honk", "honk", function(s) { if (s.dashboard) requested_type = s.dashboard; }); @@ -236,16 +217,8 @@ function get_dashboard_info(req) { if (!requested_type) requested_type = "none"; 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 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 running = !!get_honk_pid(); let res = { dashboard_type: requested_type, @@ -260,17 +233,16 @@ function get_dashboard_info(req) { external_ui: "", geosite_download_url: "", geoip_download_url: "", - default_mode: "Rule", - has_clash_api: !!parsed_clash, - has_native_api: !!(parsed_native && parsed_native.enabled) + default_mode: "Rule" }; if (requested_type == "none") { return res; } - let target_default_ui = (requested_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona"; - let other_default_ui = (requested_type == "zashboard") ? "/etc/honk/doona" : "/etc/honk/zashboard"; + let target_default_ui = get_dashboard_dir(requested_type); + let other_type = (requested_type == "zashboard") ? "doona" : "zashboard"; + let other_default_ui = get_dashboard_dir(other_type); if (parsed_native && parsed_native.enabled) { res.external_controller = parsed_native.listen; @@ -286,14 +258,6 @@ function get_dashboard_info(req) { } else { 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) { @@ -306,25 +270,35 @@ function get_dashboard_info(req) { return res; } + function download_dashboard(req) { - let requested_type = req.args ? req.args.type : null; - let url = req.args ? req.args.url : null; - if (!url) { - if (requested_type == "doona") { - url = "https://github.com/Zakkaus/doona/releases/download/v0.1.0-beta.8/doona-0.1.0-beta.8.tar.gz"; - } else { - url = "https://github.com/Zephyruso/zashboard/releases/latest/download/dist-no-fonts.zip"; + let requested_type = (req && req.args) ? req.args.type : null; + if (!requested_type) { + let u = cursor(); + if (u) { + u.load("honk"); + requested_type = u.get("honk", "config", "dashboard"); + 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" }; } let api_cfg = get_api_config(); let parsed_native = api_cfg.parsed_native; - let other_default_ui = (requested_type == "zashboard") ? "/etc/honk/doona" : "/etc/honk/zashboard"; - let target_dir = (requested_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona"; + let other_type = (requested_type == "zashboard") ? "doona" : "zashboard"; + 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) { target_dir = parsed_native.ui; } @@ -354,6 +328,18 @@ function switch_dashboard_api(target_type) { 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) { let cur = readfile(api_file); if (cur && trim(cur) != "") { @@ -366,14 +352,12 @@ function switch_dashboard_api(target_type) { } let api_cfg = get_api_config(); - let parsed_clash = api_cfg.parsed_clash; let parsed_native = api_cfg.parsed_native; - - let target_ui = (target_type == "zashboard") ? "/etc/honk/zashboard" : "/etc/honk/doona"; + let target_ui = get_dashboard_dir(target_type); // Check if already correctly configured in api.dae if (!api_cfg.is_legacy) { - if (parsed_native && parsed_native.enabled && !parsed_clash && + if (parsed_native && parsed_native.enabled && parsed_native.listen && parsed_native.config_write && parsed_native.geosite_download_url && parsed_native.geoip_download_url && @@ -429,11 +413,7 @@ return { "luci.honk": { status: { call: function(req) { - 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]+)/); - let pid = m ? m[1] : null; + let pid = get_honk_pid(); let running = !!pid; let memory = null; if (running) { @@ -484,20 +464,11 @@ return { call: get_dashboard_info }, - get_zashboard_info: { - call: get_dashboard_info - }, - download_dashboard: { args: { url: "string", type: "string" }, call: download_dashboard }, - download_zashboard: { - args: { url: "string", type: "string" }, - call: download_dashboard - }, - download_status: { call: function(req) { 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: { args: { type: "string" }, call: function(req) { diff --git a/luci-app-honk/root/www/luci-static/resources/honk/common.js b/luci-app-honk/root/www/luci-static/resources/honk/common.js index ee9321b2e..20df325be 100644 --- a/luci-app-honk/root/www/luci-static/resources/honk/common.js +++ b/luci-app-honk/root/www/luci-static/resources/honk/common.js @@ -76,9 +76,6 @@ var callHonkDownloadDashboard = rpc.declare({ expect: { } }); -var callHonkZashboardInfo = callHonkDashboardInfo; -var callHonkDownloadZashboard = callHonkDownloadDashboard; - var callHonkDownloadStatus = rpc.declare({ object: 'luci.honk', method: 'download_status', @@ -92,6 +89,13 @@ var callHonkSwitchDashboardApi = rpc.declare({ expect: { } }); +var callUciGet = rpc.declare({ + object: 'uci', + method: 'get', + params: [ 'config', 'section', 'option' ], + expect: { value: '' } +}); + function readFile(path) { if (fs.read_direct) { return fs.read_direct(path).catch(function() { @@ -173,16 +177,23 @@ function ensureEditorStyles() { document.head.appendChild(style); } +var _cmPromise = null; + function ensureCodeMirror() { + if (_cmPromise) { + return _cmPromise; + } + loadStyle(L.resource('honk/lib/codemirror.css')); loadStyle(L.resource('honk/addon/fold/foldgutter.css')); ensureEditorStyles(); 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() { return Promise.all([ loadScript(L.resource('honk/addon/edit/matchbrackets.js')), @@ -195,7 +206,12 @@ function ensureCodeMirror() { }) .then(function() { return window.CodeMirror; + }).catch(function(err) { + _cmPromise = null; + throw err; }); + + return _cmPromise; } function formatEditor(ed) { @@ -442,60 +458,57 @@ function createConfigFileView(filePath, mapTitle, mapDesc, fieldTitle, successMs } -// Advanced-only tabs: these paths are hidden when advanced=0. -// This runs on every honk page render and reads live UCI values, -// bypassing LuCI's sessionStorage menu cache entirely. -var ADVANCED_TAB_PATHS = ['/honk/dns', '/honk/node', '/honk/route']; +// Tab visibility control: +// - dns, node, route are hidden when advanced=0 +// - api is hidden when dashboard=none +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() { - // 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); + var hiddenTabs = []; if (!isAdvanced) { - var isAdvPage = (window.L && L.env && Array.isArray(L.env.dispatchpath) && ['dns', 'node', 'route'].indexOf(L.env.dispatchpath[3]) !== -1) || - ADVANCED_TAB_PATHS.some(function(p) { return window.location.pathname.replace(/\/+$/, '').endsWith(p); }); - if (isAdvPage) { - window.location.href = L.url('admin/services/honk/global'); - } + hiddenTabs.push('dns', 'node', 'route'); + } + if (dashType === 'none') { + 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() { }); } -function applyTabCss(isAdvanced) { - // Inject a style element that hides advanced-only tab items and links. - // This is idempotent and works seamlessly across themes (Bootstrap, Aurora, etc.). - var styleId = 'honk-adv-tab-style'; +var applyAdvancedTabVisibility = applyTabVisibility; + +function applyTabCss(hiddenTabs) { + var styleId = 'honk-tab-visibility-style'; var existing = document.getElementById(styleId); if (!existing) { existing = document.createElement('style'); existing.id = styleId; document.head.appendChild(existing); } - if (isAdvanced) { + if (!hiddenTabs || hiddenTabs.length === 0) { existing.textContent = ''; } else { - existing.textContent = [ - '#tabmenu .tabmenu-item-dns,', - '#tabmenu .tabmenu-item-node,', - '#tabmenu .tabmenu-item-route,', - '.tabmenu-item-dns,', - '.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'); + existing.textContent = hiddenTabs.map(function(tab) { + return '#tabmenu .tabmenu-item-' + tab + ',\n' + + '.tabmenu-item-' + tab + ',\n' + + '#tabmenu a[href$="/honk/' + tab + '"]'; + }).join(',\n') + ' { display: none !important; }'; } } @@ -557,26 +570,22 @@ function renderStatusHeader() { return callHonkStatus().then(updateStatus); }, 3); - // Apply tab visibility based on live UCI advanced value. - // This runs asynchronously after render; the CSS injection is fast enough - // that tabs flicker is imperceptible (tabs hide before user can click them). - applyAdvancedTabVisibility(); + applyTabVisibility(); return section; } return baseclass.extend({ - applyAdvancedTabVisibility: applyAdvancedTabVisibility, + applyTabVisibility: applyTabVisibility, + applyAdvancedTabVisibility: applyTabVisibility, callHonkStatus: callHonkStatus, callHonkReload: callHonkReload, callHonkRestart: callHonkRestart, callHonkGetLog: callHonkGetLog, callHonkClearLog: callHonkClearLog, callHonkDashboardInfo: callHonkDashboardInfo, - callHonkZashboardInfo: callHonkDashboardInfo, callHonkDownloadDashboard: callHonkDownloadDashboard, - callHonkDownloadZashboard: callHonkDownloadDashboard, callHonkDownloadStatus: callHonkDownloadStatus, callHonkSwitchDashboardApi: callHonkSwitchDashboardApi, readFile: readFile, diff --git a/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js b/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js index cb6297645..b9680e44a 100644 --- a/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js +++ b/luci-app-honk/root/www/luci-static/resources/view/honk/dashboard.js @@ -17,8 +17,9 @@ return view.extend({ }, render: function() { - if (honk && honk.applyAdvancedTabVisibility) { - honk.applyAdvancedTabVisibility(); + var applyTabs = (honk && (honk.applyTabVisibility || honk.applyAdvancedTabVisibility)); + if (applyTabs) { + applyTabs(); } var sec = (uci.sections('honk', 'honk')[0] || {}); var sid = sec['.name'] || 'config'; @@ -85,13 +86,12 @@ return view.extend({ 'style': 'margin-right: 10px;', 'change': function(ev) { var newType = ev.target.value; - uci.set('honk', sid, 'dashboard', newType); - uci.save().then(function() { - return uci.apply(); - }).then(function() { - return honk.callHonkSwitchDashboardApi(newType); - }).then(function() { + sel.disabled = true; + honk.callHonkSwitchDashboardApi(newType).then(function() { window.location.reload(); + }).catch(function(err) { + sel.disabled = false; + honk.showNotification(null, E('p', _('Failed to switch dashboard:') + ' ' + (err.message || err)), 'error'); }); } }); diff --git a/luci-app-honk/root/www/luci-static/resources/view/honk/global.js b/luci-app-honk/root/www/luci-static/resources/view/honk/global.js index f73c61050..f7008e054 100644 --- a/luci-app-honk/root/www/luci-static/resources/view/honk/global.js +++ b/luci-app-honk/root/www/luci-static/resources/view/honk/global.js @@ -62,6 +62,8 @@ return view.extend({ var newDash = uci.get('honk', sid, 'dashboard') || 'none'; if (newDash !== oldDash) { return honk.callHonkSwitchDashboardApi(newDash); + } else { + return honk.callHonkReload(); } }).then(function() { return ui.changes.apply(mode == '0'); diff --git a/luci-app-honk/root/www/luci-static/resources/view/honk/log.js b/luci-app-honk/root/www/luci-static/resources/view/honk/log.js index 9fbca343a..ba0a17c7e 100644 --- a/luci-app-honk/root/www/luci-static/resources/view/honk/log.js +++ b/luci-app-honk/root/www/luci-static/resources/view/honk/log.js @@ -10,8 +10,9 @@ return view.extend({ handleReset: null, render: function() { - if (honk && honk.applyAdvancedTabVisibility) { - honk.applyAdvancedTabVisibility(); + var applyTabs = (honk && (honk.applyTabVisibility || honk.applyAdvancedTabVisibility)); + if (applyTabs) { + applyTabs(); } var scrolled = false; @@ -35,12 +36,10 @@ return view.extend({ logTextarea.value = ''; logTextarea.scrollTop = 0; scrolled = false; - var notify = (honk && honk.showNotification) ? honk.showNotification : ui.addNotification; - notify(null, E('p', _('Logs cleared successfully.')), 'info'); + honk.showNotification(null, E('p', _('Logs cleared successfully.')), 'info'); }).catch(function(err) { btnClear.disabled = false; - var notify = (honk && honk.showNotification) ? honk.showNotification : ui.addNotification; - notify(null, E('p', _('Failed to clear logs:') + ' ' + (err.message || err)), 'error'); + honk.showNotification(null, E('p', _('Failed to clear logs:') + ' ' + (err.message || err)), 'error'); }); } }, _('Clear logs'));