luci-app-adblock-fast: update to 1.2.1-r3
authorStan Grishin <redacted>
Wed, 28 Jan 2026 00:23:53 +0000 (00:23 +0000)
committerStan Grishin <redacted>
Thu, 29 Jan 2026 18:36:48 +0000 (10:36 -0800)
* bugfix: check if name passed to setInitAction of rpcd script matches the
  package name
* feature: add cron job support to WebUI (thanks @Aethersailor)
* add support for dnsmasq_sanity_check and dnsmasq_validity_check to UI

Signed-off-by: Stan Grishin <redacted>
applications/luci-app-adblock-fast/Makefile
applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js
applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js
applications/luci-app-adblock-fast/po/templates/adblock-fast.pot
applications/luci-app-adblock-fast/root/usr/libexec/rpcd/luci.adblock-fast
applications/luci-app-adblock-fast/root/usr/share/rpcd/acl.d/luci-app-adblock-fast.json

index ba9215b443895d577e417ad6df3438210f37e0c9..04673e2f24fe01bd199930bc61342a6c2904097e 100644 (file)
@@ -1,13 +1,13 @@
 # SPDX-License-Identifier: AGPL-3.0-or-later
-# Copyright 2023-2025 MOSSDeF, Stan Grishin (stangri@melmac.ca).
+# Copyright 2023-2026 MOSSDeF, Stan Grishin (stangri@melmac.ca).
 
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=luci-app-adblock-fast
 PKG_LICENSE:=AGPL-3.0-or-later
 PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
-PKG_VERSION:=1.2.0
-PKG_RELEASE:=26
+PKG_VERSION:=1.2.1
+PKG_RELEASE:=3
 
 LUCI_TITLE:=AdBlock-Fast Web UI
 LUCI_URL:=https://github.com/stangri/luci-app-adblock-fast/
