From 81fc75739c13881ba339d02017ec3a5d2a32948d Mon Sep 17 00:00:00 2001 From: Stan Grishin Date: Mon, 28 Aug 2023 21:30:42 +0000 Subject: luci-app-adblock-fast: initial commit * Depends on https://github.com/openwrt/packages/pull/21943 Signed-off-by: Stan Grishin --- .../luci-static/resources/adblock-fast/status.js | 479 +++++++++++++++++++++ .../resources/view/adblock-fast/overview.js | 385 +++++++++++++++++ 2 files changed, 864 insertions(+) create mode 100644 applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js create mode 100644 applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js (limited to 'applications/luci-app-adblock-fast/htdocs') diff --git a/applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js b/applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js new file mode 100644 index 0000000000..901d3d9f26 --- /dev/null +++ b/applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js @@ -0,0 +1,479 @@ +// Copyright MOSSDeF, 2023 Stan Grishin +// This code wouldn't have been possible without help from: +// - [@vsviridov](https://github.com/vsviridov) + +"require ui"; +"require rpc"; +"require form"; +"require baseclass"; + +var pkg = { + get Name() { + return "adblock-fast"; + }, + get URL() { + return "https://docs.openwrt.melmac.net/" + pkg.Name + "/"; + }, +}; + +var getFileUrlFilesizes = rpc.declare({ + object: "luci." + pkg.Name, + method: "getFileUrlFilesizes", + params: ["name", "url"], +}); + +var getInitList = rpc.declare({ + object: "luci." + pkg.Name, + method: "getInitList", + params: ["name"], +}); + +var getInitStatus = rpc.declare({ + object: "luci." + pkg.Name, + method: "getInitStatus", + params: ["name"], +}); + +var getPlatformSupport = rpc.declare({ + object: "luci." + pkg.Name, + method: "getPlatformSupport", + params: ["name"], +}); + +var _setInitAction = rpc.declare({ + object: "luci." + pkg.Name, + method: "setInitAction", + params: ["name", "action"], + expect: { result: false }, +}); + +var RPC = { + listeners: [], + on: function (event, callback) { + var pair = { event: event, callback: callback }; + this.listeners.push(pair); + return function unsubscribe() { + this.listeners = this.listeners.filter(function (listener) { + return listener !== pair; + }); + }.bind(this); + }, + emit: function (event, data) { + this.listeners.forEach(function (listener) { + if (listener.event === event) { + listener.callback(data); + } + }); + }, + setInitAction: function (name, action) { + _setInitAction(name, action).then( + function (result) { + this.emit("setInitAction", result); + }.bind(this) + ); + }, +}; + +var status = baseclass.extend({ + render: function () { + return Promise.all([L.resolveDefault(getInitStatus(pkg.Name), {})]).then( + function (data) { + var reply = { + status: (data[0] && data[0][pkg.Name]) || { + enabled: false, + status: null, + running: null, + version: null, + errors: [], + warnings: [], + force_dns_active: null, + force_dns_ports: [], + entries: null, + dns: null, + outputFile: null, + outputCache: null, + outputGzip: null, + outputFileExists: null, + outputCacheExists: null, + outputGzipExists: null, + leds: [], + }, + }; + var text = ""; + var outputFile = reply.status.outputFile; + var outputCache = reply.status.outputCache; + var statusTable = { + statusNoInstall: _("%s is not installed or not found").format( + pkg.Name + ), + statusStopped: _("Stopped"), + statusStarting: _("Starting"), + statusProcessing: _("Processing lists"), + statusRestarting: _("Restarting"), + statusForceReloading: _("Force Reloading"), + statusDownloading: _("Downloading lists"), + statusError: _("Error"), + statusWarning: _("Warning"), + statusFail: _("Fail"), + statusSuccess: _("Active"), + }; + + var header = E("h2", {}, _("AdBlock-Fast - Status")); + var statusTitle = E( + "label", + { class: "cbi-value-title" }, + _("Service Status") + ); + if (reply.status.version) { + text += _("Version %s").format(reply.status.version) + " - "; + switch (reply.status.status) { + case "statusSuccess": + text += statusTable[reply.status.status] + "."; + text += + "
" + + _("Blocking %s domains (with %s).").format( + reply.status.entries, + reply.status.dns + ); + if (reply.status.outputGzipExists) { + text += "
" + _("Compressed cache file created."); + } + if (reply.status.force_dns_active) { + text += "
" + _("Force DNS ports:"); + reply.status.force_dns_ports.forEach((element) => { + text += " " + element; + }); + text += "."; + } + break; + case "statusStopped": + if (reply.status.enabled) { + text += statusTable[reply.status.status] + "."; + } else { + text += + statusTable[reply.status.status] + + " (" + + _("Disabled") + + ")."; + } + if (reply.status.outputCacheExists) { + text += "
" + _("Cache file found."); + } else if (reply.status.outputGzipExists) { + text += "
" + _("Compressed cache file found."); + } + break; + case "statusRestarting": + case "statusForceReloading": + case "statusDownloading": + case "statusProcessing": + text += statusTable[reply.status.status] + "..."; + break; + default: + text += statusTable[reply.status.status] + "."; + break; + } + } else { + text = _("Not installed or not found"); + } + var statusText = E("div", {}, text); + var statusField = E("div", { class: "cbi-value-field" }, statusText); + var statusDiv = E("div", { class: "cbi-value" }, [ + statusTitle, + statusField, + ]); + + var warningsDiv = []; + if (reply.status.warnings && reply.status.warnings.length) { + var warningTable = { + warningExternalDnsmasqConfig: _( + "Use of external dnsmasq config file detected, please set '%s' option to '%s'" + ).format("dns", "dnsmasq.conf"), + warningMissingRecommendedPackages: _( + "Some recommended packages are missing" + ), + }; + var warningsTitle = E( + "label", + { class: "cbi-value-title" }, + _("Service Warnings") + ); + var text = ""; + reply.status.warnings.forEach((element) => { + text += + warningTable[element.id].format(element.extra || " ") + "
"; + }); + var warningsText = E("div", {}, text); + var warningsField = E( + "div", + { class: "cbi-value-field" }, + warningsText + ); + warningsDiv = E("div", { class: "cbi-value" }, [ + warningsTitle, + warningsField, + ]); + } + + var errorsDiv = []; + if (reply.status.errors && reply.status.errors.length) { + var errorTable = { + errorConfigValidationFail: _( + "Config (%s) validation failure!" + ).format("/etc/config/" + pkg.Name), + errorServiceDisabled: _("%s is currently disabled").format( + pkg.Name + ), + errorNoDnsmasqIpset: _( + "The dnsmasq ipset support is enabled, but dnsmasq is either not installed or installed dnsmasq does not support ipset" + ), + errorNoIpset: _( + "The dnsmasq ipset support is enabled, but ipset is either not installed or installed ipset does not support '%s' type" + ).format("hash:net"), + errorNoDnsmasqNftset: _( + "The dnsmasq nft set support is enabled, but dnsmasq is either not installed or installed dnsmasq does not support nft set" + ), + errorNoNft: _( + "The dnsmasq nft sets support is enabled, but nft is not installed" + ), + errorNoWanGateway: _( + "The %s failed to discover WAN gateway" + ).format(pkg.Name), + errorOutputDirCreate: _("Failed to create directory for %s file"), + errorOutputFileCreate: _("Failed to create '%s' file").format( + outputFile + ), + errorFailDNSReload: _("Failed to restart/reload DNS resolver"), + errorSharedMemory: _("Failed to access shared memory"), + errorSorting: _("Failed to sort data file"), + errorOptimization: _("Failed to optimize data file"), + errorAllowListProcessing: _("Failed to process allow-list"), + errorDataFileFormatting: _("Failed to format data file"), + errorMovingDataFile: _( + "Failed to move temporary data file to '%s'" + ).format(outputFile), + errorCreatingCompressedCache: _( + "Failed to create compressed cache" + ), + errorRemovingTempFiles: _("Failed to remove temporary files"), + errorRestoreCompressedCache: _("Failed to unpack compressed cache"), + errorRestoreCache: _("Failed to move '%s' to '%s'").format( + outputCache, + outputFile + ), + errorOhSnap: _( + "Failed to create block-list or restart DNS resolver" + ), + errorStopping: _("Failed to stop %s").format(pkg.Name), + errorDNSReload: _("Failed to reload/restart DNS resolver"), + errorDownloadingConfigUpdate: _( + "Failed to download Config Update file" + ), + errorDownloadingList: _("Failed to download %s"), + errorParsingConfigUpdate: _("Failed to parse Config Update file"), + errorParsingList: _("Failed to parse"), + errorNoSSLSupport: _("No HTTPS/SSL support on device"), + errorCreatingDirectory: _( + "Failed to create output/cache/gzip file directory" + ), + }; + var errorsTitle = E( + "label", + { class: "cbi-value-title" }, + _("Service Errors") + ); + var text = ""; + reply.status.errors.forEach((element) => { + text += + errorTable[element.id].format(element.extra || " ") + "
"; + }); + text += _("Errors encountered, please check the %sREADME%s!").format( + "', + "
" + ); + var errorsText = E("div", {}, text); + var errorsField = E("div", { class: "cbi-value-field" }, errorsText); + errorsDiv = E("div", { class: "cbi-value" }, [ + errorsTitle, + errorsField, + ]); + } + + var btn_gap = E("span", {}, "  "); + var btn_gap_long = E( + "span", + {}, + "        " + ); + + var btn_start = E( + "button", + { + class: "btn cbi-button cbi-button-apply", + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E( + "p", + { class: "spinning" }, + _("Starting %s service").format(pkg.Name) + ), + ]); + return RPC.setInitAction(pkg.Name, "start"); + }, + }, + _("Start") + ); + + var btn_action = E( + "button", + { + class: "btn cbi-button cbi-button-apply", + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E( + "p", + { class: "spinning" }, + _("Force re-downloading %s block lists").format(pkg.Name) + ), + ]); + return RPC.setInitAction(pkg.Name, "dl"); + }, + }, + _("Force Re-Download") + ); + + var btn_stop = E( + "button", + { + class: "btn cbi-button cbi-button-reset", + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E( + "p", + { class: "spinning" }, + _("Stopping %s service").format(pkg.Name) + ), + ]); + return RPC.setInitAction(pkg.Name, "stop"); + }, + }, + _("Stop") + ); + + var btn_enable = E( + "button", + { + class: "btn cbi-button cbi-button-apply", + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E( + "p", + { class: "spinning" }, + _("Enabling %s service").format(pkg.Name) + ), + ]); + return RPC.setInitAction(pkg.Name, "enable"); + }, + }, + _("Enable") + ); + + var btn_disable = E( + "button", + { + class: "btn cbi-button cbi-button-reset", + disabled: true, + click: function (ev) { + ui.showModal(null, [ + E( + "p", + { class: "spinning" }, + _("Disabling %s service").format(pkg.Name) + ), + ]); + return RPC.setInitAction(pkg.Name, "disable"); + }, + }, + _("Disable") + ); + + if (reply.status.enabled) { + btn_enable.disabled = true; + btn_disable.disabled = false; + switch (reply.status.status) { + case "statusSuccess": + btn_start.disabled = true; + btn_action.disabled = false; + btn_stop.disabled = false; + break; + case "statusStopped": + btn_start.disabled = false; + btn_action.disabled = true; + btn_stop.disabled = true; + break; + default: + btn_start.disabled = false; + btn_action.disabled = true; + btn_stop.disabled = false; + btn_enable.disabled = true; + btn_disable.disabled = true; + break; + } + } else { + btn_start.disabled = true; + btn_action.disabled = true; + btn_stop.disabled = true; + btn_enable.disabled = false; + btn_disable.disabled = true; + } + + var buttonsDiv = []; + var buttonsTitle = E( + "label", + { class: "cbi-value-title" }, + _("Service Control") + ); + var buttonsText = E("div", {}, [ + btn_start, + btn_gap, + btn_action, + btn_gap, + btn_stop, + btn_gap_long, + btn_enable, + btn_gap, + btn_disable, + ]); + var buttonsField = E("div", { class: "cbi-value-field" }, buttonsText); + if (reply.status.version) { + buttonsDiv = E("div", { class: "cbi-value" }, [ + buttonsTitle, + buttonsField, + ]); + } + + return E("div", {}, [ + header, + statusDiv, + warningsDiv, + errorsDiv, + buttonsDiv, + ]); + } + ); + }, +}); + +RPC.on("setInitAction", function (reply) { + ui.hideModal(); + location.reload(); +}); + +return L.Class.extend({ + status: status, + getFileUrlFilesizes: getFileUrlFilesizes, + getPlatformSupport: getPlatformSupport, +}); diff --git a/applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js b/applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js new file mode 100644 index 0000000000..92caf6b35d --- /dev/null +++ b/applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js @@ -0,0 +1,385 @@ +// Copyright 2023 MOSSDeF, Stan Grishin +// This code wouldn't have been possible without help from: +// - [@stokito](https://github.com/stokito) +// - [@vsviridov](https://github.com/vsviridov) + +"use strict"; +"require form"; +"require uci"; +"require view"; +"require adblock-fast.status as adb"; + +var pkg = { + get Name() { + return "adblock-fast"; + }, + get URL() { + return "https://docs.openwrt.melmac.net/" + pkg.Name + "/"; + }, + humanFileSize: function (bytes, si = false, dp = 2) { + return `%${si ? 1000 : 1024}.${dp ?? 0}mB`.format(bytes); + }, +}; + +return view.extend({ + load: function () { + return Promise.all([ + L.resolveDefault(adb.getFileUrlFilesizes(pkg.Name), {}), + L.resolveDefault(adb.getPlatformSupport(pkg.Name), {}), + uci.load(pkg.Name), + uci.load("dhcp"), + ]); + }, + + render: function (data) { + var reply = { + sizes: (data[0] && data[0][pkg.Name] && data[0][pkg.Name]["sizes"]) || [], + platform: (data[1] && data[1][pkg.Name]) || { + ipset_installed: false, + nft_installed: false, + dnsmasq_installed: false, + unbound_installed: false, + dnsmasq_ipset_support: false, + dnsmasq_nftset_support: false, + leds: [], + }, + }; + var status, m, s1, s2, s3, o; + + status = new adb.status(); + m = new form.Map(pkg.Name, _("AdBlock-Fast - Configuration")); + s1 = m.section(form.NamedSection, "config", pkg.Name); + s1.tab("tab_basic", _("Basic Configuration")); + s1.tab("tab_advanced", _("Advanced Configuration")); + + var text = _( + "DNS resolution option, see the %sREADME%s for details." + ).format( + '', + "" + ); + if (!reply.platform.dnsmasq_installed) { + text += + "
" + + _("Please note that %s is not supported on this system.").format( + "dnsmasq.addnhosts" + ); + text += + "
" + + _("Please note that %s is not supported on this system.").format( + "dnsmasq.conf" + ); + text += + "
" + + _("Please note that %s is not supported on this system.").format( + "dnsmasq.ipset" + ); + text += + "
" + + _("Please note that %s is not supported on this system.").format( + "dnsmasq.servers" + ); + } else { + if (!reply.platform.dnsmasq_ipset_support) { + text += + "
" + + _("Please note that %s is not supported on this system.").format( + "dnsmasq.ipset" + ); + } + if (!reply.platform.dnsmasq_nftset_support) { + text += + "
" + + _("Please note that %s is not supported on this system.").format( + "dnsmasq.nftset" + ); + } + } + if (!reply.platform.unbound_installed) { + text = + text + + "
" + + _("Please note that %s is not supported on this system.").format( + "unbound.adb_list" + ); + } + + o = s1.taboption( + "tab_basic", + form.ListValue, + "dns", + _("DNS Service"), + text + ); + if (reply.platform.dnsmasq_installed) { + o.value("dnsmasq.addnhosts", _("dnsmasq additional hosts")); + o.value("dnsmasq.conf", _("dnsmasq config")); + if (reply.platform.dnsmasq_ipset_support) { + o.value("dnsmasq.ipset", _("dnsmasq ipset")); + } + if (reply.platform.dnsmasq_nftset_support) { + o.value("dnsmasq.nftset", _("dnsmasq nft set")); + } + o.value("dnsmasq.servers", _("dnsmasq servers file")); + } + if (reply.platform.unbound_installed) { + o.value("unbound.adb_list", _("unbound adblock list")); + } + o.default = ("dnsmasq.servers", _("dnsmasq servers file")); + + o = s1.taboption( + "tab_basic", + form.Value, + "dnsmasq_config_file_url", + _("Dnsmasq Config File URL"), + _( + "URL to the external dnsmasq config file, see the %sREADME%s for details." + ).format( + '', + "" + ) + ); + o.depends("dns", "dnsmasq.conf"); + + o = s1.taboption( + "tab_basic", + form.ListValue, + "dnsmasq_instance", + _("Use AdBlocking on the dnsmasq instance(s)"), + _( + "You can limit the AdBlocking to a specific dnsmasq instance(s) (%smore information%s)." + ).format( + '', + "" + ) + ); + o.value("*", _("AdBlock on all instances")); + var sections = uci.sections("dhcp", "dnsmasq"); + sections.forEach((element) => { + var description; + var key; + if (element[".name"] === uci.resolveSID("dhcp", element[".name"])) { + key = element[".index"]; + description = "dnsmasq[" + element[".index"] + "]"; + } else { + key = element[".name"]; + description = element[".name"]; + } + o.value(key, _("AdBlock on %s only").format(description)); + }); + o.value("-", _("No AdBlock on dnsmasq")); + o.default = "*"; + o.depends("dns", "dnsmasq.addnhosts"); + o.depends("dns", "dnsmasq.servers"); + o.retain = true; + + o = s1.taboption( + "tab_basic", + form.ListValue, + "force_dns", + _("Force Router DNS"), + _("Forces Router DNS use on local devices, also known as DNS Hijacking.") + ); + o.value("0", _("Let local devices use their own DNS servers if set")); + o.value("1", _("Force Router DNS server to all local devices")); + o.default = ("1", _("Force Router DNS server to all local devices")); + + o = s1.taboption( + "tab_basic", + form.ListValue, + "verbosity", + _("Output Verbosity Setting"), + _("Controls system log and console output verbosity.") + ); + o.value("0", _("Suppress output")); + o.value("1", _("Some output")); + o.value("2", _("Verbose output")); + o.default = ("2", _("Verbose output")); + + if (reply.platform.leds.length) { + o = s1.taboption( + "tab_basic", + form.ListValue, + "led", + _("LED to indicate status"), + _( + "Pick the LED not already used in %sSystem LED Configuration%s." + ).format('', "") + ); + o.value("", _("none")); + reply.platform.leds.forEach((element) => { + o.value(element); + }); + } + o = s1.taboption( + "tab_advanced", + form.ListValue, + "config_update_enabled", + _("Automatic Config Update"), + _("Perform config update before downloading the block/allow-lists.") + ); + o.value("0", _("Disable")); + o.value("1", _("Enable")); + o.default = ("0", _("Disable")); + + o = s1.taboption( + "tab_advanced", + form.ListValue, + "ipv6_enabled", + _("IPv6 Support"), + _("Add IPv6 entries to block-list.") + ); + o.value("", _("Do not add IPv6 entries")); + o.value("1", _("Add IPv6 entries")); + o.depends("dns", "dnsmasq.addnhosts"); + o.depends("dns", "dnsmasq.nftset"); + o.default = ("", _("Do not add IPv6 entries")); + o.rmempty = true; + o.retain = true; + + o = s1.taboption( + "tab_advanced", + form.Value, + "download_timeout", + _("Download time-out (in seconds)"), + _("Stop the download if it is stalled for set number of seconds.") + ); + o.default = "20"; + o.datatype = "range(1,60)"; + + o = s1.taboption( + "tab_advanced", + form.Value, + "curl_max_file_size", + _("Curl maximum file size (in bytes)"), + _( + "If curl is installed and detected, it would not download files bigger than this." + ) + ); + o.default = ""; + o.datatype = "uinteger"; + o.rmempty = true; + + o = s1.taboption( + "tab_advanced", + form.Value, + "curl_retry", + _("Curl download retry"), + _( + "If curl is installed and detected, it would retry download this many times on timeout/fail." + ) + ); + o.default = "3"; + o.datatype = "range(0,30)"; + + o = s1.taboption( + "tab_advanced", + form.ListValue, + "parallel_downloads", + _("Simultaneous processing"), + _( + "Launch all lists downloads and processing simultaneously, reducing service start time." + ) + ); + o.value("0", _("Do not use simultaneous processing")); + o.value("1", _("Use simultaneous processing")); + o.default = ("1", _("Use simultaneous processing")); + + o = s1.taboption( + "tab_advanced", + form.ListValue, + "compressed_cache", + _("Store compressed cache file on router"), + _( + "Attempt to create a compressed cache of block-list in the persistent memory." + ) + ); + o.value("0", _("Do not store compressed cache")); + o.value("1", _("Store compressed cache")); + o.default = ("0", _("Do not store compressed cache")); + + o = s1.taboption( + "tab_advanced", + form.Value, + "compressed_cache_dir", + _("Directory for compressed cache file"), + _( + "Directory for compressed cache file of block-list in the persistent memory." + ) + ); + o.datatype = "string"; + o.rmempty = true; + o.default = "/etc"; + o.depends("compressed_cache", "1"); + o.retain = true; + + o = s1.taboption( + "tab_advanced", + form.ListValue, + "debug", + _("Enable Debugging"), + _("Enables debug output to /tmp/adblock-fast.log.") + ); + o.value("0", _("Disable Debugging")); + o.value("1", _("Enable Debugging")); + o.default = ("0", _("Disable Debugging")); + + s2 = m.section( + form.NamedSection, + "config", + "adblock-fast", + _("AdBlock-Fast - Allowed and Blocked Domains") + ); + o.addremove = true; + o.rmempty = true; + o = s2.option( + form.DynamicList, + "allowed_domain", + _("Allowed Domains"), + _("Individual domains to be allowed.") + ); + + o.addremove = true; + o = s2.option( + form.DynamicList, + "blocked_domain", + _("Blocked Domains"), + _("Individual domains to be blocked.") + ); + o.addremove = true; + + s3 = m.section( + form.GridSection, + "file_url", + _("AdBlock-Fast - Allowed and Blocked Lists URLs"), + _("URLs to file(s) containing lists to be allowed or blocked.") + ); + s3.rowcolors = true; + s3.sortable = true; + s3.anonymous = true; + s3.addremove = true; + o = s3.option(form.DummyValue, "_size", "Size"); + o.modalonly = false; + o.cfgvalue = function (section_id) { + let url = uci.get(pkg.Name, section_id, "url"); + let ret = _("Unknown"); + reply.sizes.forEach((element) => { + if (element.url === url) { + ret = element.size === 0 ? ret : pkg.humanFileSize(element.size); + } + }); + return _("Size: %s").format(ret); + }; + o = s3.option(form.Flag, "enabled", _("Enable")); + o.editable = true; + o.default = "1"; + o = s3.option(form.ListValue, "action", _("Action")); + o.value("allow", _("Allow")); + o.value("block", _("Block")); + o.default = "block"; + o = s3.option(form.Value, "url", _("URL")); + o.optional = false; + + return Promise.all([status.render(), m.render()]); + }, +}); -- cgit v1.2.3