// 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 LuciCompat() { return 8; }, get ReadmeCompat() { return ""; }, get URL() { return ( "https://docs.openwrt.melmac.ca/" + pkg.Name + "/" + (pkg.ReadmeCompat ? pkg.ReadmeCompat + "/" : "") ); }, get DonateURL() { return ( "https://docs.openwrt.melmac.ca/" + pkg.Name + "/" + (pkg.ReadmeCompat ? pkg.ReadmeCompat + "/" : "") + "#Donate" ); }, isVersionMismatch: function (luci, pkg, rpcd) { return luci !== pkg || pkg !== rpcd || luci !== rpcd; }, formatMessage: function (info, template) { if (!template) return _("Unknown message") + "
"; return ( (Array.isArray(info) ? template.format(...info) : template.format(info || " ")) + "
" ); }, humanFileSize: function (bytes, si = false, dp = 2) { return `%${si ? 1000 : 1024}.${dp ?? 0}mB`.format(bytes); }, isObjEmpty: function (obj) { return Object.keys(obj).length === 0; }, statusTable: { statusNoInstall: _("%s is not installed or not found").format( "adblock-fast" ), statusStopped: _("Stopped"), statusStarting: _("Starting"), statusProcessing: _("Processing lists"), statusRestarting: _("Restarting"), statusForceReloading: _("Force Reloading"), statusDownloading: _("Downloading lists"), statusFail: _("Failed to start"), statusSuccess: _("Active"), statusTriggerBootWait: _("Waiting for trigger (on_boot)"), statusTriggerStartWait: _("Waiting for trigger (on_start)"), }, warningTable: { warningInternalVersionMismatch: _( "Internal version mismatch (package: %s, luci app: %s, luci rpcd: %s), you may need to update packages or reboot the device, please check the %sREADME%s." ), warningExternalDnsmasqConfig: _( "Use of external dnsmasq config file detected, please set '%s' option to '%s'" ).format("dns", "dnsmasq.conf"), warningMissingRecommendedPackages: _("Missing recommended package: '%s'"), warningOutdatedLuciPackage: _( "The WebUI application (luci-app-adblock-fast) is outdated, please update it" ), warningOutdatedPrincipalPackage: _( "The principal package (adblock-fast) is outdated, please update it" ), warningInvalidCompressedCacheDir: _( "Invalid compressed cache directory '%s'" ), warningFreeRamCheckFail: _("Can't detect free RAM"), warningSanityCheckTLD: _("Sanity check discovered TLDs in %s"), warningSanityCheckLeadingDot: _( "Sanity check discovered leading dots in %s" ), }, errorTable: { errorConfigValidationFail: _("Config (%s) validation failure!").format( "/etc/config/" + "adblock-fast" ), errorServiceDisabled: _("%s is currently disabled").format("adblock-fast"), 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( "adblock-fast" ), errorOutputDirCreate: _("Failed to create directory for %s file"), errorOutputFileCreate: _("Failed to create '%s' file"), 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"), // NOTE: keep placeholders; fill with info at render time errorMovingDataFile: _("Failed to move temporary data file to '%s'"), errorCreatingCompressedCache: _("Failed to create compressed cache"), errorRemovingTempFiles: _("Failed to remove temporary files"), errorRestoreCompressedCache: _("Failed to unpack compressed cache"), // NOTE: keep placeholders; fill with info at render time errorRestoreCache: _("Failed to move '%s' to '%s'"), errorOhSnap: _("Failed to create block-list or restart DNS resolver"), errorStopping: _("Failed to stop %s").format("adblock-fast"), 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 %s"), errorNoSSLSupport: _("No HTTPS/SSL support on device"), errorCreatingDirectory: _( "Failed to create output/cache/gzip file directory" ), errorDetectingFileType: _("Failed to detect format %s"), errorNothingToDo: _("No blocked list URLs nor blocked-domains enabled"), errorTooLittleRam: _( "Free ram (%s) is not enough to process all enabled block-lists" ), errorCreatingBackupFile: _("failed to create backup file %s"), errorDeletingDataFile: _("failed to delete data file %s"), errorRestoringBackupFile: _("failed to restore backup file %s"), errorNoOutputFile: _("failed to create final block-list %s"), errorNoHeartbeat: _( "Heartbeat domain is not accessible after resolver restart" ), }, }; 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 getUbusInfo = rpc.declare({ object: "luci." + pkg.Name, method: "getUbusInfo", 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), {}), L.resolveDefault(getUbusInfo(pkg.Name), {}), ]).then(function ([initStatus, ubusInfo]) { var reply = { status: initStatus?.[pkg.Name] || { enabled: false, status: null, packageCompat: 0, rpcdCompat: 0, 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: [], }, ubus: ubusInfo?.[pkg.Name]?.instances?.main?.data || { packageCompat: 0, errors: [], warnings: [], }, }; if ( pkg.isVersionMismatch( pkg.LuciCompat, reply.status.packageCompat, reply.status.rpcdCompat ) ) { reply.ubus.warnings.push({ code: "warningInternalVersionMismatch", info: [ reply.ubus.packageCompat, pkg.LuciCompat, reply.status.rpcdCompat, '', "", ], }); } var text = ""; var outputFile = reply.status.outputFile; var outputCache = reply.status.outputCache; 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 += pkg.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 += "."; } text += "
" + "
" + _( "Please %sdonate%s to support development of this project." ).format( "", "" ); break; case "statusStopped": if (reply.status.enabled) { text += pkg.statusTable[reply.status.status] + "."; } else { text += pkg.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 += pkg.statusTable[reply.status.status] + "..."; break; default: text += pkg.statusTable[reply.status.status] + "."; break; } } else { text = _("Not installed or not found"); } var statusText = E("div", { class: "cbi-value-description" }, text); var statusField = E("div", { class: "cbi-value-field" }, statusText); var statusDiv = E("div", { class: "cbi-value" }, [ statusTitle, statusField, ]); var warningsDiv = []; if (reply.ubus.warnings && reply.ubus.warnings.length) { var warningsTitle = E( "label", { class: "cbi-value-title" }, _("Service Warnings") ); var text = ""; reply.ubus.warnings.forEach((element) => { if (element.code && pkg.warningTable[element.code]) { text += pkg.formatMessage( element.info, pkg.warningTable[element.code] ); } else { text += _("Unknown warning") + "
"; } }); var warningsText = E("div", { class: "cbi-value-description" }, text); var warningsField = E( "div", { class: "cbi-value-field" }, warningsText ); warningsDiv = E("div", { class: "cbi-value" }, [ warningsTitle, warningsField, ]); } var errorsDiv = []; if (reply.ubus.errors && reply.ubus.errors.length) { var errorsTitle = E( "label", { class: "cbi-value-title" }, _("Service Errors") ); var text = ""; reply.ubus.errors.forEach((element) => { if (element.code && pkg.errorTable[element.code]) { text += pkg.formatMessage( element.info, pkg.errorTable[element.code] ); } else { text += _("Unknown error") + "
"; } }); text += _("Errors encountered, please check the %sREADME%s").format( '', "!
" ); var errorsText = E("div", { class: "cbi-value-description" }, 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_dl = E( "button", { class: "btn cbi-button cbi-button-apply", disabled: true, click: function (ev) { ui.showModal(null, [ E( "p", { class: "spinning" }, _("Force redownloading %s block lists").format(pkg.Name) ), ]); return RPC.setInitAction(pkg.Name, "dl"); }, }, _("Redownload") ); var btn_action_pause = E( "button", { class: "btn cbi-button cbi-button-apply", disabled: true, click: function (ev) { ui.showModal(null, [ E("p", { class: "spinning" }, _("Pausing %s").format(pkg.Name)), ]); return RPC.setInitAction(pkg.Name, "pause"); }, }, _("Pause") ); 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_dl.disabled = false; btn_action_pause.disabled = false; btn_stop.disabled = false; break; case "statusStopped": btn_start.disabled = false; btn_action_dl.disabled = true; btn_action_pause.disabled = true; btn_stop.disabled = true; break; default: btn_start.disabled = false; btn_action_dl.disabled = true; btn_action_pause.disabled = true; btn_stop.disabled = false; btn_enable.disabled = true; btn_disable.disabled = true; break; } } else { btn_start.disabled = true; btn_action_dl.disabled = true; btn_action_pause.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_pause, // btn_gap, btn_action_dl, 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, pkg: pkg, getInitStatus: getInitStatus, getFileUrlFilesizes: getFileUrlFilesizes, getPlatformSupport: getPlatformSupport, getUbusInfo: getUbusInfo, });