index 4f172cbd582ec6116cc7cf7f8b272176461472fa..b5f22cc561cc29738106749929a489d3e7479209 100644 (file)
@@ -12,7 +12,7 @@ var pkg = {
                return "adblock-fast";
        },
        get LuciCompat() {
-               return 9;
+               return 11;
        },
        get ReadmeCompat() {
                return "";
@@ -31,7 +31,7 @@ var pkg = {
                        pkg.Name +
                        "/" +
                        (pkg.ReadmeCompat ? pkg.ReadmeCompat + "/" : "") +
-                       "#Donate"
+                       "#donate"
                );
        },
        isVersionMismatch: function (luci, pkg, rpcd) {
@@ -54,7 +54,7 @@ var pkg = {
 
        statusTable: {
                statusNoInstall: _("%s is not installed or not found").format(
-                       "adblock-fast"
+                       "adblock-fast",
                ),
                statusStopped: _("Stopped"),
                statusStarting: _("Starting"),
@@ -70,47 +70,62 @@ var pkg = {
 
        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."
+                       "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'"
+                       "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"
+                       "The WebUI application (luci-app-adblock-fast) is outdated, please update it",
                ),
                warningOutdatedPrincipalPackage: _(
-                       "The principal package (adblock-fast) is outdated, please update it"
+                       "The principal package (adblock-fast) is outdated, please update it",
                ),
                warningInvalidCompressedCacheDir: _(
-                       "Invalid compressed cache directory '%s'"
+                       "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"
+                       "Sanity check discovered leading dots in %s",
+               ),
+               warningInvalidDomainsRemoved: _(
+                       "Removed %s invalid domain entries from block-list (domains starting with -/./numbers or containing invalid patterns)",
+               ),
+               warningCronDisabled: _(
+                       "Cron service is not enabled or running. Enable it with: %s.",
+               ),
+               warningCronMissing: _(
+                       "Cron daemon is not available. If BusyBox crond is present, enable it with: %s; otherwise install another cron daemon.",
+               ),
+               warningCronEntryMissing: _(
+                       "Cron entry is missing; click %s to recreate it.",
+               ),
+               warningCronEntryMismatch: _(
+                       "Cron entry does not match the schedule; click %s to overwrite it.",
                ),
        },
 
        errorTable: {
                errorConfigValidationFail: _("Config (%s) validation failure!").format(
-                       "/etc/config/" + "adblock-fast"
+                       "/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"
+                       "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"
+                       "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"
+                       "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"
+                       "The dnsmasq nft sets support is enabled, but nft is not installed",
                ),
                errorNoWanGateway: _("The %s failed to discover WAN gateway").format(
-                       "adblock-fast"
+                       "adblock-fast",
                ),
                errorOutputDirCreate: _("Failed to create directory for %s file"),
                errorOutputFileCreate: _("Failed to create '%s' file"),
@@ -136,19 +151,19 @@ var pkg = {
                errorParsingList: _("Failed to parse %s"),
                errorNoSSLSupport: _("No HTTPS/SSL support on device"),
                errorCreatingDirectory: _(
-                       "Failed to create output/cache/gzip file directory"
+                       "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"
+                       "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"
+                       "Heartbeat domain is not accessible after resolver restart",
                ),
        },
 };
@@ -159,6 +174,13 @@ var getFileUrlFilesizes = rpc.declare({
        params: ["name", "url"],
 });
 
+var syncCron = rpc.declare({
+       object: "luci." + pkg.Name,
+       method: "syncCron",
+       params: ["name", "action"],
+       expect: { result: false },
+});
+
 var getInitList = rpc.declare({
        object: "luci." + pkg.Name,
        method: "getInitList",
@@ -171,18 +193,24 @@ var getInitStatus = rpc.declare({
        params: ["name"],
 });
 
-var getPlatformSupport = rpc.declare({
+var getCronStatus = rpc.declare({
        object: "luci." + pkg.Name,
-       method: "getPlatformSupport",
+       method: "getCronStatus",
        params: ["name"],
 });
 
-var getUbusInfo = rpc.declare({
+var getPlatformSupport = rpc.declare({
        object: "luci." + pkg.Name,
-       method: "getUbusInfo",
+       method: "getPlatformSupport",
        params: ["name"],
 });
 
+var getServiceInfo = rpc.declare({
+       object: "service",
+       method: "list",
+       params: ["name", "verbose"],
+});
+
 var _setInitAction = rpc.declare({
        object: "luci." + pkg.Name,
        method: "setInitAction",
@@ -209,17 +237,19 @@ var RPC = {
                });
        },
        setInitAction: function (name, action) {
-               _setInitAction(name, action).then(
-                       function (result) {
-                               this.emit("setInitAction", result);
-                       }.bind(this)
-               ).catch(
-                       function (error) {
-                               // Even if RPC call fails/times out, emit event to start polling
-                               // This handles cases where the backend task starts but RPC times out
-                               this.emit("setInitAction", { timeout: true });
-                       }.bind(this)
-               );
+               _setInitAction(name, action)
+                       .then(
+                               function (result) {
+                                       this.emit("setInitAction", result);
+                               }.bind(this),
+                       )
+                       .catch(
+                               function (error) {
+                                       // Even if RPC call fails/times out, emit event to start polling
+                                       // This handles cases where the backend task starts but RPC times out
+                                       this.emit("setInitAction", { timeout: true });
+                               }.bind(this),
+                       );
        },
 };
 
@@ -232,31 +262,36 @@ var pollServiceStatus = function (callback) {
                attempt++;
 
                // Use the RPC function directly from the module scope
-               L.resolveDefault(getInitStatus(pkg.Name), {}).then(function (statusData) {
-                       var currentStatus = statusData && statusData[pkg.Name] && statusData[pkg.Name].status;
-
-                       // Check if completed or failed
-                       if (currentStatus === 'statusSuccess' ||
-                               currentStatus === 'statusFail' ||
-                               currentStatus === 'statusStopped') {
-                               callback(true, currentStatus);
-                       }
-                       // Check if timed out
-                       else if (attempt >= maxAttempts) {
-                               callback(false, 'timeout');
-                       }
-                       // Continue polling
-                       else {
-                               setTimeout(checkStatus, 1000); // Check again in 1 second
-                       }
-               }).catch(function (err) {
-                       // Retry on error unless timed out
-                       if (attempt < maxAttempts) {
-                               setTimeout(checkStatus, 1000);
-                       } else {
-                               callback(false, 'error');
-                       }
-               });
+               L.resolveDefault(getInitStatus(pkg.Name), {})
+                       .then(function (statusData) {
+                               var currentStatus =
+                                       statusData && statusData[pkg.Name] && statusData[pkg.Name].status;
+
+                               // Check if completed or failed
+                               if (
+                                       currentStatus === "statusSuccess" ||
+                                       currentStatus === "statusFail" ||
+                                       currentStatus === "statusStopped"
+                               ) {
+                                       callback(true, currentStatus);
+                               }
+                               // Check if timed out
+                               else if (attempt >= maxAttempts) {
+                                       callback(false, "timeout");
+                               }
+                               // Continue polling
+                               else {
+                                       setTimeout(checkStatus, 1000); // Check again in 1 second
+                               }
+                       })
+                       .catch(function (err) {
+                               // Retry on error unless timed out
+                               if (attempt < maxAttempts) {
+                                       setTimeout(checkStatus, 1000);
+                               } else {
+                                       callback(false, "error");
+                               }
+                       });
        };
 
        // Start polling after 2 seconds delay (give backend time to start the task)
@@ -267,8 +302,9 @@ var status = baseclass.extend({
        render: function () {
                return Promise.all([
                        L.resolveDefault(getInitStatus(pkg.Name), {}),
-                       L.resolveDefault(getUbusInfo(pkg.Name), {}),
-               ]).then(function ([initStatus, ubusInfo]) {
+                       L.resolveDefault(getServiceInfo(pkg.Name, true), {}),
+                       L.resolveDefault(getCronStatus(pkg.Name), {}),
+               ]).then(function ([initStatus, ubusInfo, cronStatus]) {
                        var reply = {
                                status: initStatus?.[pkg.Name] || {
                                        enabled: false,
@@ -296,13 +332,22 @@ var status = baseclass.extend({
                                        errors: [],
                                        warnings: [],
                                },
+                               cron: cronStatus?.[pkg.Name] || {
+                                       auto_update_enabled: false,
+                                       cron_init: false,
+                                       cron_bin: false,
+                                       cron_enabled: false,
+                                       cron_running: false,
+                                       cron_line_present: false,
+                                       cron_line_match: false,
+                               },
                        };
 
                        if (
                                pkg.isVersionMismatch(
                                        pkg.LuciCompat,
                                        reply.status.packageCompat,
-                                       reply.status.rpcdCompat
+                                       reply.status.rpcdCompat,
                                )
                        ) {
                                reply.ubus.warnings.push({
@@ -312,12 +357,44 @@ var status = baseclass.extend({
                                                pkg.LuciCompat,
                                                reply.status.rpcdCompat,
                                                '<a href="' +
-                                               pkg.URL +
-                                               '#internal_version_mismatch" target="_blank">',
+                                                       pkg.URL +
+                                                       '#internal_version_mismatch" target="_blank">',
                                                "</a>",
                                        ],
                                });
                        }
+                       var cronSyncNeeded = false;
+                       if (reply.cron.auto_update_enabled) {
+                               var enableCronCmd =
+                                       "<code>/etc/init.d/cron enable && /etc/init.d/cron start</code>";
+                               var resyncLabel = "<code>" + _("Resync Cron") + "</code>";
+                               if (reply.status.enabled && reply.status.running) {
+                                       if (!reply.cron.cron_init || !reply.cron.cron_bin) {
+                                               reply.ubus.warnings.push({
+                                                       code: "warningCronMissing",
+                                                       info: enableCronCmd,
+                                               });
+                                       } else if (!reply.cron.cron_enabled || !reply.cron.cron_running) {
+                                               reply.ubus.warnings.push({
+                                                       code: "warningCronDisabled",
+                                                       info: enableCronCmd,
+                                               });
+                                       }
+                                       if (!reply.cron.cron_line_present) {
+                                               reply.ubus.warnings.push({
+                                                       code: "warningCronEntryMissing",
+                                                       info: resyncLabel,
+                                               });
+                                               cronSyncNeeded = true;
+                                       } else if (!reply.cron.cron_line_match) {
+                                               reply.ubus.warnings.push({
+                                                       code: "warningCronEntryMismatch",
+                                                       info: resyncLabel,
+                                               });
+                                               cronSyncNeeded = true;
+                                       }
+                               }
+                       }
                        var text = "";
                        var outputFile = reply.status.outputFile;
                        var outputCache = reply.status.outputCache;
@@ -326,7 +403,7 @@ var status = baseclass.extend({
                        var statusTitle = E(
                                "label",
                                { class: "cbi-value-title" },
-                               _("Service Status")
+                               _("Service Status"),
                        );
                        if (reply.status.version) {
                                text += _("Version %s").format(reply.status.version) + " - ";
@@ -337,7 +414,7 @@ var status = baseclass.extend({
                                                        "<br />" +
                                                        _("Blocking %s domains (with %s).").format(
                                                                reply.status.entries,
-                                                               reply.status.dns
+                                                               reply.status.dns,
                                                        );
                                                if (reply.status.outputGzipExists) {
                                                        text += "<br />" + _("Compressed cache file created.");
@@ -353,10 +430,10 @@ var status = baseclass.extend({
                                                        "<br />" +
                                                        "<br />" +
                                                        _(
-                                                               "Please %sdonate%s to support development of this project."
+                                                               "Please %sdonate%s to support development of this project.",
                                                        ).format(
                                                                "<a href='" + pkg.DonateURL + "' target='_blank'>",
-                                                               "</a>"
+                                                               "</a>",
                                                        );
                                                break;
                                        case "statusStopped":
@@ -400,14 +477,14 @@ var status = baseclass.extend({
                                var warningsTitle = E(
                                        "label",
                                        { class: "cbi-value-title" },
-                                       _("Service Warnings")
+                                       _("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]
+                                                       pkg.warningTable[element.code],
                                                );
                                        } else {
                                                text += _("Unknown warning") + "<br />";
@@ -417,7 +494,7 @@ var status = baseclass.extend({
                                var warningsField = E(
                                        "div",
                                        { class: "cbi-value-field" },
-                                       warningsText
+                                       warningsText,
                                );
                                warningsDiv = E("div", { class: "cbi-value" }, [
                                        warningsTitle,
@@ -430,14 +507,14 @@ var status = baseclass.extend({
                                var errorsTitle = E(
                                        "label",
                                        { class: "cbi-value-title" },
-                                       _("Service Errors")
+                                       _("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]
+                                                       pkg.errorTable[element.code],
                                                );
                                        } else {
                                                text += _("Unknown error") + "<br />";
@@ -445,7 +522,7 @@ var status = baseclass.extend({
                                });
                                text += _("Errors encountered, please check the %sREADME%s").format(
                                        '<a href="' + pkg.URL + '" target="_blank">',
-                                       "</a>!<br />"
+                                       "</a>!<br />",
                                );
                                var errorsText = E("div", { class: "cbi-value-description" }, text);
                                var errorsField = E("div", { class: "cbi-value-field" }, errorsText);
@@ -459,7 +536,7 @@ var status = baseclass.extend({
                        var btn_gap_long = E(
                                "span",
                                {},
-                               "&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;"
+                               "&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;",
                        );
 
                        var btn_start = E(
@@ -472,13 +549,13 @@ var status = baseclass.extend({
                                                        E(
                                                                "p",
                                                                { class: "spinning" },
-                                                               _("Starting %s service").format(pkg.Name)
+                                                               _("Starting %s service").format(pkg.Name),
                                                        ),
                                                ]);
                                                return RPC.setInitAction(pkg.Name, "start");
                                        },
                                },
-                               _("Start")
+                               _("Start"),
                        );
 
                        var btn_action_dl = E(
@@ -491,13 +568,40 @@ var status = baseclass.extend({
                                                        E(
                                                                "p",
                                                                { class: "spinning" },
-                                                               _("Force redownloading %s block lists").format(pkg.Name)
+                                                               _("Force redownloading %s block lists").format(pkg.Name),
                                                        ),
                                                ]);
                                                return RPC.setInitAction(pkg.Name, "dl");
                                        },
                                },
-                               _("Redownload")
+                               _("Redownload"),
+                       );
+
+                       var btn_sync_cron = E(
+                               "button",
+                               {
+                                       class: "btn cbi-button cbi-button-apply",
+                                       disabled: true,
+                                       click: function (ev) {
+                                               ui.showModal(null, [
+                                                       E("p", { class: "spinning" }, _("Syncing cron schedule")),
+                                               ]);
+                                               return syncCron(pkg.Name, "apply").then(
+                                                       function (result) {
+                                                               ui.hideModal();
+                                                               location.reload();
+                                                       },
+                                                       function (error) {
+                                                               ui.hideModal();
+                                                               ui.addNotification(
+                                                                       null,
+                                                                       E("p", {}, _("Failed to sync cron schedule")),
+                                                               );
+                                                       },
+                                               );
+                                       },
+                               },
+                               _("Resync Cron"),
                        );
 
                        var btn_action_pause = E(
@@ -512,7 +616,7 @@ var status = baseclass.extend({
                                                return RPC.setInitAction(pkg.Name, "pause");
                                        },
                                },
-                               _("Pause")
+                               _("Pause"),
                        );
 
                        var btn_stop = E(
@@ -525,13 +629,13 @@ var status = baseclass.extend({
                                                        E(
                                                                "p",
                                                                { class: "spinning" },
-                                                               _("Stopping %s service").format(pkg.Name)
+                                                               _("Stopping %s service").format(pkg.Name),
                                                        ),
                                                ]);
                                                return RPC.setInitAction(pkg.Name, "stop");
                                        },
                                },
-                               _("Stop")
+                               _("Stop"),
                        );
 
                        var btn_enable = E(
@@ -544,13 +648,13 @@ var status = baseclass.extend({
                                                        E(
                                                                "p",
                                                                { class: "spinning" },
-                                                               _("Enabling %s service").format(pkg.Name)
+                                                               _("Enabling %s service").format(pkg.Name),
                                                        ),
                                                ]);
                                                return RPC.setInitAction(pkg.Name, "enable");
                                        },
                                },
-                               _("Enable")
+                               _("Enable"),
                        );
 
                        var btn_disable = E(
@@ -563,13 +667,13 @@ var status = baseclass.extend({
                                                        E(
                                                                "p",
                                                                { class: "spinning" },
-                                                               _("Disabling %s service").format(pkg.Name)
+                                                               _("Disabling %s service").format(pkg.Name),
                                                        ),
                                                ]);
                                                return RPC.setInitAction(pkg.Name, "disable");
                                        },
                                },
-                               _("Disable")
+                               _("Disable"),
                        );
 
                        if (reply.status.enabled) {
@@ -584,7 +688,7 @@ var status = baseclass.extend({
                                                break;
                                        case "statusStopped":
                                                btn_start.disabled = false;
-                                               btn_action_dl.disabled = true;
+                                               btn_action_dl.disabled = false;
                                                btn_action_pause.disabled = true;
                                                btn_stop.disabled = true;
                                                break;
@@ -605,26 +709,35 @@ var status = baseclass.extend({
                                btn_enable.disabled = false;
                                btn_disable.disabled = true;
                        }
+                       if (cronSyncNeeded) {
+                               btn_sync_cron.disabled = false;
+                       }
 
                        var buttonsDiv = [];
                        var buttonsTitle = E(
                                "label",
                                { class: "cbi-value-title" },
-                               _("Service Control")
+                               _("Service Control"),
                        );
-                       var buttonsText = E("div", {}, [
+                       var buttonsTextItems = [
                                btn_start,
                                btn_gap,
                                // btn_action_pause,
                                // btn_gap,
                                btn_action_dl,
+                       ];
+                       if (cronSyncNeeded) {
+                               buttonsTextItems.push(btn_gap, btn_sync_cron);
+                       }
+                       buttonsTextItems.push(
                                btn_gap,
                                btn_stop,
                                btn_gap_long,
                                btn_enable,
                                btn_gap,
                                btn_disable,
-                       ]);
+                       );
+                       var buttonsText = E("div", {}, buttonsTextItems);
                        var buttonsField = E("div", { class: "cbi-value-field" }, buttonsText);
                        if (reply.status.version) {
                                buttonsDiv = E("div", { class: "cbi-value" }, [
@@ -658,6 +771,8 @@ return L.Class.extend({
        pkg: pkg,
        getInitStatus: getInitStatus,
        getFileUrlFilesizes: getFileUrlFilesizes,
+       syncCron: syncCron,
+       getCronStatus: getCronStatus,
        getPlatformSupport: getPlatformSupport,
-       getUbusInfo: getUbusInfo,
+       getServiceInfo: getServiceInfo,
 });
index 01c6f9a0c5b0bd2f6df1bb429890bf016a732af8..56f6ac973242afd8e9a9e935521ce648912e622a 100644 (file)
@@ -6,6 +6,7 @@
 "use strict";
 "require form";
 "require view";
+"require ui";
 "require adblock-fast.status as adb";
 
 var pkg = adb.pkg;
@@ -50,45 +51,45 @@ return view.extend({
                s1.tab("tab_advanced", _("Advanced Configuration"));
 
                var text = _(
-                       "DNS resolution option, see the %sREADME%s for details."
+                       "DNS resolution option, see the %sREADME%s for details.",
                ).format(
                        '<a href="' + pkg.URL + '#dns-resolver-option" target="_blank">',
-                       "</a>"
+                       "</a>",
                );
                if (!reply.platform.dnsmasq_installed) {
                        text +=
                                "<br />" +
                                _("Please note that %s is not supported on this system.").format(
-                                       "<i>dnsmasq.addnhosts</i>"
+                                       "<i>dnsmasq.addnhosts</i>",
                                );
                        text +=
                                "<br />" +
                                _("Please note that %s is not supported on this system.").format(
-                                       "<i>dnsmasq.conf</i>"
+                                       "<i>dnsmasq.conf</i>",
                                );
                        text +=
                                "<br />" +
                                _("Please note that %s is not supported on this system.").format(
-                                       "<i>dnsmasq.ipset</i>"
+                                       "<i>dnsmasq.ipset</i>",
                                );
                        text +=
                                "<br />" +
                                _("Please note that %s is not supported on this system.").format(
-                                       "<i>dnsmasq.servers</i>"
+                                       "<i>dnsmasq.servers</i>",
                                );
                } else {
                        if (!reply.platform.dnsmasq_ipset_support) {
                                text +=
                                        "<br />" +
                                        _("Please note that %s is not supported on this system.").format(
-                                               "<i>dnsmasq.ipset</i>"
+                                               "<i>dnsmasq.ipset</i>",
                                        );
                        }
                        if (!reply.platform.dnsmasq_nftset_support) {
                                text +=
                                        "<br />" +
                                        _("Please note that %s is not supported on this system.").format(
-                                               "<i>dnsmasq.nftset</i>"
+                                               "<i>dnsmasq.nftset</i>",
                                        );
                        }
                }
@@ -97,21 +98,21 @@ return view.extend({
                                text +
                                "<br />" +
                                _("Please note that %s is not supported on this system.").format(
-                                       "<i>smartdns.domainset</i>"
+                                       "<i>smartdns.domainset</i>",
                                );
                } else {
                        if (!reply.platform.smartdns_ipset_support) {
                                text +=
                                        "<br />" +
                                        _("Please note that %s is not supported on this system.").format(
-                                               "<i>smartdns.ipset</i>"
+                                               "<i>smartdns.ipset</i>",
                                        );
                        }
                        if (!reply.platform.smartdns_nftset_support) {
                                text +=
                                        "<br />" +
                                        _("Please note that %s is not supported on this system.").format(
-                                               "<i>smartdns.nftset</i>"
+                                               "<i>smartdns.nftset</i>",
                                        );
                        }
                }
@@ -120,7 +121,7 @@ return view.extend({
                                text +
                                "<br />" +
                                _("Please note that %s is not supported on this system.").format(
-                                       "<i>unbound.adb_list</i>"
+                                       "<i>unbound.adb_list</i>",
                                );
                }
 
@@ -129,7 +130,7 @@ return view.extend({
                        form.ListValue,
                        "dns",
                        _("DNS Service"),
-                       text
+                       text,
                );
                if (reply.platform.dnsmasq_installed) {
                        o.value("dnsmasq.addnhosts", _("dnsmasq additional hosts"));
@@ -162,11 +163,11 @@ return view.extend({
                        "dnsmasq_config_file_url",
                        _("Dnsmasq Config File URL"),
                        _(
-                               "URL to the external dnsmasq config file, see the %sREADME%s for details."
+                               "URL to the external dnsmasq config file, see the %sREADME%s for details.",
                        ).format(
                                '<a href="' + pkg.URL + '#dnsmasq_config_file_url" target="_blank">',
-                               "</a>"
-                       )
+                               "</a>",
+                       ),
                );
                o.depends("dns", "dnsmasq.conf");
 
@@ -177,11 +178,11 @@ return view.extend({
                                "dnsmasq_instance_option",
                                _("Use ad-blocking on the dnsmasq instance(s)"),
                                _(
-                                       "You can limit the ad-blocking to the specific dnsmasq instance(s) (%smore information%s)."
+                                       "You can limit the ad-blocking to the specific dnsmasq instance(s) (%smore information%s).",
                                ).format(
                                        '<a href="' + pkg.URL + "#dnsmasq_instance" + '" target="_blank">',
-                                       "</a>"
-                               )
+                                       "</a>",
+                               ),
                        );
                        o.value("*", _("Ad-blocking on all instances"));
                        o.value("+", _("Ad-blocking on select instances"));
@@ -194,7 +195,7 @@ return view.extend({
                                let val = this.map.data.get(
                                        this.map.config,
                                        section_id,
-                                       "dnsmasq_instance"
+                                       "dnsmasq_instance",
                                );
                                if (val && val[0]) {
                                        switch (val[0]) {
@@ -214,22 +215,22 @@ return view.extend({
                                "tab_basic",
                                form.MultiValue,
                                "dnsmasq_instance",
-                               _("Pick the dnsmasq instance(s) for ad-blocking")
+                               _("Pick the dnsmasq instance(s) for ad-blocking"),
+                       );
+                       Object.values(L.uci.sections("dhcp", "dnsmasq")).forEach(
+                               function (element) {
+                                       var description;
+                                       var key;
+                                       if (element[".name"] === L.uci.resolveSID("dhcp", element[".name"])) {
+                                               key = element[".index"];
+                                               description = "dnsmasq[" + element[".index"] + "]";
+                                       } else {
+                                               key = element[".name"];
+                                               description = element[".name"];
+                                       }
+                                       o.value(key, description);
+                               },
                        );
-                       Object.values(L.uci.sections("dhcp", "dnsmasq")).forEach(function (
-                               element
-                       ) {
-                               var description;
-                               var key;
-                               if (element[".name"] === L.uci.resolveSID("dhcp", element[".name"])) {
-                                       key = element[".index"];
-                                       description = "dnsmasq[" + element[".index"] + "]";
-                               } else {
-                                       key = element[".name"];
-                                       description = element[".name"];
-                               }
-                               o.value(key, description);
-                       });
                        o.depends("dnsmasq_instance_option", "+");
                        o.retain = true;
                }
@@ -241,11 +242,11 @@ return view.extend({
                                "smartdns_instance_option",
                                _("Use ad-blocking on the SmartDNS instance(s)"),
                                _(
-                                       "You can limit the ad-blocking to the specific SmartDNS instance(s) (%smore information%s)."
+                                       "You can limit the ad-blocking to the specific SmartDNS instance(s) (%smore information%s).",
                                ).format(
                                        '<a href="' + pkg.URL + "#smartdns_instance" + '" target="_blank">',
-                                       "</a>"
-                               )
+                                       "</a>",
+                               ),
                        );
                        o.value("*", _("Ad-blocking on all instances"));
                        o.value("+", _("Ad-blocking on select instances"));
@@ -257,7 +258,7 @@ return view.extend({
                                let val = this.map.data.get(
                                        this.map.config,
                                        section_id,
-                                       "smartdns_instance"
+                                       "smartdns_instance",
                                );
                                if (val && val[0]) {
                                        switch (val[0]) {
@@ -277,24 +278,24 @@ return view.extend({
                                "tab_basic",
                                form.MultiValue,
                                "smartdns_instance",
-                               _("Pick the SmartDNS instance(s) for ad-blocking")
+                               _("Pick the SmartDNS instance(s) for ad-blocking"),
+                       );
+                       Object.values(L.uci.sections("smartdns", "smartdns")).forEach(
+                               function (element) {
+                                       var description;
+                                       var key;
+                                       if (
+                                               element[".name"] === L.uci.resolveSID("smartdns", element[".name"])
+                                       ) {
+                                               key = element[".index"];
+                                               description = "smartdns[" + element[".index"] + "]";
+                                       } else {
+                                               key = element[".name"];
+                                               description = element[".name"];
+                                       }
+                                       o.value(key, description);
+                               },
                        );
-                       Object.values(L.uci.sections("smartdns", "smartdns")).forEach(function (
-                               element
-                       ) {
-                               var description;
-                               var key;
-                               if (
-                                       element[".name"] === L.uci.resolveSID("smartdns", element[".name"])
-                               ) {
-                                       key = element[".index"];
-                                       description = "smartdns[" + element[".index"] + "]";
-                               } else {
-                                       key = element[".name"];
-                                       description = element[".name"];
-                               }
-                               o.value(key, description);
-                       });
                        o.depends("smartdns_instance_option", "+");
                        o.retain = true;
                }
@@ -303,7 +304,7 @@ return view.extend({
                        form.ListValue,
                        "force_dns",
                        _("Force Router DNS"),
-                       _("Forces Router DNS use on local devices, also known as DNS Hijacking.")
+                       _("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"));
@@ -314,7 +315,7 @@ return view.extend({
                        form.ListValue,
                        "verbosity",
                        _("Output Verbosity Setting"),
-                       _("Controls system log and console output verbosity.")
+                       _("Controls system log and console output verbosity."),
                );
                o.value("0", _("Suppress output"));
                o.value("1", _("Some output"));
@@ -328,8 +329,8 @@ return view.extend({
                                "led",
                                _("LED to indicate status"),
                                _(
-                                       "Pick the LED not already used in %sSystem LED Configuration%s."
-                               ).format('<a href="' + L.url("admin", "system", "leds") + '">', "</a>")
+                                       "Pick the LED not already used in %sSystem LED Configuration%s.",
+                               ).format('<a href="' + L.url("admin", "system", "leds") + '">', "</a>"),
                        );
                        o.value("", _("none"));
                        reply.platform.leds.forEach((element) => {
@@ -343,18 +344,133 @@ return view.extend({
                        form.ListValue,
                        "config_update_enabled",
                        _("Automatic Config Update"),
-                       _("Perform config update before downloading the block/allow-lists.")
+                       _("Perform config update before downloading the block/allow-lists."),
+               );
+               o.value("0", _("Disable"));
+               o.value("1", _("Enable"));
+               o.default = "0";
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_enabled",
+                       _("Automatic List Update"),
+                       _("Enable scheduled list redownloads via /etc/init.d/adblock-fast dl."),
                );
                o.value("0", _("Disable"));
                o.value("1", _("Enable"));
                o.default = "0";
 
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_mode",
+                       _("Schedule Type"),
+               );
+               o.description = _("Select how often the update should run.");
+               o.value("daily", _("Daily"));
+               o.value("weekly", _("Weekly"));
+               o.value("monthly", _("Monthly"));
+               o.value("every_n_days", _("Every N days"));
+               o.value("every_n_hours", _("Every N hours"));
+               o.default = "daily";
+               o.depends("auto_update_enabled", "1");
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_hour",
+                       _("Update Hour"),
+                       _("Hour of day to run the update (0-23)."),
+               );
+               for (var i = 0; i < 24; i++) {
+                       var hourLabel = i < 10 ? "0" + i : "" + i;
+                       o.value(String(i), hourLabel);
+               }
+               o.default = "4";
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "daily" });
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "weekly" });
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "monthly" });
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "every_n_days" });
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_minute",
+                       _("Update Minute"),
+                       _(
+                               "Minute of hour to run the update (0-59). In 'Every N hours' mode, updates run at the selected minute within each interval.",
+                       ),
+               );
+               for (var i = 0; i < 60; i++) {
+                       var minuteLabel = i < 10 ? "0" + i : "" + i;
+                       o.value(String(i), minuteLabel);
+               }
+               o.default = "0";
+               o.depends("auto_update_enabled", "1");
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_weekday",
+                       _("Day of Week"),
+                       _("Run on the selected weekday."),
+               );
+               o.value("0", _("Sunday"));
+               o.value("1", _("Monday"));
+               o.value("2", _("Tuesday"));
+               o.value("3", _("Wednesday"));
+               o.value("4", _("Thursday"));
+               o.value("5", _("Friday"));
+               o.value("6", _("Saturday"));
+               o.default = "0";
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "weekly" });
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_monthday",
+                       _("Day of Month"),
+                       _("Run on the selected day of month."),
+               );
+               for (var i = 1; i <= 31; i++) {
+                       o.value(String(i), String(i));
+               }
+               o.default = "1";
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "monthly" });
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_every_ndays",
+                       _("Every N days"),
+                       _("Run once every N days."),
+               );
+               for (var i = 1; i <= 31; i++) {
+                       o.value(String(i), String(i));
+               }
+               o.default = "3";
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "every_n_days" });
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "auto_update_every_nhours",
+                       _("Every N hours"),
+                       _("Run once every N hours."),
+               );
+               for (var i = 1; i <= 23; i++) {
+                       o.value(String(i), String(i));
+               }
+               o.default = "6";
+               o.depends({ auto_update_enabled: "1", auto_update_mode: "every_n_hours" });
+
                o = s1.taboption(
                        "tab_advanced",
                        form.ListValue,
                        "ipv6_enabled",
                        _("IPv6 Support"),
-                       _("Add IPv6 entries to block-list.")
+                       _("Add IPv6 entries to block-list."),
                );
                o.value("", _("Do not add IPv6 entries"));
                o.value("1", _("Add IPv6 entries"));
@@ -369,7 +485,7 @@ return view.extend({
                        form.Value,
                        "download_timeout",
                        _("Download time-out (in seconds)"),
-                       _("Stop the download if it is stalled for set number of seconds.")
+                       _("Stop the download if it is stalled for set number of seconds."),
                );
                o.default = "20";
                o.datatype = "range(1,60)";
@@ -380,8 +496,8 @@ return view.extend({
                        "curl_max_file_size",
                        _("Curl maximum file size (in bytes)"),
                        _(
-                               "If curl is installed and detected, it would not download files bigger than this."
-                       )
+                               "If curl is installed and detected, it would not download files bigger than this.",
+                       ),
                );
                o.default = "";
                o.datatype = "uinteger";
@@ -393,8 +509,8 @@ return view.extend({
                        "curl_retry",
                        _("Curl download retry"),
                        _(
-                               "If curl is installed and detected, it would retry download this many times on timeout/fail."
-                       )
+                               "If curl is installed and detected, it would retry download this many times on timeout/fail.",
+                       ),
                );
                o.default = "3";
                o.datatype = "range(0,30)";
@@ -405,8 +521,8 @@ return view.extend({
                        "parallel_downloads",
                        _("Simultaneous processing"),
                        _(
-                               "Launch all lists downloads and processing simultaneously, reducing service start time."
-                       )
+                               "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"));
@@ -418,8 +534,8 @@ return view.extend({
                        "compressed_cache",
                        _("Store compressed cache file on router"),
                        _(
-                               "Attempt to create a compressed cache of block-list in the persistent memory."
-                       )
+                               "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"));
@@ -431,8 +547,8 @@ return view.extend({
                        "compressed_cache_dir",
                        _("Directory for compressed cache file"),
                        _(
-                               "Directory for compressed cache file of block-list in the persistent memory."
-                       )
+                               "Directory for compressed cache file of block-list in the persistent memory.",
+                       ),
                );
                o.datatype = "string";
                o.rmempty = true;
@@ -440,12 +556,38 @@ return view.extend({
                o.depends("compressed_cache", "1");
                o.retain = true;
 
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "dnsmasq_sanity_check",
+                       _("Enable dnsmasq sanity check"),
+                       _(
+                               "Enable sanity check for dnsmasq block-list processing to detect and report issues.",
+                       ),
+               );
+               o.value("0", _("Disable"));
+               o.value("1", _("Enable"));
+               o.default = "1";
+
+               o = s1.taboption(
+                       "tab_advanced",
+                       form.ListValue,
+                       "dnsmasq_validity_check",
+                       _("Enable dnsmasq domain validation"),
+                       _(
+                               "Enable RFC 1123 compliant domain validation for dnsmasq block-lists to remove invalid entries.",
+                       ),
+               );
+               o.value("0", _("Disable"));
+               o.value("1", _("Enable"));
+               o.default = "0";
+
                o = s1.taboption(
                        "tab_advanced",
                        form.ListValue,
                        "debug",
                        _("Enable Debugging"),
-                       _("Enables debug output to /tmp/adblock-fast.log.")
+                       _("Enables debug output to /tmp/adblock-fast.log."),
                );
                o.value("0", _("Disable Debugging"));
                o.value("1", _("Enable Debugging"));
@@ -455,7 +597,7 @@ return view.extend({
                        form.NamedSection,
                        "config",
                        "adblock-fast",
-                       _("AdBlock-Fast - Allowed and Blocked Domains")
+                       _("AdBlock-Fast - Allowed and Blocked Domains"),
                );
                o.addremove = true;
                o.rmempty = true;
@@ -464,7 +606,7 @@ return view.extend({
                        form.DynamicList,
                        "allowed_domain",
                        _("Allowed Domains"),
-                       _("Individual domains to be allowed.")
+                       _("Individual domains to be allowed."),
                );
                o.addremove = true;
 
@@ -472,7 +614,7 @@ return view.extend({
                        form.DynamicList,
                        "blocked_domain",
                        _("Blocked Domains"),
-                       _("Individual domains to be blocked.")
+                       _("Individual domains to be blocked."),
                );
                o.addremove = true;
 
@@ -480,7 +622,7 @@ return view.extend({
                        form.GridSection,
                        "file_url",
                        _("AdBlock-Fast - Allowed and Blocked Lists URLs"),
-                       _("URLs to file(s) containing lists to be allowed or blocked.")
+                       _("URLs to file(s) containing lists to be allowed or blocked."),
                );
                s3.rowcolors = true;
                s3.sortable = true;
@@ -532,4 +674,27 @@ return view.extend({
 
                return Promise.all([status.render(), m.render()]);
        },
+
+       handleSave: function (ev) {
+               return this.super("handleSave", [ev]);
+       },
+
+       handleSaveApply: function (ev, mode) {
+               return this.super("handleSave", [ev]).then(function () {
+                       var onApplied = function () {
+                               L.resolveDefault(adb.syncCron(pkg.Name, "apply"), {
+                                       result: false,
+                               }).then(function (result) {
+                                       if (!result || result.result === false) {
+                                               ui.addNotification(
+                                                       null,
+                                                       E("p", {}, _("Failed to update cron schedule.")),
+                                               );
+                                       }
+                               });
+                       };
+                       document.addEventListener("uci-applied", onApplied, { once: true });
+                       ui.changes.apply(mode == "0");
+               });
+       },
 });
index e12733275f902b62c3015e0003986ed8d8388380..22d1a7016fbefe564dddc901849e7cfccb54580b 100644 (file)
@@ -1,7 +1,7 @@
 msgid ""
 msgstr "Content-Type: text/plain; charset=UTF-8"
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:99
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:114
 msgid "%s is currently disabled"
 msgstr ""
 
@@ -17,7 +17,7 @@ msgstr ""
 msgid "-"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:507
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:649
 msgid "Action"
 msgstr ""
 
@@ -25,13 +25,13 @@ msgstr ""
 msgid "Active"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:186
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:250
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:187
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:251
 msgid "Ad-blocking on all instances"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:187
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:251
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:188
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:252
 msgid "Ad-blocking on select instances"
 msgstr ""
 
@@ -43,67 +43,71 @@ msgstr ""
 msgid "AdBlock-Fast"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:458
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:600
 msgid "AdBlock-Fast - Allowed and Blocked Domains"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:482
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:624
 msgid "AdBlock-Fast - Allowed and Blocked Lists URLs"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:46
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:47
 msgid "AdBlock-Fast - Configuration"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:325
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:402
 msgid "AdBlock-Fast - Status"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:360
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:476
 msgid "Add IPv6 entries"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:357
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:473
 msgid "Add IPv6 entries to block-list."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:50
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:51
 msgid "Advanced Configuration"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:508
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:513
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:650
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:655
 msgid "Allow"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:466
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:608
 msgid "Allowed Domains"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:421
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:537
 msgid ""
 "Attempt to create a compressed cache of block-list in the persistent memory."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:345
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:346
 msgid "Automatic Config Update"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:49
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:357
+msgid "Automatic List Update"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:50
 msgid "Basic Configuration"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:509
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:513
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:651
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:655
 msgid "Block"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:474
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:616
 #: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/status/include/70_adblock-fast.js:62
 msgid "Blocked Domains"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:338
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:415
 msgid "Blocking %s domains (with %s)."
 msgstr ""
 
@@ -115,7 +119,7 @@ msgstr ""
 msgid "Cache file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:373
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:450
 msgid "Cache file found."
 msgstr ""
 
@@ -127,82 +131,115 @@ msgstr ""
 msgid "Compressed cache"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:343
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:420
 msgid "Compressed cache file created."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:375
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:452
 msgid "Compressed cache file found."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:96
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:111
 msgid "Config (%s) validation failure!"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:317
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:318
 msgid "Controls system log and console output verbosity."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:394
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:100
+msgid ""
+"Cron daemon is not available. If BusyBox crond is present, enable it with: "
+"%s; otherwise install another cron daemon."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:106
+msgid "Cron entry does not match the schedule; click %s to overwrite it."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:103
+msgid "Cron entry is missing; click %s to recreate it."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:97
+msgid "Cron service is not enabled or running. Enable it with: %s."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:510
 msgid "Curl download retry"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:381
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:497
 msgid "Curl maximum file size (in bytes)"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:131
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:132
 #: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/status/include/70_adblock-fast.js:61
 msgid "DNS Service"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:53
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:54
 msgid "DNS resolution option, see the %sREADME%s for details."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:432
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:371
+msgid "Daily"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:433
+msgid "Day of Month"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:416
+msgid "Day of Week"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:548
 msgid "Directory for compressed cache file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:434
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:550
 msgid ""
 "Directory for compressed cache file of block-list in the persistent memory."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:572
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:348
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:676
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:349
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:360
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:568
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:581
 msgid "Disable"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:450
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:592
 msgid "Disable Debugging"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:369
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:446
 msgid "Disabled"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:566
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:670
 msgid "Disabling %s service"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:163
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:164
 msgid "Dnsmasq Config File URL"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:359
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:475
 msgid "Do not add IPv6 entries"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:424
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:540
 msgid "Do not store compressed cache"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:411
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:527
 msgid "Do not use simultaneous processing"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:371
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:487
 msgid "Download time-out (in seconds)"
 msgstr ""
 
@@ -210,106 +247,143 @@ msgstr ""
 msgid "Downloading lists"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:553
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:349
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:503
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:657
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:350
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:361
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:569
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:582
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:645
 msgid "Enable"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:447
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:451
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:589
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:593
 msgid "Enable Debugging"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:448
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:578
+msgid ""
+"Enable RFC 1123 compliant domain validation for dnsmasq block-lists to "
+"remove invalid entries."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:576
+msgid "Enable dnsmasq domain validation"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:563
+msgid "Enable dnsmasq sanity check"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:565
+msgid ""
+"Enable sanity check for dnsmasq block-list processing to detect and report "
+"issues."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:358
+msgid "Enable scheduled list redownloads via /etc/init.d/adblock-fast dl."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:590
 msgid "Enables debug output to /tmp/adblock-fast.log."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:547
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:651
 msgid "Enabling %s service"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:446
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:523
 msgid "Errors encountered, please check the %sREADME%s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:118
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:374
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:446
+msgid "Every N days"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:375
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:459
+msgid "Every N hours"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:133
 msgid "Failed to access shared memory"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:116
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:131
 msgid "Failed to create '%s' file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:130
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:145
 msgid "Failed to create block-list or restart DNS resolver"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:125
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:140
 msgid "Failed to create compressed cache"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:115
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:130
 msgid "Failed to create directory for %s file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:139
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:154
 msgid "Failed to create output/cache/gzip file directory"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:141
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:156
 msgid "Failed to detect format %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:134
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:149
 msgid "Failed to download %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:133
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:148
 msgid "Failed to download Config Update file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:122
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:137
 msgid "Failed to format data file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:129
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:144
 msgid "Failed to move '%s' to '%s'"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:124
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:139
 msgid "Failed to move temporary data file to '%s'"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:120
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:135
 msgid "Failed to optimize data file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:136
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:151
 msgid "Failed to parse %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:135
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:150
 msgid "Failed to parse Config Update file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:121
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:136
 msgid "Failed to process allow-list"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:132
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:147
 msgid "Failed to reload/restart DNS resolver"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:126
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:141
 msgid "Failed to remove temporary files"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:117
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:132
 msgid "Failed to restart/reload DNS resolver"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:119
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:134
 msgid "Failed to sort data file"
 msgstr ""
 
@@ -317,19 +391,27 @@ msgstr ""
 msgid "Failed to start"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:131
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:146
 msgid "Failed to stop %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:127
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:598
+msgid "Failed to sync cron schedule"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:142
 msgid "Failed to unpack compressed cache"
 msgstr ""
 
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:691
+msgid "Failed to update cron schedule."
+msgstr ""
+
 #: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/status/include/70_adblock-fast.js:64
 msgid "Force DNS Ports"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:346
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:423
 msgid "Force DNS ports:"
 msgstr ""
 
@@ -337,55 +419,63 @@ msgstr ""
 msgid "Force Reloading"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:305
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:306
 msgid "Force Router DNS"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:309
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:310
 msgid "Force Router DNS server to all local devices"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:494
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:571
 msgid "Force redownloading %s block lists"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:306
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:307
 msgid "Forces Router DNS use on local devices, also known as DNS Hijacking."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:144
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:159
 msgid "Free ram (%s) is not enough to process all enabled block-lists"
 msgstr ""
 
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:424
+msgid "Friday"
+msgstr ""
+
 #: applications/luci-app-adblock-fast/root/usr/share/rpcd/acl.d/luci-app-adblock-fast.json:3
 msgid "Grant UCI and file access for luci-app-adblock-fast"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:151
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:166
 msgid "Heartbeat domain is not accessible after resolver restart"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:356
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:384
+msgid "Hour of day to run the update (0-23)."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:472
 msgid "IPv6 Support"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:383
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:499
 msgid ""
 "If curl is installed and detected, it would not download files bigger than "
 "this."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:396
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:512
 msgid ""
 "If curl is installed and detected, it would retry download this many times "
 "on timeout/fail."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:467
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:609
 msgid "Individual domains to be allowed."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:475
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:617
 msgid "Individual domains to be blocked."
 msgstr ""
 
@@ -400,94 +490,108 @@ msgstr ""
 msgid "Invalid compressed cache directory '%s'"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:329
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:330
 msgid "LED to indicate status"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:408
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:524
 msgid ""
 "Launch all lists downloads and processing simultaneously, reducing service "
 "start time."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:308
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:309
 msgid "Let local devices use their own DNS servers if set"
 msgstr ""
 
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:402
+msgid ""
+"Minute of hour to run the update (0-59). In 'Every N hours' mode, updates "
+"run at the selected minute within each interval."
+msgstr ""
+
 #: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:78
 msgid "Missing recommended package: '%s'"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:525
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:420
+msgid "Monday"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:373
+msgid "Monthly"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:667
 msgid "Name"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:516
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:658
 msgid "Name/URL"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:252
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:253
 msgid "No Ad-blocking on SmartDNS"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:188
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:189
 msgid "No Ad-blocking on dnsmasq"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:137
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:152
 msgid "No HTTPS/SSL support on device"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:142
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:157
 msgid "No blocked list URLs nor blocked-domains enabled"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:389
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:466
 msgid "Not installed or not found"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:316
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:317
 msgid "Output Verbosity Setting"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:515
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:619
 msgid "Pause"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:510
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:614
 msgid "Pausing %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:346
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:347
 msgid "Perform config update before downloading the block/allow-lists."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:331
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:332
 msgid "Pick the LED not already used in %sSystem LED Configuration%s."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:280
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:281
 msgid "Pick the SmartDNS instance(s) for ad-blocking"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:217
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:218
 msgid "Pick the dnsmasq instance(s) for ad-blocking"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:356
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:433
 msgid "Please %sdonate%s to support development of this project."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:61
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:66
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:71
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:76
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:83
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:90
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:99
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:106
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:113
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:122
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:62
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:67
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:72
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:77
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:84
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:91
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:100
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:107
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:114
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:123
 msgid "Please note that %s is not supported on this system."
 msgstr ""
 
@@ -495,14 +599,41 @@ msgstr ""
 msgid "Processing lists"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:500
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:577
 msgid "Redownload"
 msgstr ""
 
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:94
+msgid ""
+"Removed %s invalid domain entries from block-list (domains starting with -/./"
+"numbers or containing invalid patterns)"
+msgstr ""
+
 #: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:62
 msgid "Restarting"
 msgstr ""
 
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:370
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:604
+msgid "Resync Cron"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:434
+msgid "Run on the selected day of month."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:417
+msgid "Run on the selected weekday."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:447
+msgid "Run once every N days."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:460
+msgid "Run once every N hours."
+msgstr ""
+
 #: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:89
 msgid "Sanity check discovered TLDs in %s"
 msgstr ""
@@ -511,39 +642,51 @@ msgstr ""
 msgid "Sanity check discovered leading dots in %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:613
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:425
+msgid "Saturday"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:368
+msgid "Schedule Type"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:370
+msgid "Select how often the update should run."
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:720
 msgid "Service Control"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:433
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:510
 msgid "Service Errors"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:329
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:406
 msgid "Service Status"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:403
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:480
 msgid "Service Warnings"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:406
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:522
 msgid "Simultaneous processing"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:490
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:632
 msgid "Size"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:500
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:642
 msgid "Size: %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:320
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:321
 msgid "Some output"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:481
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:558
 msgid "Start"
 msgstr ""
 
@@ -551,7 +694,7 @@ msgstr ""
 msgid "Starting"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:475
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:552
 msgid "Starting %s service"
 msgstr ""
 
@@ -559,11 +702,11 @@ msgstr ""
 msgid "Status"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:534
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:638
 msgid "Stop"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:372
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:488
 msgid "Stop the download if it is stalled for set number of seconds."
 msgstr ""
 
@@ -571,23 +714,31 @@ msgstr ""
 msgid "Stopped"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:528
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:632
 msgid "Stopping %s service"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:425
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:541
 msgid "Store compressed cache"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:419
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:535
 msgid "Store compressed cache file on router"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:319
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:419
+msgid "Sunday"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:320
 msgid "Suppress output"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:112
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:587
+msgid "Syncing cron schedule"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:127
 msgid "The %s failed to discover WAN gateway"
 msgstr ""
 
@@ -596,25 +747,25 @@ msgid ""
 "The WebUI application (luci-app-adblock-fast) is outdated, please update it"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:101
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:116
 msgid ""
 "The dnsmasq ipset support is enabled, but dnsmasq is either not installed or "
 "installed dnsmasq does not support ipset"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:104
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:119
 msgid ""
 "The dnsmasq ipset support is enabled, but ipset is either not installed or "
 "installed ipset does not support '%s' type"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:107
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:122
 msgid ""
 "The dnsmasq nft set support is enabled, but dnsmasq is either not installed "
 "or installed dnsmasq does not support nft set"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:110
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:125
 msgid "The dnsmasq nft sets support is enabled, but nft is not installed"
 msgstr ""
 
@@ -622,26 +773,34 @@ msgstr ""
 msgid "The principal package (adblock-fast) is outdated, please update it"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:529
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:423
+msgid "Thursday"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:421
+msgid "Tuesday"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:671
 msgid "URL"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:165
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:166
 msgid ""
 "URL to the external dnsmasq config file, see the %sREADME%s for details."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:483
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:625
 msgid "URLs to file(s) containing lists to be allowed or blocked."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:494
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:521
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:636
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:663
 #: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/status/include/70_adblock-fast.js:70
 msgid "Unknown"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:443
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:520
 msgid "Unknown error"
 msgstr ""
 
@@ -649,15 +808,23 @@ msgstr ""
 msgid "Unknown message"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:413
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:490
 msgid "Unknown warning"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:242
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:383
+msgid "Update Hour"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:400
+msgid "Update Minute"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:243
 msgid "Use ad-blocking on the SmartDNS instance(s)"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:178
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:179
 msgid "Use ad-blocking on the dnsmasq instance(s)"
 msgstr ""
 
@@ -666,11 +833,11 @@ msgid ""
 "Use of external dnsmasq config file detected, please set '%s' option to '%s'"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:412
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:528
 msgid "Use simultaneous processing"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:321
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:322
 msgid "Verbose output"
 msgstr ""
 
@@ -678,7 +845,7 @@ msgstr ""
 msgid "Version"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:332
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:409
 msgid "Version %s"
 msgstr ""
 
@@ -690,70 +857,78 @@ msgstr ""
 msgid "Waiting for trigger (on_start)"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:244
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:422
+msgid "Wednesday"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:372
+msgid "Weekly"
+msgstr ""
+
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:245
 msgid ""
 "You can limit the ad-blocking to the specific SmartDNS instance(s) (%smore "
 "information%s)."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:180
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:181
 msgid ""
 "You can limit the ad-blocking to the specific dnsmasq instance(s) (%smore "
 "information%s)."
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:135
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:136
 msgid "dnsmasq additional hosts"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:136
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:137
 msgid "dnsmasq config"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:138
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:139
 msgid "dnsmasq ipset"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:141
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:142
 msgid "dnsmasq nft set"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:143
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:144
 msgid "dnsmasq servers file"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:146
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:161
 msgid "failed to create backup file %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:149
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:164
 msgid "failed to create final block-list %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:147
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:162
 msgid "failed to delete data file %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:148
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/adblock-fast/status.js:163
 msgid "failed to restore backup file %s"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:334
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:335
 msgid "none"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:146
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:147
 msgid "smartdns domain set"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:148
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:149
 msgid "smartdns ipset"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:151
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:152
 msgid "smartdns nft set"
 msgstr ""
 
-#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:155
+#: applications/luci-app-adblock-fast/htdocs/luci-static/resources/view/adblock-fast/overview.js:156
 msgid "unbound adblock list"
 msgstr ""
index df1b48e59a3f476dde12b001807205317f25cf1f..861cadb93430f795b7a598d6ebc24ec05aae614f 100755 (executable)
@@ -15,7 +15,7 @@
 # ubus -S call luci.adblock-fast setInitAction '{"name": "adblock-fast", "action": "pause" }'
 # ubus -S call luci.adblock-fast setInitAction '{"name": "adblock-fast", "action": "stop" }'
 
-readonly rpcdCompat='9'
+readonly rpcdCompat='11'
 readonly adbFunctionsFile="${IPKG_INSTROOT}/etc/init.d/adblock-fast"
 if [ -s "$adbFunctionsFile" ]; then
 # shellcheck source=../../../../../adblock-fast/files/etc/init.d/adblock-fast
@@ -52,6 +52,204 @@ get_file_url_filesizes() {
        uci_changes "$name" && uci_commit "$name"
 }
 
+update_cron() {
+       local action="$1"
+       local cron_file="/etc/crontabs/root"
+       local tmp_file
+       local auto_enabled add_line
+
+       tmp_file="$(mktemp /tmp/adblock-fast.cron.XXXXXX)" || return 1
+
+       config_load "$packageName"
+
+       if [ -f "$cron_file" ]; then
+               if [ -s "$cron_file" ]; then
+                       grep -v -E "/etc/init.d/${packageName}[[:space:]]+dl" "$cron_file" > "$tmp_file" || true
+               else
+                       : > "$tmp_file"
+               fi
+       else
+               : > "$tmp_file" && touch "$cron_file" || { rm -f "$tmp_file"; return 1; }
+       fi
+
+       config_get auto_enabled 'config' 'auto_update_enabled' '0'
+       add_line=0
+       case "$action" in
+               disable|stop)
+                       add_line=0
+                       ;;
+               enable)
+                       if [ "$auto_enabled" = "1" ] && is_running "$packageName"; then
+                               add_line=1
+                       fi
+                       ;;
+               start)
+                       [ "$auto_enabled" = "1" ] && add_line=1
+                       ;;
+               *)
+                       if [ "$auto_enabled" = "1" ] && [ "$(is_enabled "$packageName")" = "1" ] && is_running "$packageName"; then
+                               add_line=1
+                       fi
+                       ;;
+       esac
+
+       if [ "$add_line" = "1" ]; then
+               local mode minute hour wday mday ndays nhours dom dow
+               config_get mode 'config' 'auto_update_mode' 'daily'
+               config_get minute 'config' 'auto_update_minute' '0'
+               config_get hour 'config' 'auto_update_hour' '4'
+               config_get wday 'config' 'auto_update_weekday' '0'
+               config_get mday 'config' 'auto_update_monthday' '1'
+               config_get ndays 'config' 'auto_update_every_ndays' '3'
+               config_get nhours 'config' 'auto_update_every_nhours' '6'
+               dom="*"
+               dow="*"
+               case "$mode" in
+                       weekly)
+                               dow="$wday"
+                               ;;
+                       monthly)
+                               dom="$mday"
+                               ;;
+                       every_n_days)
+                               dom="*/$ndays"
+                               ;;
+                       every_n_hours)
+                               hour="*/$nhours"
+                               ;;
+               esac
+               printf '%s %s %s * %s /etc/init.d/%s dl # adblock-fast-auto\n' "$minute" "$hour" "$dom" "$dow" "$packageName" >> "$tmp_file"
+       fi
+
+       if mv "$tmp_file" "$cron_file"; then
+               chmod 600 "$cron_file" 2>/dev/null
+       else
+               rm -f "$tmp_file"
+               return 1
+       fi
+
+       [ -x /etc/init.d/cron ] && /etc/init.d/cron reload >/dev/null 2>&1
+       return 0
+}
+
+sync_cron() {
+       local name action
+       name="$(basename "$1")"
+       action="$2"
+       [ "$name" = "$packageName" ] || { print_json_bool 'result' '0'; return 1; }
+       if update_cron "$action"; then
+               print_json_bool 'result' '1'
+       else
+               print_json_bool 'result' '0'
+       fi
+}
+
+get_cron_status() {
+       local name auto_enabled cron_init cron_bin cron_enabled cron_running
+       local cron_line_present cron_line_match
+       local mode minute hour wday mday ndays nhours dom dow
+       local cron_file line line_count match_count
+       name="$(basename "$1")"
+       name="${name:-$packageName}"
+
+       config_load "$packageName"
+       config_get auto_enabled 'config' 'auto_update_enabled' '0'
+       if [ "$auto_enabled" = "1" ]; then
+               auto_enabled=1
+       else
+               auto_enabled=0
+       fi
+
+       cron_init=0
+       cron_bin=0
+       cron_enabled=0
+       cron_running=0
+
+       [ -x /etc/init.d/cron ] && cron_init=1
+       if command -v crond >/dev/null 2>&1 || [ -x /usr/sbin/crond ]; then
+               cron_bin=1
+       fi
+       if [ "$cron_init" = "1" ] && /etc/init.d/cron enabled >/dev/null 2>&1; then
+               cron_enabled=1
+       fi
+       if [ "$cron_init" = "1" ] && /etc/init.d/cron status >/dev/null 2>&1; then
+               cron_running=1
+       elif pidof crond >/dev/null 2>&1; then
+               cron_running=1
+       fi
+
+       cron_line_present=0
+       cron_line_match=0
+
+       config_get mode 'config' 'auto_update_mode' 'daily'
+       config_get minute 'config' 'auto_update_minute' '0'
+       config_get hour 'config' 'auto_update_hour' '4'
+       config_get wday 'config' 'auto_update_weekday' '0'
+       config_get mday 'config' 'auto_update_monthday' '1'
+       config_get ndays 'config' 'auto_update_every_ndays' '3'
+       config_get nhours 'config' 'auto_update_every_nhours' '6'
+       dom="*"
+       dow="*"
+       case "$mode" in
+               weekly)
+                       dow="$wday"
+                       ;;
+               monthly)
+                       dom="$mday"
+                       ;;
+               every_n_days)
+                       dom="*/$ndays"
+                       ;;
+               every_n_hours)
+                       hour="*/$nhours"
+                       ;;
+       esac
+
+       cron_file="/etc/crontabs/root"
+       line_count=0
+       match_count=0
+       if [ -s "$cron_file" ]; then
+               set -f
+               while IFS= read -r line; do
+                       case "$line" in
+                               ''|[[:space:]]\#*|\#*) continue ;;
+                       esac
+                       line_count=$((line_count + 1))
+                       set -- $line
+                       if [ "${1#@}" != "$1" ]; then
+                               continue
+                       fi
+                       if [ "${6:-}" = "/etc/init.d/$packageName" ] && [ "${7:-}" = "dl" ] && \
+                               [ "$1" = "$minute" ] && [ "$2" = "$hour" ] && [ "$3" = "$dom" ] && \
+                               [ "$4" = "*" ] && [ "$5" = "$dow" ]; then
+                               match_count=$((match_count + 1))
+                       fi
+               done <<-EOF
+               $(grep -v -E '^[[:space:]]*#' "$cron_file" | grep -E "/etc/init.d/${packageName}[[:space:]]+dl" 2>/dev/null)
+               EOF
+               set +f
+       fi
+       if [ "$line_count" -gt 0 ]; then
+               cron_line_present=1
+       fi
+       if [ "$line_count" -eq 1 ] && [ "$match_count" -eq 1 ]; then
+               cron_line_match=1
+       fi
+
+       json_init
+       json_add_object "$name"
+       json_add_boolean 'auto_update_enabled' "$auto_enabled"
+       json_add_boolean 'cron_init' "$cron_init"
+       json_add_boolean 'cron_bin' "$cron_bin"
+       json_add_boolean 'cron_enabled' "$cron_enabled"
+       json_add_boolean 'cron_running' "$cron_running"
+       json_add_boolean 'cron_line_present' "$cron_line_present"
+       json_add_boolean 'cron_line_match' "$cron_line_match"
+       json_close_object
+       json_dump
+       json_cleanup
+}
+
 get_init_list() {
        local name
        name="$(basename "$1")"
@@ -70,31 +268,35 @@ get_init_list() {
 }
 
 set_init_action() {
-       local name action="$2" cmd
-       name="$(basename "$1")"
-       name="${name:-$packageName}"
-       if [ ! -f "/etc/init.d/$name" ]; then
+       local action="$2" cmd
+       [ "$(basename "$1")" = "$packageName" ] || { print_json_bool 'result' '0'; return 1; }
+       if [ ! -f "/etc/init.d/$packageName" ]; then
                print_json_string 'error' 'Init script not found!'
                return
        fi
        case $action in
                enable)
-                       cmd="/etc/init.d/${name} ${action}"
-                       cmd="${cmd} && uci_set ${name} config enabled 1 && uci_commit $name"
+                       cmd="/etc/init.d/${packageName} ${action}"
+                       cmd="${cmd} && uci_set ${packageName} config enabled 1 && uci_commit $packageName"
                ;;
                disable)
-                       cmd="/etc/init.d/${name} ${action}"
-                       cmd="${cmd} && uci_set ${name} config enabled 0 && uci_commit $name"
+                       cmd="/etc/init.d/${packageName} ${action}"
+                       cmd="${cmd} && uci_set ${packageName} config enabled 0 && uci_commit $packageName"
                ;;
                start|stop|reload|restart|dl|pause)
-                       cmd="/etc/init.d/${name} ${action}"
+                       cmd="/etc/init.d/${packageName} ${action}"
                ;;
        esac
+       local result=0
        if [ -n "$cmd" ] && eval "$cmd" >/dev/null 2>&1; then
-               print_json_bool "result" '1'
-       else
-               print_json_bool "result" '0'
+               result=1
        fi
+       case "$action" in
+               enable|start|disable|stop)
+                       update_cron "$action" || logger -t adblock-fast 'error' 'failed to update cron'
+                       ;;
+       esac
+       print_json_bool 'result' "$result"
 }
 
 get_init_status() {
@@ -229,12 +431,6 @@ get_platform_support() {
        json_cleanup
 }
 
-get_ubus_info() {
-       local name
-       name="$(basename "$1")"
-       name="${name:-$packageName}"
-       ubus call service list '{"name":"'"$name"'"}'
-}
 
 case "$1" in
        list)
@@ -242,16 +438,20 @@ case "$1" in
                json_add_object "getFileUrlFilesizes"
                        json_add_string 'name' 'name'
                json_close_object
+               json_add_object "syncCron"
+                       json_add_string 'name' 'name'
+                       json_add_string 'action' 'action'
+               json_close_object
                json_add_object "getInitList"
                        json_add_string 'name' 'name'
                json_close_object
                json_add_object "getInitStatus"
                        json_add_string 'name' 'name'
                json_close_object
-               json_add_object "getPlatformSupport"
+               json_add_object "getCronStatus"
                        json_add_string 'name' 'name'
                json_close_object
-               json_add_object "getUbusInfo"
+               json_add_object "getPlatformSupport"
                        json_add_string 'name' 'name'
                json_close_object
                json_add_object "setInitAction"
@@ -270,6 +470,14 @@ case "$1" in
                                json_cleanup
                                get_file_url_filesizes "$name"
                                ;;
+                       syncCron)
+                               read -r input
+                               json_load "$input"
+                               json_get_var name 'name'
+                               json_get_var action 'action'
+                               json_cleanup
+                               sync_cron "$name" "$action"
+                               ;;
                        getInitList)
                                read -r input
                                json_load "$input"
@@ -284,19 +492,19 @@ case "$1" in
                                json_cleanup
                                get_init_status "$name"
                                ;;
-                       getPlatformSupport)
+                       getCronStatus)
                                read -r input
                                json_load "$input"
                                json_get_var name 'name'
                                json_cleanup
-                               get_platform_support "$name"
+                               get_cron_status "$name"
                                ;;
-                       getUbusInfo)
+                       getPlatformSupport)
                                read -r input
                                json_load "$input"
                                json_get_var name 'name'
                                json_cleanup
-                               get_ubus_info "$name"
+                               get_platform_support "$name"
                                ;;
                        setInitAction)
                                read -r input
index c9484ee547c35d9482264701cd1f434fe58f427c..d9db2287837b7b1e754ea9ebb989e5e622c086d0 100644 (file)
                                        "getFileUrlFilesizes",
                                        "getInitList",
                                        "getInitStatus",
-                                       "getPlatformSupport",
-                                       "getUbusInfo"
+                                       "getCronStatus",
+                                       "getPlatformSupport"
+                               ],
+                               "service": [
+                                       "list"
                                ]
                        },
                        "uci": [
@@ -27,6 +30,7 @@
                        ],
                        "ubus": {
                                "luci.adblock-fast": [
+                                       "syncCron",
                                        "setInitAction"
                                ]
                        }
git clone https://git.99rst.org/PROJECT